/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2003-2005 University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;

import edu.umd.cs.findbugs.ba.AnalysisContext;

/**
 * A DetectorFactory is responsible for creating instances of Detector objects
 * and for maintaining meta-information about the detector class.
 *
 * @author David Hovemeyer
 * @see Detector
 */
public class DetectorFactory {
	private static final boolean DEBUG_JAVA_VERSION = SystemProperties.getBoolean("findbugs.debug.javaversion");

	private static final Class<?>[] constructorArgTypes = new Class<?>[]{BugReporter.class};

	static class ReflectionDetectorCreator {
		private Class<?> detectorClass;
		private Method setAnalysisContext;

		ReflectionDetectorCreator(Class<?> detectorClass) {
			this.detectorClass = detectorClass;

			try {
				setAnalysisContext = detectorClass.getDeclaredMethod(
						"setAnalysisContext", new Class[]{AnalysisContext.class});
			} catch (NoSuchMethodException e) {
				// Ignore
			}
		}

		public Detector createDetector(BugReporter bugReporter) {
			try {
				Constructor<?> constructor =
					detectorClass.getConstructor(constructorArgTypes);
				Detector detector = (Detector) constructor.newInstance(new Object[]{bugReporter});
				if (setAnalysisContext != null) {
					setAnalysisContext.invoke(
							detector,
							new Object[]{AnalysisContext.currentAnalysisContext()});
				}
				return detector;
			} catch (Exception e) {
				throw new RuntimeException("Could not instantiate " + detectorClass.getName() +
						" as Detector", e);
			}
		}

		public Detector2 createDetector2(BugReporter bugReporter) {
			if (Detector2.class.isAssignableFrom(detectorClass)) {
				try {
					Constructor<?> constructor =
						detectorClass.getConstructor(constructorArgTypes);
					return (Detector2) constructor.newInstance(new Object[]{bugReporter});
				} catch (Exception e) {
					throw new RuntimeException("Could not instantiate " + detectorClass.getName() +
							" as Detector2", e);
				}
			}

			if (Detector.class.isAssignableFrom(detectorClass)) {
				if (NonReportingDetector.class.isAssignableFrom(detectorClass))
					return new NonReportingDetectorToDetector2Adapter(createDetector(bugReporter));
				return new DetectorToDetector2Adapter(
						createDetector(bugReporter));
				
			}

			throw new RuntimeException("Class " + detectorClass.getName() + " is not a detector class");
		}

		public Class<?> getDetectorClass() {
			return detectorClass;
		}
	}

	private Plugin plugin;
	private final ReflectionDetectorCreator detectorCreator;
	private int positionSpecifiedInPluginDescriptor;
	private boolean defEnabled;
	private final String speed;
	private final String reports;
	private final String requireJRE;
	private String detailHTML;
	private int priorityAdjustment;
    private boolean enabledButNonReporting;
	private boolean hidden;

	/**
	 * Constructor.
	 *
	 * @param plugin        the Plugin the Detector is part of
	 * @param detectorClass the Class object of the Detector
	 * @param enabled       true if the Detector is enabled by default, false if disabled
	 * @param speed         a string describing roughly how expensive the analysis performed
	 *                      by the detector is; suggested values are "fast", "moderate", and "slow"
	 * @param reports       comma separated list of bug pattern codes reported
	 *                      by the detector; empty if unknown
	 * @param requireJRE    string describing JRE version required to run the
	 *                      the detector: e.g., "1.5"
	 */
	public DetectorFactory(Plugin plugin,
						   Class<?> detectorClass, boolean enabled, String speed, String reports,
						   String requireJRE) {
		this.plugin = plugin;
		this.detectorCreator = new ReflectionDetectorCreator(detectorClass);
		this.defEnabled = enabled;
		this.speed = speed;
		this.reports = reports;
		this.requireJRE = requireJRE;
		this.priorityAdjustment = 0;
		this.hidden = false;
	}

	/**
	 * Set the overall position in which this detector was specified
	 * in the plugin descriptor.
	 * 
	 * @param positionSpecifiedInPluginDescriptor position in plugin descriptor
	 */
	public void setPositionSpecifiedInPluginDescriptor(
			int positionSpecifiedInPluginDescriptor) {
		this.positionSpecifiedInPluginDescriptor = positionSpecifiedInPluginDescriptor;
	}

	/**
	 * Get the overall position in which this detector was specified
	 * in the plugin descriptor.
	 * 
	 * @return position in plugin descriptor
	 */
	public int getPositionSpecifiedInPluginDescriptor() {
		return positionSpecifiedInPluginDescriptor;
	}

	/**
	 * Get the Plugin that this Detector is part of.
	 * 
	 * @return the Plugin this Detector is part of
	 */
	public Plugin getPlugin() {
		return plugin;
	}

	/**
	 * Determine whether the detector class is a subtype of the given class (or interface).
	 * 
	 * @param otherClass a class or interface
	 * @return true if the detector class is a subtype of the given class or interface
	 */
	public boolean isDetectorClassSubtypeOf(Class<?> otherClass) {
		return otherClass.isAssignableFrom(detectorCreator.getDetectorClass());
	}

	/**
	 * Return whether or not this DetectorFactory produces detectors
	 * which report warnings.
	 * 
	 * @return true if the created Detectors report warnings, false if not
	 */
	public boolean isReportingDetector() {
		return !isDetectorClassSubtypeOf(TrainingDetector.class)
			&& !isDetectorClassSubtypeOf(FirstPassDetector.class);

	}

	/**
	 * Check to see if we are running on a recent-enough JRE for
	 * this detector to be enabled.
	 * 
	 * @return true if the current JRE is recent enough to run the Detector,
	 *         false if it is too old
	 */
	public boolean isEnabledForCurrentJRE() {
		if (requireJRE.equals(""))
			return true;
		try {
			JavaVersion requiredVersion = new JavaVersion(requireJRE);
			JavaVersion runtimeVersion = JavaVersion.getRuntimeVersion(); 

			if (DEBUG_JAVA_VERSION) {
				System.out.println(
						"Checking JRE version for " + getShortName() +
						" (requires " + requiredVersion +
						", running on " + runtimeVersion + ")");
			}


			boolean enabledForCurrentJRE = runtimeVersion.isSameOrNewerThan(requiredVersion);
			if (DEBUG_JAVA_VERSION) {
				System.out.println("\t==> " + enabledForCurrentJRE);
			}
			return enabledForCurrentJRE;
		} catch (JavaVersionException e) {
			if (DEBUG_JAVA_VERSION) {
				System.out.println("Couldn't check Java version: " + e.toString());
				e.printStackTrace(System.out);
			}
			return false;
		}
	}

	/**
	 * Set visibility of the factory (to GUI dialogs to configure detectors).
	 * Invisible detectors are those that are needed behind the scenes,
	 * but shouldn't be explicitly enabled or disabled by the user.
	 *
	 * @param hidden true if this factory should be hidden, false if not
	 */
	public void setHidden(boolean hidden) {
		this.hidden = hidden;
	}

	/**
	 * Get visibility of the factory (to GUI dialogs to configure detectors).
	 */
	public boolean isHidden() {
		return hidden;
	}

	/**
	 * Is this factory enabled by default
	 */
	public boolean isDefaultEnabled() {
		return defEnabled;
	}

	/**
	 * Set the priority adjustment for the detector produced by this factory.
	 * 
	 * @param priorityAdjustment the priority adjustment
	 */
	public void setPriorityAdjustment(int priorityAdjustment) {
		this.priorityAdjustment = priorityAdjustment;
	}
    public void setEnabledButNonReporting(boolean notReporting) {
    	this.enabledButNonReporting = notReporting;
    }

	/**
	 * Get the priority adjustment for the detector produced by this factory.
	 * 
	 * @return the priority adjustment
	 */
	public int getPriorityAdjustment() {
        if (enabledButNonReporting) return 100;
		return priorityAdjustment;
	}

	/**
	 * Get the speed of the Detector produced by this factory.
	 */
	public String getSpeed() {
		return speed;
	}

	/**
	 * Get list of bug pattern codes reported by the detector: blank if unknown.
	 */
	public String getReportedBugPatternCodes() {
		return reports;
	}

	/**
	 * Get set of all BugPatterns this detector reports.
	 * An empty set means that we don't know what kind of
	 * bug patterns might be reported.
	 */
	public Set<BugPattern> getReportedBugPatterns() {
		Set<BugPattern> result = new TreeSet<BugPattern>();
		StringTokenizer tok = new StringTokenizer(reports, ",");
		while (tok.hasMoreTokens()) {
			String type = tok.nextToken();
			BugPattern bugPattern = I18N.instance().lookupBugPattern(type);
			if (bugPattern != null)
				result.add(bugPattern);
		}
		return result;
	}

	/**
	 * Get an HTML document describing the Detector.
	 */
	public String getDetailHTML() {
		return detailHTML;
	}

	/**
	 * Set the HTML document describing the Detector.
	 */
	public void setDetailHTML(String detailHTML) {
		this.detailHTML = detailHTML;
	}

	/**
	 * Create a Detector instance.
	 * This method is only guaranteed to work for
	 * old-style detectors using the BCEL bytecode framework.
	 * 
	 * @param bugReporter the BugReporter to be used to report bugs
	 * @return the Detector
	 * @deprecated Use createDetector2 in new code
	 */
	@Deprecated
	public Detector create(BugReporter bugReporter) {
		return detectorCreator.createDetector(bugReporter);
	}

	/**
	 * Create a Detector2 instance.
	 *
	 * @param bugReporter the BugReporter to be used to report bugs
	 * @return the Detector2
	 */
	public Detector2 createDetector2(BugReporter bugReporter) {
		return detectorCreator.createDetector2(bugReporter);
	}

	/**
	 * Get the short name of the Detector.
	 * This is the name of the detector class without the package qualification.
	 */
	public String getShortName() {
		String className = detectorCreator.getDetectorClass().getName();
		int endOfPkg = className.lastIndexOf('.');
		if (endOfPkg >= 0)
			className = className.substring(endOfPkg + 1);
		return className;
	}

	/**
	 * Get the full name of the detector.
	 * This is the name of the detector class, with package qualification.
	 */
	public String getFullName() {
		return detectorCreator.getDetectorClass().getName();
	}
}

// vim:ts=4
