diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/IContract.java b/src/main/org/apache/tools/ant/taskdefs/optional/IContract.java new file mode 100644 index 000000000..64f3f3488 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/IContract.java @@ -0,0 +1,628 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.tools.ant.taskdefs.optional; + + + + + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Date; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.Mkdir; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Instruments Java classes with iContract + * DBC preprocessor. + * + * @author Aslak Hellesøy + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
AttributeDescriptionRequired
srcdirLocation of the java filesYes
instrumentdirIndicates where the instrumented java and class files + * should goYes
repositorydirIndicates where the repository java and class files should + * goYes
preIndicates whether or not to instrument for preconditions. + * Defaults to trueNo
postIndicates whether or not to instrument for postconditions. + * Defaults to trueNo
invariantIndicates whether or not to instrument for invariants. + * Defaults to trueNo
failthrowableThe full name of the Throwable (Exception) that should be + * thrown when an assertion is violated. Defaults to java.lang.ErrorNo
controlfileThe name of the control file to pass to iContract. Default + * is to not pass a fileNo
verbosityIndicates the verbosity level of iContract. Any combination + * of error*,warning*,note*,info*,progress*,debug* (comma separated) can be + * used. Defaults to error*,warning*No
+ * + *

+ * Note: iContract will use the java compiler indicated by the project's + * build.compiler property. See documentation for the Javac task for + * more information. + * + *

Example:

+ * + *
+ * <!-- =================================================================== -->
+ * <!-- Instruments source codes with iContract                             -->
+ * <!-- =================================================================== -->
+ * <target name="instrument" depends="compile">
+ *   <icontract
+ *     srcdir="${build.src}"
+ *     instrumentdir="${instrumented.dir}"
+ *     repositorydir="${repository.dir}"
+ *   >
+ *     <classpath>
+ *       <fileset dir="./lib">
+ *         <include name="*.jar"/>
+ *       </fileset>
+ *     </classpath>
+ *   </icontract>
+ * </target>
+ * 
+ * + */ +public class IContract extends Task { + + /** \ on windows, / on linux/unix */ + private static final String ps = System.getProperty( "path.separator" ); + + /** compiler to use for instrumenation */ + private String icCompiler = "javac"; + + /** temporary file with file names of all java files to be instrumented */ + private File targets = null; + + /** will be set to true if any of the sourca files are newer than the instrumented files */ + private boolean dirty = false; + + /** set to true if the iContract jar is missing */ + private boolean iContractMissing = false; + + /** source file root */ + private File srcDir = null; + + /** instrumentation root */ + private File instrumentDir = null; + + /** repository root */ + private File repositoryDir = null; + + /** classpath */ + private Path classpath = null; + + /** The class of the Throwable to be thrown on failed assertions */ + private String failThrowable = "java.lang.Error"; + + /** The -v option */ + private String verbosity = "error*,warning*"; + + /** Indicates whether or not to use internal compilation */ + private boolean internalcompilation = false; + + /** The -m option */ + private File controlFile = null; + + /** Indicates whether or not to instrument for preconditions */ + private boolean pre = true; + + /** Indicates whether or not to instrument for postconditions */ + private boolean post = true; + + /** Indicates whether or not to instrument for invariants */ + private boolean invariant = true; + + /** Indicates whether or not to instrument all files regardless of timestamp */ + // can't be explicitly set, is set if control file exists and is newer than any source file + private boolean instrumentall = true; + + /** + * Sets the source directory + * + * @param srcDir the source directory + */ + public void setSrcdir( File srcDir ) { + this.srcDir = srcDir; + } + + /** + * Sets the instrumentation directory + * + * @param instrumentDir the source directory + */ + public void setInstrumentdir( File instrumentDir ) { + this.instrumentDir = instrumentDir; + } + + /** + * Sets the repository directory + * + * @param repositoryDir the source directory + */ + public void setRepositorydir( File repositoryDir ) { + this.repositoryDir = repositoryDir; + } + + /** + * Turns on/off precondition instrumentation + * + * @param pre true turns it on + */ + public void setPre( boolean pre ) { + this.pre = pre; + } + + /** + * Turns on/off postcondition instrumentation + * + * @param post true turns it on + */ + public void setPost( boolean post ) { + this.post = post; + } + + /** + * Turns on/off invariant instrumentation + * + * @param invariant true turns it on + */ + public void setInvariant( boolean invariant ) { + this.invariant = invariant; + } + + /** + * Sets the Throwable (Exception) to be thrown on assertion violation + * + * @param clazz the Throwable class + */ + public void setFailthrowable( Class clazz ) { + this.failThrowable = clazz.getName(); + } + + /** + * Sets the verbosity level of iContract. Any combination of + * error*,warning*,note*,info*,progress*,debug* (comma separated) + * can be used. Defaults to error*,warning* + * + * @param clazz the Throwable class + */ + public void setVerbosity( String verbosity ) { + this.verbosity = verbosity; + } + + /** + * Turns on/off internal compilation. + *
+ * If set to true, Sun's javac will be run within the same VM as Ant. + *
+ * If set to false, the compiler indicated by the project property + * build.compiler will be used, defaulting to javac, + * and run in a separate VM. + * + * @param internalcompilation set to true for internal compilation + */ + /* FIXME: Doesn't work + public void setInternalcompilation( boolean internalcompilation ) { + this.internalcompilation = internalcompilation; + } + */ + + /** + * Sets the control file to pass to iContract. + * + * @param clazz the Throwable class + */ + public void setControlfile( File controlFile ) { + this.controlFile = controlFile; + } + + /** + * Sets the classpath to be used for invocation of iContract. + * + * @path the classpath + */ + public void setClasspath( Path path ) { + createClasspath().append( path ); + } + + /** + * Creates a nested classpath element + * + * @return the nested classpath element + */ + public Path createClasspath() { + if (classpath == null) { + classpath = new Path( getProject() ); + } + return classpath; + } + + /** + * Adds a reference to a classpath defined elsewhere. + * + * @param reference referenced classpath + */ + public void setClasspathRef( Reference reference ) { + createClasspath().setRefid( reference ); + } + + /** + * Executes the task + * + * @exception BuildException if the instrumentation fails + */ + public void execute() throws BuildException { + preconditions(); + scan(); + if( dirty ) { + // We want to be notified if iContract jar is missing. This makes life easier for the user + // who didn't understand that iContract is a separate library (duh!) + getProject().addBuildListener( new IContractPresenceDetector() ); + + // Prepare the directories for iContract. iContract will make them if they + // don't exist, but for some reason I don't know, it will complain about the REP files + // afterwards + Mkdir mkdir = (Mkdir) project.createTask( "mkdir" ); + mkdir.setDir( instrumentDir ); + mkdir.execute(); + mkdir.setDir( repositoryDir ); + mkdir.execute(); + + // Set the compiler + setCompiler(); + + // Set the classpath that is needed for regular Javac compilation + Path baseClasspath = createClasspath(); + + // Create the classpath required to compile the sourcefiles BEFORE instrumentation + Path beforeInstrumentationClasspath = ((Path) baseClasspath.clone()); + beforeInstrumentationClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + + // Create the classpath required to compile the sourcefiles AFTER instrumentation + Path afterInstrumentationClasspath = ((Path) baseClasspath.clone()); + afterInstrumentationClasspath.append( new Path( getProject(), instrumentDir.getAbsolutePath() ) ); + afterInstrumentationClasspath.append( new Path( getProject(), repositoryDir.getAbsolutePath() ) ); + afterInstrumentationClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + + // Create the classpath required to automatically compile the repository files + Path repositoryClasspath = ((Path) baseClasspath.clone()); + repositoryClasspath.append( new Path( getProject(), instrumentDir.getAbsolutePath() ) ); + repositoryClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + repositoryClasspath.append( new Path( getProject(), repositoryDir.getAbsolutePath() ) ); + + // Create the classpath required for iContract itself + Path iContractClasspath = ((Path) baseClasspath.clone()); + iContractClasspath.append( new Path( getProject(), System.getProperty( "java.home" ) + File.separator + ".." + File.separator + "lib" + File.separator + "tools.jar" ) ); + iContractClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + iContractClasspath.append( new Path( getProject(), repositoryDir.getAbsolutePath() ) ); + iContractClasspath.append( new Path( getProject(), instrumentDir.getAbsolutePath() ) ); + + // Create a forked java process + Java iContract = (Java) project.createTask( "java" ); + iContract.setTaskName( getTaskName() ); + iContract.setFork( true ); + iContract.setClassname( "com.reliablesystems.iContract.Tool" ); + iContract.setClasspath( iContractClasspath ); + + // Build the arguments to iContract + StringBuffer args = new StringBuffer(); + args.append( directiveString() ); + args.append( "-v" ).append( verbosity ).append( " " ); + args.append( "-b" ).append( icCompiler ).append( "\"" ).append( " -classpath " ).append( beforeInstrumentationClasspath ).append( "\" " ); + args.append( "-c" ).append( icCompiler ).append( "\"" ).append( " -classpath " ).append( afterInstrumentationClasspath ).append( "\" " ); + args.append( "-n" ).append( icCompiler ).append( "\"" ).append( " -classpath " ).append( repositoryClasspath ).append( "\" " ); + args.append( "-d" ).append( failThrowable ).append( " " ); + args.append( "-o" ).append( instrumentDir ).append( File.separator ).append( "@p" ).append( File.separator ).append( "@f.@e " ); + args.append( "-k" ).append( repositoryDir ).append( File.separator ).append( "@p " ); + args.append( instrumentall ? "-a " : "" ); // reinstrument everything if controlFile exists and is newer than source + args.append( "@" ).append( targets.getName() ); + iContract.createArg().setLine( args.toString() ); + +// System.out.println( "JAVA -classpath " + iContractClasspath + " com.reliablesystems.iContract.Tool " + args.toString() ); + + int result = iContract.executeJava(); + if( result != 0 ) { + if( iContractMissing ) { + log( "iContract can't be found on your classpath. Your classpath is:" ); + log( classpath.toString() ); + log( "If you don't have the iContract jar, go get it at http://www.reliable-systems.com/tools/" ); + } + throw new BuildException( "iContract instrumentation failed. Code=" + result ); + } + } else { + //log( "Nothing to do. Everything up to date." ); + } + } + + /** + * Checks that the required attributes are set. + */ + private void preconditions() throws BuildException { + if (srcDir == null) { + throw new BuildException( "srcdir attribute must be set!", location ); + } + if (!srcDir.exists()) { + throw new BuildException( "srcdir \"" + srcDir.getPath() + "\" does not exist!", location ); + } + if (instrumentDir == null) { + throw new BuildException( "instrumentdir attribute must be set!", location ); + } + if (repositoryDir == null) { + throw new BuildException( "repositorydir attribute must be set!", location ); + } + } + + /** + * Verifies whether any of the source files have changed. Done by comparing date of source/class files. + * The whole lot is "dirty" if at least one source file is newer than the instrumented files. If not dirty, + * iContract will not be executed. + *
+ * Also creates a temporary file with a list of the source files, that will be deleted upon exit. + */ + private void scan() throws BuildException { + long now = (new Date()).getTime(); + + FileSet fileset = new FileSet(); + fileset.setDefaultexcludes( true ); + fileset.setDir( srcDir ); + DirectoryScanner ds = fileset.getDirectoryScanner( project ); + + String[] files = ds.getIncludedFiles(); + + try { + targets = File.createTempFile( "iContractTargets", "tmp", new File( System.getProperty( "user.dir" ) ) ); + targets.deleteOnExit(); + FileOutputStream fos = new FileOutputStream( targets ); + PrintStream ps = new PrintStream( fos ); + for (int i = 0; i < files.length; i++ ) { + File srcFile = new File(srcDir, files[i]); + if (files[i].endsWith(".java")) { + ps.println( srcFile.getAbsolutePath() ); + + File classFile = new File( instrumentDir, files[i].substring( 0, files[i].indexOf( ".java" ) ) + ".class" ); + + if (srcFile.lastModified() > now) { + log("Warning: file modified in the future: " + + files[i], Project.MSG_WARN); + } + + if (!classFile.exists() || srcFile.lastModified() > classFile.lastModified()) { + //log( "Found a file newer than the instrumentDir class file: " + srcFile.getPath() + " newer than " + classFile.getPath() + ". Running iContract again..." ); + dirty = true; + } + } + } + ps.flush(); + ps.close(); + } catch( IOException e ) { + throw new BuildException( "Could not create temporary file:" + e.getMessage() ); + } + + // also, check controlFile timestamp + long controlFileTime = -1; + if( controlFile != null ) { + if( controlFile.exists() ) { + controlFileTime = controlFile.lastModified(); + fileset.setDir( instrumentDir ); + ds = fileset.getDirectoryScanner( project ); + files = ds.getIncludedFiles(); + for( int i = 0; i < files.length; i++ ) { + File srcFile = new File(srcDir, files[i]); + if( files[i].endsWith( ".class" ) ) { + if( controlFileTime > srcFile.lastModified() ) { + if( !dirty ) { + log( "Control file " + controlFile.getAbsolutePath() + " has been updated. Instrumenting all files..." ); + } + dirty = true; + instrumentall = true; + } + } + } + } + } + } + + + /** + * Creates the -m option based on the values of controlFile, pre, post and invariant. + */ + private final String directiveString() { + StringBuffer sb = new StringBuffer(); + boolean comma = false; + if( (controlFile != null) || pre || post || invariant ) { + sb.append( "-m" ); + } + if(controlFile != null) { + sb.append( "@" ).append( controlFile ); + comma = true; + } + if( pre ) { + if( comma ) { + sb.append( "," ); + } + sb.append( "pre" ); + comma = true; + } + if( post ) { + if( comma ) { + sb.append( "," ); + } + sb.append( "post" ); + comma = true; + } + if( invariant ) { + if( comma ) { + sb.append( "," ); + } + sb.append( "inv" ); + comma = true; + } + sb.append( " " ); + return sb.toString(); + } + + /** + * Sets the compiler as specified by the project's build.compiler property + * If the internalcompilation attribute is set to true, Sun's javac + * will be run from the same VM as Ant. + * + * NOTE: This has not been tested, as I only have JDK. + */ + private void setCompiler() { + if( !internalcompilation ) { + String compiler = project.getProperty("build.compiler"); + if (compiler == null) { + if (Project.getJavaVersion().startsWith("1.3")) { + compiler = "modern"; + } else { + compiler = "classic"; + } + } + + if (compiler.equalsIgnoreCase("classic")) { + icCompiler = "javac"; + } else if (compiler.equalsIgnoreCase("modern")) { + icCompiler = "javac"; + } else if (compiler.equalsIgnoreCase("jikes")) { + icCompiler = "jikes"; + } else if (compiler.equalsIgnoreCase("jvc")) { + icCompiler = "jvc"; + } else { + String msg = "Don't know how to use compiler " + compiler; + throw new BuildException(msg, location); + } + } else { + // This is how we tell iContract to use internal compiler + // FIXME: Doesn't work +// icCompiler = ":"; + } + } + + /** + * BuildListener that sets the iContractMissing flag to true if a + * message about missing iContract is missing. Used to indicate + * a more verbose error to the user, with advice about how to solve + * the problem + */ + private class IContractPresenceDetector implements BuildListener { + public void buildFinished(BuildEvent event) {} + public void buildStarted(BuildEvent event) {} + public void messageLogged(BuildEvent event) { + if( "java.lang.NoClassDefFoundError: com/reliablesystems/iContract/Tool".equals( event.getMessage() ) ) { + iContractMissing = true; + } + } + public void targetFinished(BuildEvent event) {} + public void targetStarted(BuildEvent event) {} + public void taskFinished(BuildEvent event) {} + public void taskStarted(BuildEvent event) {} + } +}