package jenkins.model;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.core.JVM;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
import hudson.ExtensionPoint;
import hudson.model.AbstractDescribableImpl;
import hudson.model.AbstractProject;
import hudson.model.Job;
import hudson.model.Run;
import hudson.tasks.LogRotator;
import hudson.util.RobustReflectionConverter;

import java.io.IOException;

/**
 * Implementation of "Discard old build records" feature.
 *
 * <p>
 * This extension point allows plugins to implement a different strategy to decide what builds to discard
 * and what builds to keep.
 *
 * @author Kohsuke Kawaguchi
 * @since 1.503
 */
public abstract class BuildDiscarder extends AbstractDescribableImpl<BuildDiscarder> implements ExtensionPoint {
    /**
     * Called to perform "garbage collection" on the job to discard old build records.
     *
     * <p>
     * Normally invoked automatically jobs when new builds occur.
     * The general expectation is that those marked as {@link Run#isKeepLog()} will be kept untouched.
     * To delete the build record, call {@link Run#delete()}.
     *
     * @see Job#logRotate()
     */
    public abstract void perform(Job<?,?> job) throws IOException, InterruptedException;

    @Override
    public BuildDiscarderDescriptor getDescriptor() {
        return (BuildDiscarderDescriptor)super.getDescriptor();
    }

    /**
     * {@link AbstractProject#logRotator} used to be typed as {@link LogRotator},
     * so such configuration file ends up trying to unmarshal {@link BuildDiscarder} and
     * not its subtype.
     *
     * This converter makes this work by unmarshalling a {@link LogRotator}.
     */
    public static class ConverterImpl implements Converter {
        private RobustReflectionConverter ref;

        public ConverterImpl(Mapper m) {
            ref = new RobustReflectionConverter(m,new JVM().bestReflectionProvider()) {
                @Override
                protected Object instantiateNewInstance(HierarchicalStreamReader reader, UnmarshallingContext context) {
                    return reflectionProvider.newInstance(LogRotator.class);
                }
            };
        }

        public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
            // abstract class, so there shouldn't be any instance.
            throw new UnsupportedOperationException();
        }

        public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
            // force unmarshal as LogRotator
            return ref.unmarshal(reader,context);
        }

        public boolean canConvert(Class type) {
            return type==BuildDiscarder.class;
        }
    }
}
