Java tutorial
package com.atlassian.clover.ant.taskdefs; import clover.com.google.common.collect.Sets; import clover.org.apache.commons.lang3.reflect.FieldUtils; import com.atlassian.clover.ant.AntInstrUtils; import com.atlassian.clover.instr.java.Instrumenter; import com.atlassian.clover.instr.tests.FileMappedTestDetector; import com.atlassian.clover.instr.tests.TestDetector; import com.atlassian.clover.api.CloverException; import com.atlassian.clover.CloverStartup; import com.atlassian.clover.Contract; import com.atlassian.clover.Logger; import com.atlassian.clover.ant.AntLogger; import com.atlassian.clover.ant.tasks.AntInstrumentationConfig; import com.atlassian.clover.ant.tasks.TestSourceSet; import com.atlassian.clover.util.ClassPathUtil; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Javac; import org.apache.tools.ant.util.JavaEnvUtils; import org.apache.tools.ant.taskdefs.compilers.CompilerAdapter; import org.apache.tools.ant.taskdefs.compilers.CompilerAdapterFactory; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.PatternSet; import org.jetbrains.annotations.Nullable; import java.io.File; import java.lang.reflect.Field; import java.util.Collection; import java.util.List; import java.util.Set; /** * error handling in this class sux - but it is forced on us by Ant, or more * precisely Javac, which doesn't respect Build exceptions thrown out of * setJavac(). So we have to store them and throw them later out of execute. */ public class CloverCompilerAdapter implements CompilerAdapter { /** * A helper class to "fiddle" with the "build.complier" of * compilerVersion setting in the associated Javac instance. <p> * * This fixes bug Defect-6486: The problem is that if you pass * "source=1.3" to the JAVAC task in Ant, and you are running under JDK * 1.3, then the following happens: a) In Ant1.4.1, "-source" get's * passed to javac.exe, which it doesn't understand; b) In Ant1.5+, some * smarts occur, and "-source" WILL NOT get passed to javac.exe; c) In * Ant1.5+, but when "with.clover" is enabled, then the "smarts" do not * kick in, java.exe complains that it does not understand "-source". * * The problem c) occurs because the "smarts" depends on the setting of * the compilerVersion (aka build.compiler) property. <p> * * The solution, which this class implements, is to "fudge" then * "restore" the compilerVersion setting before calling the underlying * compiler adapther */ class CompilerVersionFiddler { private String ourCompilerStr = null; /** * set the compilerVersion to something the underlying * CompilerAdapter will understand. */ void push() { try { ourCompilerStr = javac.getCompilerVersion(); // the underlying compiler needs to think it is the compiler for this javac javac.setCompiler(compilerDelegate); } catch (NoSuchMethodError e) { // ignore, nothing we can do about this bug in Ant141 anyway } } /** restore compilerVersion */ void pop() { if (ourCompilerStr != null) { try { javac.setCompiler(ourCompilerStr);// restore it } catch (NoSuchMethodError e) { // ignore, nothing we can do about this bug in Ant141 anyway } } ourCompilerStr = null; } } /** use to log messages * */ private Logger log; private String compilerDelegate; private Javac javac; private CompilerAdapter underlyingCompiler; private Project project; private CloverException error; private String javacName; private File tmpDir; private boolean preserveInstr; private CompilerVersionFiddler compilerVersionFiddler = new CompilerVersionFiddler(); private void cloverLogging() { javacName = javac.getTaskName(); javac.setTaskName("clover"); } private void javacLogging() { javac.setTaskName(javacName); } /** * @param aJavac The new javac value * @see CompilerAdapter#setJavac * */ @Override @SuppressWarnings("unchecked") public void setJavac(final Javac aJavac) { Contract.pre(aJavac != null); javac = aJavac; project = javac.getProject(); Logger.setInstance(new AntLogger(project, javac)); log = Logger.getInstance(); cloverLogging(); CloverStartup.loadLicense(log); if (javac.getDestdir() == null) { error = new CloverException("When using Clover you need to set the destdir on <javac>"); return; } AntInstrumentationConfig instrConfig = AntInstrumentationConfig.getFrom(project); if (instrConfig == null) { log.warn( "Clover configuration was not found (did you call <ant> or <antcall> without inheritRefs=true?)." + " Default settings for code instrumentation will be used."); instrConfig = new AntInstrumentationConfig(project); } compilerDelegate = instrConfig.getCompilerDelegate(); preserveInstr = instrConfig.isPreserve(); if (javac.isForkedJavac()) { log.info("Fork is set to true and will be respected (ignore any warnings from Ant)."); if (compilerDelegate != null && !"extJavac".equals(compilerDelegate)) { log.warn("Ignoring clover.compiler setting because fork is true. Using 'extJavac' compiler."); } compilerDelegate = "extJavac"; } if (compilerDelegate != null && compilerDelegate.equals(this.getClass().getName())) { error = new CloverException( "You cannot set clover.compiler to be the Clover Compiler Adapter. Check the value you are passing to clover.compiler"); return; } if (compilerDelegate == null) { compilerDelegate = getDefaultAntCompiler(); } log.debug("using underlying compiler '" + compilerDelegate + "'"); underlyingCompiler = CompilerAdapterFactory.getCompiler(compilerDelegate, javac); log.debug("underlying compiler instance is " + underlyingCompiler.getClass().getName()); if (instrConfig.isEnabled()) { // locally set source level overrides previously configured value final String src = javac.getSource(); if (src != null) { instrConfig.setSourceLevel(src); } // locally set encoding overrides previously configured value final String encoding = javac.getEncoding(); if (encoding != null) { instrConfig.setEncoding(javac.getEncoding()); } // determine if there are any patternsets restricting instrumentation final PatternSet instrPatternSet = AntInstrUtils.calcInstrPatternSet(project); // determine if there are any filesets restricting instrumentation final List<FileSet> instrFileSetsList = AntInstrUtils.calcInstrFileSets(project); // the compileset has the original list of files that were handed to javac to compile final File[] compileList = getJavacCompileList(); if (compileList == null) { error = new CloverException( "The javac.compileList is null. Unable to integrate Clover with Javac."); return; } final Set<File> compileSet = Sets.newHashSet(compileList); //the copyset contains files that are to be compiled without instrumentation. Initially //this is the entire compile set final Collection<File> copySet = Sets.newHashSet(compileSet); //the instrset contains files that need instrumentation. This is initially empty. final Collection<File> instrSet = Sets.newHashSet(); //Sort source into copied source and instrumented source AntInstrUtils.sieveSourceForInstrumentation(project, javac.getSrcdir(), instrPatternSet, instrFileSetsList, compileSet, copySet, instrSet); // apply test source definitions if they are specified final TestDetector testDetector = calcTestDetector(compileSet, copySet, instrSet); if (testDetector != null) { instrConfig.setTestDetector(testDetector); } // generate a new list for the compiler. Add all the files that won't be instrumented. final Collection<File> replacementCompileSet = Sets.newHashSet(copySet); try { tmpDir = AntInstrUtils.createInstrDir(instrConfig.getTmpDir()); final Instrumenter instrumenter = new Instrumenter(log, instrConfig); // do the actual instrumentation instrumenter.startInstrumentation(); for (final File f : instrSet) { final File copy = instrumenter.instrument(f, tmpDir, instrConfig.getEncoding()); replacementCompileSet.add(copy); } instrumenter.endInstrumentation(); if (!setJavacCompileList(replacementCompileSet.toArray(new File[replacementCompileSet.size()]))) { error = new CloverException( "Failed to write to javac.compileList field. Unable to integrate Clover with Javac."); return; } } catch (CloverException e) { error = e; log.error(error.getMessage()); log.error("** Error(s) occurred and the instrumentation process can't continue."); AntInstrUtils.cleanUpInstrDir(tmpDir, preserveInstr); } final String cloverPathStr = ClassPathUtil.getCloverJarPath(); if (cloverPathStr != null) { final Path cloverPath = new Path(project, cloverPathStr); javac.setClasspath(cloverPath); } } else { log.info("** Clover is disabled. Delegating straight to the compiler."); } compilerVersionFiddler.push(); underlyingCompiler.setJavac(javac); compilerVersionFiddler.pop(); javacLogging(); } /** * Read value of the super.javac.compileList field, which has non-public visiblity. * @return javac.compileList protected field or <code>null</code> if reflection failed */ @Nullable private File[] getJavacCompileList() { try { return (File[]) FieldUtils.readField(javac, "compileList", true); } catch (SecurityException ex) { log.error("** Failed to access javac.compileList protected field", ex); } catch (IllegalAccessException ex) { log.error("** Failed to access javac.compileList protected field", ex); } catch (ClassCastException ex) { log.error("** Failed to access javac.compileList protected field", ex); } return null; } /** * Write value to the super.javac.compileList field, which has non-public visiblity. * @return boolean <code>true</code> if field was set, <code>false</code> otherwise */ private boolean setJavacCompileList(File[] newValue) { try { FieldUtils.writeField(javac, "compileList", newValue, true); return true; } catch (SecurityException ex) { log.error("** Failed to access javac.compileList protected field", ex); } catch (IllegalAccessException ex) { log.error("** Failed to access javac.compileList protected field", ex); } catch (IllegalArgumentException ex) { log.error("** Failed to access javac.compileList protected field", ex); } return false; } private TestDetector calcTestDetector(Set<File> compileSet, Collection<File> copySet, Collection<File> instrSet) { final AntInstrumentationConfig config = AntInstrumentationConfig.getFrom(project); if (config != null && config.getTestSources() != null) { final FileMappedTestDetector fileMappedTestDetector = new FileMappedTestDetector(); final List<TestSourceSet> testSourcesList = config.getTestSources(); for (final TestSourceSet testSourceSet : testSourcesList) { final Set<File> included = testSourceSet.getIncludedFiles(); for (final File inc : included) { if (compileSet.contains(inc)) { instrSet.add(inc); copySet.remove(inc); } } final Set<File> excluded = testSourceSet.getExcludedFiles(); for (final File exc : excluded) { if (compileSet.contains(exc)) { instrSet.remove(exc); copySet.add(exc); } } fileMappedTestDetector.addTestSourceMatcher(testSourceSet); } return fileMappedTestDetector; } return null; } public static String getDefaultAntCompiler() { if (JavaEnvUtils.getJavaVersion().equals(JavaEnvUtils.JAVA_1_1) || JavaEnvUtils.getJavaVersion().equals(JavaEnvUtils.JAVA_1_2)) { return "classic"; } return "modern"; } /** * @see CompilerAdapter#execute() * */ @Override public boolean execute() throws BuildException { cloverLogging(); if (error != null) { Throwable cause = error; if (error.getCause() != null) { cause = error.getCause(); } throw new BuildException(cause); } try { log.debug("delegating to compiler impl: " + underlyingCompiler.getClass().getName()); // delegate to the actual compiler instance javacLogging(); compilerVersionFiddler.push(); final boolean retval = underlyingCompiler.execute(); compilerVersionFiddler.pop(); return retval; } finally { cloverLogging(); AntInstrUtils.cleanUpInstrDir(tmpDir, preserveInstr); javacLogging(); } } }