diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/AntClassLoader.java b/proposal/myrmidon/src/main/org/apache/tools/ant/AntClassLoader.java new file mode 100644 index 000000000..d2ef10528 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/AntClassLoader.java @@ -0,0 +1,1088 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.tools.ant.types.Path; + +/** + * Used to load classes within ant with a different claspath from that used to + * start ant. Note that it is possible to force a class into this loader even + * when that class is on the system classpath by using the forceLoadClass + * method. Any subsequent classes loaded by that class will then use this loader + * rather than the system class loader. + * + * @author Conor MacNeill + * @author Jesse Glick + */ +public class AntClassLoader extends ClassLoader implements BuildListener +{ + + /** + * The size of buffers to be used in this classloader. + */ + private final static int BUFFER_SIZE = 8192; + + private static Method getProtectionDomain = null; + private static Method defineClassProtectionDomain = null; + private static Method getContextClassLoader = null; + private static Method setContextClassLoader = null; + + /** + * The components of the classpath that the classloader searches for classes + */ + Vector pathComponents = new Vector(); + + /** + * Indicates whether the parent class loader should be consulted before + * trying to load with this class loader. + */ + private boolean parentFirst = true; + + /** + * These are the package roots that are to be loaded by the parent class + * loader regardless of whether the parent class loader is being searched + * first or not. + */ + private Vector systemPackages = new Vector(); + + /** + * These are the package roots that are to be loaded by this class loader + * regardless of whether the parent class loader is being searched first or + * not. + */ + private Vector loaderPackages = new Vector(); + + /** + * This flag indicates that the classloader will ignore the base classloader + * if it can't find a class. + */ + private boolean ignoreBase = false; + + /** + * The parent class loader, if one is given or can be determined + */ + private ClassLoader parent = null; + + /** + * A hashtable of zip files opened by the classloader + */ + private Hashtable zipFiles = new Hashtable(); + + /** + * The context loader saved when setting the thread's current context + * loader. + */ + private ClassLoader savedContextLoader = null; + private boolean isContextLoaderSaved = false; + + /** + * The project to which this class loader belongs. + */ + private Project project; + static + { + try + { + getProtectionDomain = Class.class.getMethod( "getProtectionDomain", new Class[0] ); + Class protectionDomain = Class.forName( "java.security.ProtectionDomain" ); + Class[] args = new Class[]{String.class, byte[].class, Integer.TYPE, Integer.TYPE, protectionDomain}; + defineClassProtectionDomain = ClassLoader.class.getDeclaredMethod( "defineClass", args ); + + getContextClassLoader = Thread.class.getMethod( "getContextClassLoader", new Class[0] ); + args = new Class[]{ClassLoader.class}; + setContextClassLoader = Thread.class.getMethod( "setContextClassLoader", args ); + } + catch( Exception e ) + {} + } + + + /** + * Create a classloader for the given project using the classpath given. + * + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. This is + * combined with the system classpath in a manner determined by the + * value of ${build.sysclasspath} + */ + public AntClassLoader( Project project, Path classpath ) + { + parent = AntClassLoader.class.getClassLoader(); + this.project = project; + project.addBuildListener( this ); + if( classpath != null ) + { + Path actualClasspath = classpath.concatSystemClasspath( "ignore" ); + String[] pathElements = actualClasspath.list(); + for( int i = 0; i < pathElements.length; ++i ) + { + try + { + addPathElement( ( String )pathElements[i] ); + } + catch( BuildException e ) + { + // ignore path elements which are invalid relative to the project + } + } + } + } + + /** + * Create a classloader for the given project using the classpath given. + * + * @param parent the parent classloader to which unsatisfied loading + * attempts are delgated + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( ClassLoader parent, Project project, Path classpath, + boolean parentFirst ) + { + this( project, classpath ); + if( parent != null ) + { + this.parent = parent; + } + this.parentFirst = parentFirst; + addSystemPackageRoot( "java" ); + addSystemPackageRoot( "javax" ); + } + + + /** + * Create a classloader for the given project using the classpath given. + * + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( Project project, Path classpath, boolean parentFirst ) + { + this( null, project, classpath, parentFirst ); + } + + /** + * Create an empty class loader. The classloader should be configured with + * path elements to specify where the loader is to look for classes. + * + * @param parent the parent classloader to which unsatisfied loading + * attempts are delgated + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( ClassLoader parent, boolean parentFirst ) + { + if( parent != null ) + { + this.parent = parent; + } + else + { + parent = AntClassLoader.class.getClassLoader(); + } + project = null; + this.parentFirst = parentFirst; + } + + /** + * Force initialization of a class in a JDK 1.1 compatible, albeit hacky way + * + * @param theClass Description of Parameter + */ + public static void initializeClass( Class theClass ) + { + // ***HACK*** We try to create an instance to force the VM to run the + // class' static initializer. We don't care if the instance can't + // be created - we are just interested in the side effect. + try + { + theClass.newInstance(); + } + catch( Throwable t ) + { + //ignore - our work is done + } + } + + /** + * Set this classloader to run in isolated mode. In isolated mode, classes + * not found on the given classpath will not be referred to the base class + * loader but will cause a classNotFoundException. + * + * @param isolated The new Isolated value + */ + public void setIsolated( boolean isolated ) + { + ignoreBase = isolated; + } + + /** + * Set the current thread's context loader to this classloader, storing the + * current loader value for later resetting + */ + public void setThreadContextLoader() + { + if( isContextLoaderSaved ) + { + throw new BuildException( "Context loader has not been reset" ); + } + if( getContextClassLoader != null && setContextClassLoader != null ) + { + try + { + savedContextLoader + = ( ClassLoader )getContextClassLoader.invoke( Thread.currentThread(), new Object[0] ); + Object[] args = new Object[]{this}; + setContextClassLoader.invoke( Thread.currentThread(), args ); + isContextLoaderSaved = true; + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t.toString() ); + } + catch( Exception e ) + { + throw new BuildException( e.toString() ); + } + } + } + + /** + * Finds the resource with the given name. A resource is some data (images, + * audio, text, etc) that can be accessed by class code in a way that is + * independent of the location of the code. + * + * @param name the name of the resource for which a stream is required. + * @return a URL for reading the resource, or null if the resource could not + * be found or the caller doesn't have adequate privileges to get the + * resource. + */ + public URL getResource( String name ) + { + // we need to search the components of the path to see if we can find the + // class we want. + URL url = null; + if( isParentFirst( name ) ) + { + url = ( parent == null ) ? super.getResource( name ) : parent.getResource( name ); + } + + if( url != null ) + { + log( "Resource " + name + " loaded from parent loader", + Project.MSG_DEBUG ); + + } + else + { + // try and load from this loader if the parent either didn't find + // it or wasn't consulted. + for( Enumeration e = pathComponents.elements(); e.hasMoreElements() && url == null; ) + { + File pathComponent = ( File )e.nextElement(); + url = getResourceURL( pathComponent, name ); + if( url != null ) + { + log( "Resource " + name + + " loaded from ant loader", + Project.MSG_DEBUG ); + } + } + } + + if( url == null && !isParentFirst( name ) ) + { + // this loader was first but it didn't find it - try the parent + + url = ( parent == null ) ? super.getResource( name ) : parent.getResource( name ); + if( url != null ) + { + log( "Resource " + name + " loaded from parent loader", + Project.MSG_DEBUG ); + } + } + + if( url == null ) + { + log( "Couldn't load Resource " + name, Project.MSG_DEBUG ); + } + + return url; + } + + /** + * Get a stream to read the requested resource name. + * + * @param name the name of the resource for which a stream is required. + * @return a stream to the required resource or null if the resource cannot + * be found on the loader's classpath. + */ + public InputStream getResourceAsStream( String name ) + { + + InputStream resourceStream = null; + if( isParentFirst( name ) ) + { + resourceStream = loadBaseResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from parent loader", Project.MSG_DEBUG ); + + } + else + { + resourceStream = loadResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from ant loader", Project.MSG_DEBUG ); + } + } + } + else + { + resourceStream = loadResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from ant loader", Project.MSG_DEBUG ); + + } + else + { + resourceStream = loadBaseResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from parent loader", Project.MSG_DEBUG ); + } + } + } + + if( resourceStream == null ) + { + log( "Couldn't load ResourceStream for " + name, + Project.MSG_DEBUG ); + } + + return resourceStream; + } + + /** + * Add a package root to the list of packages which must be loaded using + * this loader. All subpackages are also included. + * + * @param packageRoot the root of akll packages to be included. + */ + public void addLoaderPackageRoot( String packageRoot ) + { + loaderPackages.addElement( packageRoot + "." ); + } + + + /** + * Add an element to the classpath to be searched + * + * @param pathElement The feature to be added to the PathElement attribute + * @exception BuildException Description of Exception + */ + public void addPathElement( String pathElement ) + throws BuildException + { + File pathComponent + = project != null ? project.resolveFile( pathElement ) + : new File( pathElement ); + pathComponents.addElement( pathComponent ); + } + + /** + * Add a package root to the list of packages which must be loaded on the + * parent loader. All subpackages are also included. + * + * @param packageRoot the root of all packages to be included. + */ + public void addSystemPackageRoot( String packageRoot ) + { + systemPackages.addElement( packageRoot + "." ); + } + + public void buildFinished( BuildEvent event ) + { + cleanup(); + } + + public void buildStarted( BuildEvent event ) { } + + public void cleanup() + { + pathComponents = null; + project = null; + for( Enumeration e = zipFiles.elements(); e.hasMoreElements(); ) + { + ZipFile zipFile = ( ZipFile )e.nextElement(); + try + { + zipFile.close(); + } + catch( IOException ioe ) + { + // ignore + } + } + zipFiles = new Hashtable(); + } + + /** + * Search for and load a class on the classpath of this class loader. + * + * @param name the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class findClass( String name ) + throws ClassNotFoundException + { + log( "Finding class " + name, Project.MSG_DEBUG ); + + return findClassInComponents( name ); + } + + + /** + * Load a class through this class loader even if that class is available on + * the parent classpath. This ensures that any classes which are loaded by + * the returned class will use this classloader. + * + * @param classname the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class forceLoadClass( String classname ) + throws ClassNotFoundException + { + log( "force loading " + classname, Project.MSG_DEBUG ); + + Class theClass = findLoadedClass( classname ); + + if( theClass == null ) + { + theClass = findClass( classname ); + } + + return theClass; + } + + /** + * Load a class through this class loader but defer to the parent class + * loader This ensures that instances of the returned class will be + * compatible with instances which which have already been loaded on the + * parent loader. + * + * @param classname the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class forceLoadSystemClass( String classname ) + throws ClassNotFoundException + { + log( "force system loading " + classname, Project.MSG_DEBUG ); + + Class theClass = findLoadedClass( classname ); + + if( theClass == null ) + { + theClass = findBaseClass( classname ); + } + + return theClass; + } + + public void messageLogged( BuildEvent event ) { } + + /** + * Reset the current thread's context loader to its original value + */ + public void resetThreadContextLoader() + { + if( isContextLoaderSaved && + getContextClassLoader != null && setContextClassLoader != null ) + { + try + { + Object[] args = new Object[]{savedContextLoader}; + setContextClassLoader.invoke( Thread.currentThread(), args ); + savedContextLoader = null; + isContextLoaderSaved = false; + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t.toString() ); + } + catch( Exception e ) + { + throw new BuildException( e.toString() ); + } + } + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) { } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + /** + * Returns an enumeration of URLs representing all the resources with the + * given name by searching the class loader's classpath. + * + * @param name the resource name. + * @return an enumeration of URLs for the resources. + * @throws IOException if I/O errors occurs (can't happen) + */ + protected Enumeration findResources( String name ) + throws IOException + { + return new ResourceEnumeration( name ); + } + + + /** + * Load a class with this class loader. This method will load a class. This + * class attempts to load the class firstly using the parent class loader. + * For JDK 1.1 compatability, this uses the findSystemClass method. + * + * @param classname the name of the class to be loaded. + * @param resolve true if all classes upon which this class depends are to + * be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * the system classpath or this loader's classpath. + */ + protected Class loadClass( String classname, boolean resolve ) + throws ClassNotFoundException + { + + Class theClass = findLoadedClass( classname ); + if( theClass != null ) + { + return theClass; + } + + if( isParentFirst( classname ) ) + { + try + { + theClass = findBaseClass( classname ); + log( "Class " + classname + " loaded from parent loader", Project.MSG_DEBUG ); + } + catch( ClassNotFoundException cnfe ) + { + theClass = findClass( classname ); + log( "Class " + classname + " loaded from ant loader", Project.MSG_DEBUG ); + } + } + else + { + try + { + theClass = findClass( classname ); + log( "Class " + classname + " loaded from ant loader", Project.MSG_DEBUG ); + } + catch( ClassNotFoundException cnfe ) + { + if( ignoreBase ) + { + throw cnfe; + } + theClass = findBaseClass( classname ); + log( "Class " + classname + " loaded from parent loader", Project.MSG_DEBUG ); + } + } + + if( resolve ) + { + resolveClass( theClass ); + } + + return theClass; + } + + /** + * Log a message through the project object if one has been provided. + * + * @param message the message to log + * @param priority the logging priority of the message + */ + protected void log( String message, int priority ) + { + if( project != null ) + { + project.log( message, priority ); + } +// else { +// System.out.println(message); +// } + } + + /** + * Convert the class dot notation to a filesystem equivalent for searching + * purposes. + * + * @param classname the class name in dot format (ie java.lang.Integer) + * @return the classname in filesystem format (ie java/lang/Integer.class) + */ + private String getClassFilename( String classname ) + { + return classname.replace( '.', '/' ) + ".class"; + } + + /** + * Read a class definition from a stream. + * + * @param stream the stream from which the class is to be read. + * @param classname the class name of the class in the stream. + * @return the Class object read from the stream. + * @throws IOException if there is a problem reading the class from the + * stream. + */ + private Class getClassFromStream( InputStream stream, String classname ) + throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int bytesRead = -1; + byte[] buffer = new byte[BUFFER_SIZE]; + + while( ( bytesRead = stream.read( buffer, 0, BUFFER_SIZE ) ) != -1 ) + { + baos.write( buffer, 0, bytesRead ); + } + + byte[] classData = baos.toByteArray(); + + // Simply put: + // defineClass(classname, classData, 0, classData.length, Project.class.getProtectionDomain()); + // Made more elaborate to be 1.1-safe. + if( defineClassProtectionDomain != null ) + { + try + { + Object domain = getProtectionDomain.invoke( Project.class, new Object[0] ); + Object[] args = new Object[]{classname, classData, new Integer( 0 ), new Integer( classData.length ), domain}; + return ( Class )defineClassProtectionDomain.invoke( this, args ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof ClassFormatError ) + { + throw ( ClassFormatError )t; + } + else if( t instanceof NoClassDefFoundError ) + { + throw ( NoClassDefFoundError )t; + } + else + { + throw new IOException( t.toString() ); + } + } + catch( Exception e ) + { + throw new IOException( e.toString() ); + } + } + else + { + return defineClass( classname, classData, 0, classData.length ); + } + } + + /** + * Get an inputstream to a given resource in the given file which may either + * be a directory or a zip file. + * + * @param file the file (directory or jar) in which to search for the + * resource. + * @param resourceName the name of the resource for which a stream is + * required. + * @return a stream to the required resource or null if the resource cannot + * be found in the given file object + */ + private InputStream getResourceStream( File file, String resourceName ) + { + try + { + if( !file.exists() ) + { + return null; + } + + if( file.isDirectory() ) + { + File resource = new File( file, resourceName ); + + if( resource.exists() ) + { + return new FileInputStream( resource ); + } + } + else + { + // is the zip file in the cache + ZipFile zipFile = ( ZipFile )zipFiles.get( file ); + if( zipFile == null ) + { + zipFile = new ZipFile( file ); + zipFiles.put( file, zipFile ); + } + ZipEntry entry = zipFile.getEntry( resourceName ); + if( entry != null ) + { + return zipFile.getInputStream( entry ); + } + } + } + catch( Exception e ) + { + log( "Ignoring Exception " + e.getClass().getName() + ": " + e.getMessage() + + " reading resource " + resourceName + " from " + file, Project.MSG_VERBOSE ); + } + + return null; + } + + /** + * Get an inputstream to a given resource in the given file which may either + * be a directory or a zip file. + * + * @param file the file (directory or jar) in which to search for the + * resource. + * @param resourceName the name of the resource for which a stream is + * required. + * @return a stream to the required resource or null if the resource cannot + * be found in the given file object + */ + private URL getResourceURL( File file, String resourceName ) + { + try + { + if( !file.exists() ) + { + return null; + } + + if( file.isDirectory() ) + { + File resource = new File( file, resourceName ); + + if( resource.exists() ) + { + try + { + return new URL( "file:" + resource.toString() ); + } + catch( MalformedURLException ex ) + { + return null; + } + } + } + else + { + ZipFile zipFile = ( ZipFile )zipFiles.get( file ); + if( zipFile == null ) + { + zipFile = new ZipFile( file ); + zipFiles.put( file, zipFile ); + } + + ZipEntry entry = zipFile.getEntry( resourceName ); + if( entry != null ) + { + try + { + return new URL( "jar:file:" + file.toString() + "!/" + entry ); + } + catch( MalformedURLException ex ) + { + return null; + } + } + } + } + catch( Exception e ) + { + e.printStackTrace(); + } + + return null; + } + + private boolean isParentFirst( String resourceName ) + { + // default to the global setting and then see + // if this class belongs to a package which has been + // designated to use a specific loader first (this one or the parent one) + boolean useParentFirst = parentFirst; + + for( Enumeration e = systemPackages.elements(); e.hasMoreElements(); ) + { + String packageName = ( String )e.nextElement(); + if( resourceName.startsWith( packageName ) ) + { + useParentFirst = true; + break; + } + } + + for( Enumeration e = loaderPackages.elements(); e.hasMoreElements(); ) + { + String packageName = ( String )e.nextElement(); + if( resourceName.startsWith( packageName ) ) + { + useParentFirst = false; + break; + } + } + + return useParentFirst; + } + + /** + * Find a system class (which should be loaded from the same classloader as + * the Ant core). + * + * @param name Description of Parameter + * @return Description of the Returned Value + * @exception ClassNotFoundException Description of Exception + */ + private Class findBaseClass( String name ) + throws ClassNotFoundException + { + if( parent == null ) + { + return findSystemClass( name ); + } + else + { + return parent.loadClass( name ); + } + } + + + /** + * Find a class on the given classpath. + * + * @param name Description of Parameter + * @return Description of the Returned Value + * @exception ClassNotFoundException Description of Exception + */ + private Class findClassInComponents( String name ) + throws ClassNotFoundException + { + // we need to search the components of the path to see if we can find the + // class we want. + InputStream stream = null; + String classFilename = getClassFilename( name ); + try + { + for( Enumeration e = pathComponents.elements(); e.hasMoreElements(); ) + { + File pathComponent = ( File )e.nextElement(); + try + { + stream = getResourceStream( pathComponent, classFilename ); + if( stream != null ) + { + return getClassFromStream( stream, name ); + } + } + catch( IOException ioe ) + { + // ioe.printStackTrace(); + log( "Exception reading component " + pathComponent, Project.MSG_VERBOSE ); + } + } + + throw new ClassNotFoundException( name ); + } + finally + { + try + { + if( stream != null ) + { + stream.close(); + } + } + catch( IOException e ) + {} + } + } + + /** + * Find a system resource (which should be loaded from the parent + * classloader). + * + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private InputStream loadBaseResource( String name ) + { + if( parent == null ) + { + return getSystemResourceAsStream( name ); + } + else + { + return parent.getResourceAsStream( name ); + } + } + + + /** + * Get a stream to read the requested resource name from this loader. + * + * @param name the name of the resource for which a stream is required. + * @return a stream to the required resource or null if the resource cannot + * be found on the loader's classpath. + */ + private InputStream loadResource( String name ) + { + // we need to search the components of the path to see if we can find the + // class we want. + InputStream stream = null; + + for( Enumeration e = pathComponents.elements(); e.hasMoreElements() && stream == null; ) + { + File pathComponent = ( File )e.nextElement(); + stream = getResourceStream( pathComponent, name ); + } + return stream; + } + + /** + * An enumeration of all resources of a given name found within the + * classpath of this class loader. This enumeration is used by the {@link + * #findResources(String) findResources} method, which is in turn used by + * the {@link ClassLoader#getResources ClassLoader.getResources} method. + * + * @author David A. Herman + * @see AntClassLoader#findResources(String) + * @see java.lang.ClassLoader#getResources(String) + */ + private class ResourceEnumeration implements Enumeration + { + + /** + * The URL of the next resource to return in the enumeration. If this + * field is null then the enumeration has been completed, + * i.e., there are no more elements to return. + */ + private URL nextResource; + + /** + * The index of the next classpath element to search. + */ + private int pathElementsIndex; + + /** + * The name of the resource being searched for. + */ + private String resourceName; + + /** + * Construct a new enumeration of resources of the given name found + * within this class loader's classpath. + * + * @param name the name of the resource to search for. + */ + ResourceEnumeration( String name ) + { + this.resourceName = name; + this.pathElementsIndex = 0; + findNextResource(); + } + + /** + * Indicates whether there are more elements in the enumeration to + * return. + * + * @return true if there are more elements in the + * enumeration; false otherwise. + */ + public boolean hasMoreElements() + { + return ( this.nextResource != null ); + } + + /** + * Returns the next resource in the enumeration. + * + * @return the next resource in the enumeration. + */ + public Object nextElement() + { + URL ret = this.nextResource; + findNextResource(); + return ret; + } + + /** + * Locates the next resource of the correct name in the classpath and + * sets nextResource to the URL of that resource. If no + * more resources can be found, nextResource is set to + * null. + */ + private void findNextResource() + { + URL url = null; + while( ( pathElementsIndex < pathComponents.size() ) && + ( url == null ) ) + { + try + { + File pathComponent + = ( File )pathComponents.elementAt( pathElementsIndex ); + url = getResourceURL( pathComponent, this.resourceName ); + pathElementsIndex++; + } + catch( BuildException e ) + { + // ignore path elements which are not valid relative to the project + } + } + this.nextResource = url; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/BuildEvent.java b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildEvent.java new file mode 100644 index 000000000..feef6ff96 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildEvent.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.EventObject; + +public class BuildEvent extends EventObject +{ + private int priority = Project.MSG_VERBOSE; + private Throwable exception; + private String message; + private Project project; + private Target target; + private Task task; + + /** + * Construct a BuildEvent for a project level event + * + * @param project the project that emitted the event. + */ + public BuildEvent( Project project ) + { + super( project ); + this.project = project; + this.target = null; + this.task = null; + } + + /** + * Construct a BuildEvent for a target level event + * + * @param target the target that emitted the event. + */ + public BuildEvent( Target target ) + { + super( target ); + this.project = target.getProject(); + this.target = target; + this.task = null; + } + + /** + * Construct a BuildEvent for a task level event + * + * @param task the task that emitted the event. + */ + public BuildEvent( Task task ) + { + super( task ); + this.project = task.getProject(); + this.target = task.getOwningTarget(); + this.task = task; + } + + public void setException( Throwable exception ) + { + this.exception = exception; + } + + public void setMessage( String message, int priority ) + { + this.message = message; + this.priority = priority; + } + + /** + * Returns the exception that was thrown, if any. This field will only be + * set for "taskFinished", "targetFinished", and "buildFinished" events. + * + * @return The Exception value + * @see BuildListener#taskFinished(BuildEvent) + * @see BuildListener#targetFinished(BuildEvent) + * @see BuildListener#buildFinished(BuildEvent) + */ + public Throwable getException() + { + return exception; + } + + /** + * Returns the logging message. This field will only be set for + * "messageLogged" events. + * + * @return The Message value + * @see BuildListener#messageLogged(BuildEvent) + */ + public String getMessage() + { + return message; + } + + /** + * Returns the priority of the logging message. This field will only be set + * for "messageLogged" events. + * + * @return The Priority value + * @see BuildListener#messageLogged(BuildEvent) + */ + public int getPriority() + { + return priority; + } + + /** + * Returns the project that fired this event. + * + * @return The Project value + */ + public Project getProject() + { + return project; + } + + /** + * Returns the target that fired this event. + * + * @return The Target value + */ + public Target getTarget() + { + + return target; + } + + /** + * Returns the task that fired this event. + * + * @return The Task value + */ + public Task getTask() + { + return task; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/BuildException.java b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildException.java new file mode 100644 index 000000000..a22ef8933 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildException.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.TaskException; + +/** + * Signals an error condition during a build. + * + * @author James Duncan Davidson + */ +public class BuildException + extends TaskException +{ + /** + * Location in the build file where the exception occured + */ + private Location location = Location.UNKNOWN_LOCATION; + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of or information about the exception. + */ + public BuildException( String msg ) + { + super( msg ); + } + + /** + * Constructs an exception with the given message and exception as a root + * cause. + * + * @param msg Description of or information about the exception. + * @param cause Throwable that might have cause this one. + */ + public BuildException( String msg, Throwable cause ) + { + super( msg, cause ); + } + + /** + * Constructs an exception with the given message and exception as a root + * cause and a location in a file. + * + * @param msg Description of or information about the exception. + * @param cause Exception that might have cause this one. + * @param location Location in the project file where the error occured. + */ + public BuildException( String msg, Throwable cause, Location location ) + { + this( msg, cause ); + this.location = location; + } + + /** + * Constructs an exception with the given exception as a root cause. + * + * @param cause Exception that might have caused this one. + */ + public BuildException( Throwable cause ) + { + super( cause.toString(), cause ); + } + + /** + * Constructs an exception with the given descriptive message and a location + * in a file. + * + * @param msg Description of or information about the exception. + * @param location Location in the project file where the error occured. + */ + public BuildException( String msg, Location location ) + { + super( msg ); + this.location = location; + } + + /** + * Sets the file location where the error occured. + * + * @param location The new Location value + */ + public void setLocation( Location location ) + { + this.location = location; + } + + /** + * Returns the file location where the error occured. + * + * @return The Location value + */ + public Location getLocation() + { + return location; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/BuildListener.java b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildListener.java new file mode 100644 index 000000000..f6055badb --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildListener.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.EventListener; + +/** + * Classes that implement this interface will be notified when things happend + * during a build. + * + * @author RT + * @see BuildEvent + * @see Project#addBuildListener(BuildListener) + */ +public interface BuildListener extends EventListener +{ + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + void buildStarted( BuildEvent event ); + + /** + * Fired after the last target has finished. This event will still be thrown + * if an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void buildFinished( BuildEvent event ); + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTarget() + */ + void targetStarted( BuildEvent event ); + + /** + * Fired when a target has finished. This event will still be thrown if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void targetFinished( BuildEvent event ); + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTask() + */ + void taskStarted( BuildEvent event ); + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void taskFinished( BuildEvent event ); + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + * @see BuildEvent#getMessage() + * @see BuildEvent#getPriority() + */ + void messageLogged( BuildEvent event ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/BuildLogger.java b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildLogger.java new file mode 100644 index 000000000..9e3879d14 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildLogger.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.PrintStream; + +/** + * Interface used by Ant to log the build output. A build logger is a build + * listener which has the 'right' to send output to the ant log, which is + * usually System.out unles redirected by the -logfile option. + * + * @author Conor MacNeill + */ +public interface BuildLogger extends BuildListener +{ + /** + * Set the msgOutputLevel this logger is to respond to. Only messages with a + * message level lower than or equal to the given level are output to the + * log.

+ * + * Constants for the message levels are in Project.java. The order of the + * levels, from least to most verbose, is MSG_ERR, MSG_WARN, MSG_INFO, + * MSG_VERBOSE, MSG_DEBUG. + * + * @param level the logging level for the logger. + */ + void setMessageOutputLevel( int level ); + + /** + * Set the output stream to which this logger is to send its output. + * + * @param output the output stream for the logger. + */ + void setOutputPrintStream( PrintStream output ); + + /** + * Set this logger to produce emacs (and other editor) friendly output. + * + * @param emacsMode true if output is to be unadorned so that emacs and + * other editors can parse files names, etc. + */ + void setEmacsMode( boolean emacsMode ); + + /** + * Set the output stream to which this logger is to send error messages. + * + * @param err the error stream for the logger. + */ + void setErrorPrintStream( PrintStream err ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Constants.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Constants.java new file mode 100644 index 000000000..7f26d5f4e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Constants.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Abstract interface to hold constants. + * + * @author Peter Donald + */ +interface Constants +{ +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/DefaultLogger.java b/proposal/myrmidon/src/main/org/apache/tools/ant/DefaultLogger.java new file mode 100644 index 000000000..eb578b54e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/DefaultLogger.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import org.apache.tools.ant.util.StringUtils; + +/** + * Writes build event to a PrintStream. Currently, it only writes which targets + * are being executed, and any messages that get logged. + * + * @author RT + */ +public class DefaultLogger implements BuildLogger +{ + private static int LEFT_COLUMN_SIZE = 12; + protected int msgOutputLevel = Project.MSG_ERR; + private long startTime = System.currentTimeMillis(); + + protected boolean emacsMode = false; + protected PrintStream err; + + protected PrintStream out; + + protected static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + + } + + /** + * Set this logger to produce emacs (and other editor) friendly output. + * + * @param emacsMode true if output is to be unadorned so that emacs and + * other editors can parse files names, etc. + */ + public void setEmacsMode( boolean emacsMode ) + { + this.emacsMode = emacsMode; + } + + /** + * Set the output stream to which this logger is to send error messages. + * + * @param err the error stream for the logger. + */ + public void setErrorPrintStream( PrintStream err ) + { + this.err = new PrintStream( err, true ); + } + + /** + * Set the msgOutputLevel this logger is to respond to. Only messages with a + * message level lower than or equal to the given level are output to the + * log.

+ * + * Constants for the message levels are in Project.java. The order of the + * levels, from least to most verbose, is MSG_ERR, MSG_WARN, MSG_INFO, + * MSG_VERBOSE, MSG_DEBUG. The default message level for DefaultLogger is + * Project.MSG_ERR. + * + * @param level the logging level for the logger. + */ + public void setMessageOutputLevel( int level ) + { + this.msgOutputLevel = level; + } + + /** + * Set the output stream to which this logger is to send its output. + * + * @param output the output stream for the logger. + */ + public void setOutputPrintStream( PrintStream output ) + { + this.out = new PrintStream( output, true ); + } + + /** + * Prints whether the build succeeded or failed, and any errors the occured + * during the build. + * + * @param event Description of Parameter + */ + public void buildFinished( BuildEvent event ) + { + Throwable error = event.getException(); + StringBuffer message = new StringBuffer(); + + if( error == null ) + { + message.append( StringUtils.LINE_SEP ); + message.append( "BUILD SUCCESSFUL" ); + } + else + { + message.append( StringUtils.LINE_SEP ); + message.append( "BUILD FAILED" ); + message.append( StringUtils.LINE_SEP ); + + if( Project.MSG_VERBOSE <= msgOutputLevel || + !( error instanceof BuildException ) ) + { + message.append( StringUtils.getStackTrace( error ) ); + } + else + { + if( error instanceof BuildException ) + { + message.append( error.toString() ).append( StringUtils.LINE_SEP ); + } + else + { + message.append( error.getMessage() ).append( StringUtils.LINE_SEP ); + } + } + } + message.append( StringUtils.LINE_SEP ); + message.append( "Total time: " + + formatTime( System.currentTimeMillis() - startTime ) ); + + String msg = message.toString(); + if( error == null ) + { + out.println( msg ); + } + else + { + err.println( msg ); + } + log( msg ); + } + + public void buildStarted( BuildEvent event ) + { + startTime = System.currentTimeMillis(); + } + + public void messageLogged( BuildEvent event ) + { + // Filter out messages based on priority + if( event.getPriority() <= msgOutputLevel ) + { + + StringBuffer message = new StringBuffer(); + // Print out the name of the task if we're in one + if( event.getTask() != null ) + { + String name = event.getTask().getTaskName(); + + if( !emacsMode ) + { + String label = "[" + name + "] "; + for( int i = 0; i < ( LEFT_COLUMN_SIZE - label.length() ); i++ ) + { + message.append( " " ); + } + message.append( label ); + } + } + + message.append( event.getMessage() ); + String msg = message.toString(); + if( event.getPriority() != Project.MSG_ERR ) + { + out.println( msg ); + } + else + { + err.println( msg ); + } + log( msg ); + } + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) + { + if( Project.MSG_INFO <= msgOutputLevel ) + { + String msg = StringUtils.LINE_SEP + event.getTarget().getName() + ":"; + out.println( msg ); + log( msg ); + } + } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + /** + * Empty implementation which allows subclasses to receive the same output + * that is generated here. + * + * @param message Description of Parameter + */ + protected void log( String message ) { } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/DemuxOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/ant/DemuxOutputStream.java new file mode 100644 index 000000000..ef0e5472f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/DemuxOutputStream.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Hashtable; + + +/** + * Logs content written by a thread and forwards the buffers onto the project + * object which will forward the content to the appropriate task + * + * @author Conor MacNeill + */ +public class DemuxOutputStream extends OutputStream +{ + + private final static int MAX_SIZE = 1024; + + private Hashtable buffers = new Hashtable(); +// private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private boolean skip = false; + private boolean isErrorStream; + private Project project; + + /** + * Creates a new instance of this class. + * + * @param project Description of Parameter + * @param isErrorStream Description of Parameter + */ + public DemuxOutputStream( Project project, boolean isErrorStream ) + { + this.project = project; + this.isErrorStream = isErrorStream; + } + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + flush(); + } + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void flush() + throws IOException + { + if( getBuffer().size() > 0 ) + { + processBuffer(); + } + } + + /** + * Write the data to the buffer and flush the buffer, if a line separator is + * detected. + * + * @param cc data to log (byte). + * @exception IOException Description of Exception + */ + public void write( int cc ) + throws IOException + { + final byte c = ( byte )cc; + if( ( c == '\n' ) || ( c == '\r' ) ) + { + if( !skip ) + { + processBuffer(); + } + } + else + { + ByteArrayOutputStream buffer = getBuffer(); + buffer.write( cc ); + if( buffer.size() > MAX_SIZE ) + { + processBuffer(); + } + } + skip = ( c == '\r' ); + } + + + /** + * Converts the buffer to a string and sends it to processLine + */ + protected void processBuffer() + { + String output = getBuffer().toString(); + project.demuxOutput( output, isErrorStream ); + resetBuffer(); + } + + private ByteArrayOutputStream getBuffer() + { + Thread current = Thread.currentThread(); + ByteArrayOutputStream buffer = ( ByteArrayOutputStream )buffers.get( current ); + if( buffer == null ) + { + buffer = new ByteArrayOutputStream(); + buffers.put( current, buffer ); + } + return buffer; + } + + private void resetBuffer() + { + Thread current = Thread.currentThread(); + buffers.remove( current ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/DesirableFilter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/DesirableFilter.java new file mode 100644 index 000000000..a53315fa2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/DesirableFilter.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FilenameFilter; + + +/** + * Filters filenames to determine whether or not the file is desirable. + * + * @author Jason Hunter [jhunter@servlets.com] + * @author james@x180.com + */ +public class DesirableFilter implements FilenameFilter +{ + + /** + * Test the given filename to determine whether or not it's desirable. This + * helps tasks filter temp files and files used by CVS. + * + * @param dir Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + + public boolean accept( File dir, String name ) + { + + // emacs save file + if( name.endsWith( "~" ) ) + { + return false; + } + + // emacs autosave file + if( name.startsWith( "#" ) && name.endsWith( "#" ) ) + { + return false; + } + + // openwindows text editor does this I think + if( name.startsWith( "%" ) && name.endsWith( "%" ) ) + { + return false; + } + + /* + * CVS stuff -- hopefully there won't be a case with + * an all cap file/dir named "CVS" that somebody wants + * to keep around... + */ + if( name.equals( "CVS" ) ) + { + return false; + } + + /* + * If we are going to ignore CVS might as well ignore + * this one as well... + */ + if( name.equals( ".cvsignore" ) ) + { + return false; + } + + // CVS merge autosaves. + if( name.startsWith( ".#" ) ) + { + return false; + } + + // SCCS/CSSC/TeamWare: + if( name.equals( "SCCS" ) ) + { + return false; + } + + // Visual Source Save + if( name.equals( "vssver.scc" ) ) + { + return false; + } + + // default + return true; + } +} + + + + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/DirectoryScanner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/DirectoryScanner.java new file mode 100644 index 000000000..7d95dd304 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/DirectoryScanner.java @@ -0,0 +1,1177 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * Class for scanning a directory for files/directories that match a certain + * criteria.

+ * + * These criteria consist of a set of include and exclude patterns. With these + * patterns, you can select which files you want to have included, and which + * files you want to have excluded.

+ * + * The idea is simple. A given directory is recursively scanned for all files + * and directories. Each file/directory is matched against a set of include and + * exclude patterns. Only files/directories that match at least one pattern of + * the include pattern list, and don't match a pattern of the exclude pattern + * list will be placed in the list of files/directories found.

+ * + * When no list of include patterns is supplied, "**" will be used, which means + * that everything will be matched. When no list of exclude patterns is + * supplied, an empty list is used, such that nothing will be excluded.

+ * + * The pattern matching is done as follows: The name to be matched is split up + * in path segments. A path segment is the name of a directory or file, which is + * bounded by File.separator ('/' under UNIX, '\' under Windows). + * E.g. "abc/def/ghi/xyz.java" is split up in the segments "abc", "def", "ghi" + * and "xyz.java". The same is done for the pattern against which should be + * matched.

+ * + * Then the segments of the name and the pattern will be matched against each + * other. When '**' is used for a path segment in the pattern, then it matches + * zero or more path segments of the name.

+ * + * There are special case regarding the use of File.separators at + * the beginningof the pattern and the string to match:
+ * When a pattern starts with a File.separator, the string to match + * must also start with a File.separator. When a pattern does not + * start with a File.separator, the string to match may not start + * with a File.separator. When one of these rules is not obeyed, + * the string will not match.

+ * + * When a name path segment is matched against a pattern path segment, the + * following special characters can be used: '*' matches zero or more + * characters, '?' matches one character.

+ * + * Examples:

+ * + * "**\*.class" matches all .class files/dirs in a directory tree.

+ * + * "test\a??.java" matches all files/dirs which start with an 'a', then two more + * characters and then ".java", in a directory called test.

+ * + * "**" matches everything in a directory tree.

+ * + * "**\test\**\XYZ*" matches all files/dirs that start with "XYZ" and where + * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").

+ * + * Case sensitivity may be turned off if necessary. By default, it is turned on. + *

+ * + * Example of usage:

+ *   String[] includes = {"**\\*.class"};
+ *   String[] excludes = {"modules\\*\\**"};
+ *   ds.setIncludes(includes);
+ *   ds.setExcludes(excludes);
+ *   ds.setBasedir(new File("test"));
+ *   ds.setCaseSensitive(true);
+ *   ds.scan();
+ *
+ *   System.out.println("FILES:");
+ *   String[] files = ds.getIncludedFiles();
+ *   for (int i = 0; i < files.length;i++) {
+ *     System.out.println(files[i]);
+ *   }
+ * 
This will scan a directory called test for .class files, but excludes + * all .class files in all directories under a directory called "modules" + * + * @author Arnout J. Kuiper ajkuiper@wxs.nl + * @author Magesh Umasankar + */ +public class DirectoryScanner implements FileScanner +{ + + /** + * Patterns that should be excluded by default. + * + * @see #addDefaultExcludes() + */ + protected final static String[] DEFAULTEXCLUDES = { + "**/*~", + "**/#*#", + "**/.#*", + "**/%*%", + "**/CVS", + "**/CVS/**", + "**/.cvsignore", + "**/SCCS", + "**/SCCS/**", + "**/vssver.scc" + }; + + /** + * Have the Vectors holding our results been built by a slow scan? + */ + protected boolean haveSlowResults = false; + + /** + * Should the file system be treated as a case sensitive one? + */ + protected boolean isCaseSensitive = true; + + /** + * Is everything we've seen so far included? + */ + protected boolean everythingIncluded = true; + + /** + * The base directory which should be scanned. + */ + protected File basedir; + + /** + * The files that where found and matched at least one includes, and also + * matched at least one excludes. + */ + protected Vector dirsExcluded; + + /** + * The directories that where found and matched at least one includes, and + * matched no excludes. + */ + protected Vector dirsIncluded; + + /** + * The directories that where found and did not match any includes. + */ + protected Vector dirsNotIncluded; + + /** + * The patterns for the files that should be excluded. + */ + protected String[] excludes; + + /** + * The files that where found and matched at least one includes, and also + * matched at least one excludes. + */ + protected Vector filesExcluded; + + /** + * The files that where found and matched at least one includes, and matched + * no excludes. + */ + protected Vector filesIncluded; + + /** + * The files that where found and did not match any includes. + */ + protected Vector filesNotIncluded; + + /** + * The patterns for the files that should be included. + */ + protected String[] includes; + + /** + * Constructor. + */ + public DirectoryScanner() { } + + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @return true when the string matches against the pattern, + * false otherwise. + */ + public static boolean match( String pattern, String str ) + { + return match( pattern, str, true ); + } + + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @param isCaseSensitive Description of Parameter + * @return true when the string matches against the pattern, + * false otherwise. + */ + protected static boolean match( String pattern, String str, boolean isCaseSensitive ) + { + char[] patArr = pattern.toCharArray(); + char[] strArr = str.toCharArray(); + int patIdxStart = 0; + int patIdxEnd = patArr.length - 1; + int strIdxStart = 0; + int strIdxEnd = strArr.length - 1; + char ch; + + boolean containsStar = false; + for( int i = 0; i < patArr.length; i++ ) + { + if( patArr[i] == '*' ) + { + containsStar = true; + break; + } + } + + if( !containsStar ) + { + // No '*'s, so we make a shortcut + if( patIdxEnd != strIdxEnd ) + { + return false;// Pattern and string do not have the same size + } + for( int i = 0; i <= patIdxEnd; i++ ) + { + ch = patArr[i]; + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[i] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[i] ) ) + { + return false;// Character mismatch + } + } + } + return true;// String matches against pattern + } + + if( patIdxEnd == 0 ) + { + return true;// Pattern contains only '*', which matches anything + } + + // Process characters before first star + while( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd ) + { + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxStart] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxStart] ) ) + { + return false;// Character mismatch + } + } + patIdxStart++; + strIdxStart++; + } + if( strIdxStart > strIdxEnd ) + { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + // Process characters after last star + while( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd ) + { + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxEnd] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxEnd] ) ) + { + return false;// Character mismatch + } + } + patIdxEnd--; + strIdxEnd--; + } + if( strIdxStart > strIdxEnd ) + { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + // process pattern between stars. padIdxStart and patIdxEnd point + // always to a '*'. + while( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd ) + { + int patIdxTmp = -1; + for( int i = patIdxStart + 1; i <= patIdxEnd; i++ ) + { + if( patArr[i] == '*' ) + { + patIdxTmp = i; + break; + } + } + if( patIdxTmp == patIdxStart + 1 ) + { + // Two stars next to each other, skip the first one. + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = ( patIdxTmp - patIdxStart - 1 ); + int strLength = ( strIdxEnd - strIdxStart + 1 ); + int foundIdx = -1; + strLoop : + for( int i = 0; i <= strLength - patLength; i++ ) + { + for( int j = 0; j < patLength; j++ ) + { + ch = patArr[patIdxStart + j + 1]; + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxStart + i + j] ) + { + continue strLoop; + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxStart + i + j] ) ) + { + continue strLoop; + } + } + } + + foundIdx = strIdxStart + i; + break; + } + + if( foundIdx == -1 ) + { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + // All characters in the string are used. Check if only '*'s are left + // in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + /** + * Matches a path against a pattern. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @return true when the pattern matches against the string. + * false otherwise. + */ + protected static boolean matchPath( String pattern, String str ) + { + return matchPath( pattern, str, true ); + } + + /** + * Matches a path against a pattern. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @param isCaseSensitive must a case sensitive match be done? + * @return true when the pattern matches against the string. + * false otherwise. + */ + protected static boolean matchPath( String pattern, String str, boolean isCaseSensitive ) + { + // When str starts with a File.separator, pattern has to start with a + // File.separator. + // When pattern starts with a File.separator, str has to start with a + // File.separator. + if( str.startsWith( File.separator ) != + pattern.startsWith( File.separator ) ) + { + return false; + } + + Vector patDirs = new Vector(); + StringTokenizer st = new StringTokenizer( pattern, File.separator ); + while( st.hasMoreTokens() ) + { + patDirs.addElement( st.nextToken() ); + } + + Vector strDirs = new Vector(); + st = new StringTokenizer( str, File.separator ); + while( st.hasMoreTokens() ) + { + strDirs.addElement( st.nextToken() ); + } + + int patIdxStart = 0; + int patIdxEnd = patDirs.size() - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.size() - 1; + + // up to first '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxStart ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxStart ), isCaseSensitive ) ) + { + return false; + } + patIdxStart++; + strIdxStart++; + } + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + return true; + } + else + { + if( patIdxStart > patIdxEnd ) + { + // String not exhausted, but pattern is. Failure. + return false; + } + } + + // up to last '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxEnd ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxEnd ), isCaseSensitive ) ) + { + return false; + } + patIdxEnd--; + strIdxEnd--; + } + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + return true; + } + + while( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd ) + { + int patIdxTmp = -1; + for( int i = patIdxStart + 1; i <= patIdxEnd; i++ ) + { + if( patDirs.elementAt( i ).equals( "**" ) ) + { + patIdxTmp = i; + break; + } + } + if( patIdxTmp == patIdxStart + 1 ) + { + // '**/**' situation, so skip one + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = ( patIdxTmp - patIdxStart - 1 ); + int strLength = ( strIdxEnd - strIdxStart + 1 ); + int foundIdx = -1; + strLoop : + for( int i = 0; i <= strLength - patLength; i++ ) + { + for( int j = 0; j < patLength; j++ ) + { + String subPat = ( String )patDirs.elementAt( patIdxStart + j + 1 ); + String subStr = ( String )strDirs.elementAt( strIdxStart + i + j ); + if( !match( subPat, subStr, isCaseSensitive ) ) + { + continue strLoop; + } + } + + foundIdx = strIdxStart + i; + break; + } + + if( foundIdx == -1 ) + { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + + return true; + } + + + /** + * Does the path match the start of this pattern up to the first "**".

+ * + * This is not a general purpose test and should only be used if you can + * live with false positives.

+ * + * pattern=**\\a and str=b will yield true. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @return Description of the Returned Value + */ + protected static boolean matchPatternStart( String pattern, String str ) + { + return matchPatternStart( pattern, str, true ); + } + + /** + * Does the path match the start of this pattern up to the first "**".

+ * + * This is not a general purpose test and should only be used if you can + * live with false positives.

+ * + * pattern=**\\a and str=b will yield true. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @param isCaseSensitive must matches be case sensitive? + * @return Description of the Returned Value + */ + protected static boolean matchPatternStart( String pattern, String str, + boolean isCaseSensitive ) + { + // When str starts with a File.separator, pattern has to start with a + // File.separator. + // When pattern starts with a File.separator, str has to start with a + // File.separator. + if( str.startsWith( File.separator ) != + pattern.startsWith( File.separator ) ) + { + return false; + } + + Vector patDirs = new Vector(); + StringTokenizer st = new StringTokenizer( pattern, File.separator ); + while( st.hasMoreTokens() ) + { + patDirs.addElement( st.nextToken() ); + } + + Vector strDirs = new Vector(); + st = new StringTokenizer( str, File.separator ); + while( st.hasMoreTokens() ) + { + strDirs.addElement( st.nextToken() ); + } + + int patIdxStart = 0; + int patIdxEnd = patDirs.size() - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.size() - 1; + + // up to first '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxStart ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxStart ), isCaseSensitive ) ) + { + return false; + } + patIdxStart++; + strIdxStart++; + } + + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + return true; + } + else if( patIdxStart > patIdxEnd ) + { + // String not exhausted, but pattern is. Failure. + return false; + } + else + { + // pattern now holds ** while string is not exhausted + // this will generate false positives but we can live with that. + return true; + } + } + + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. All '/' and '\' characters are replaced by File.separatorChar + * . So the separator used need not match File.separatorChar. + * + * @param basedir the (non-null) basedir for scanning + */ + public void setBasedir( String basedir ) + { + setBasedir( new File( basedir.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ) ) ); + } + + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the basedir for scanning + */ + public void setBasedir( File basedir ) + { + this.basedir = basedir; + } + + + /** + * Sets the case sensitivity of the file system + * + * @param isCaseSensitive The new CaseSensitive value + */ + public void setCaseSensitive( boolean isCaseSensitive ) + { + this.isCaseSensitive = isCaseSensitive; + } + + + /** + * Sets the set of exclude patterns to use. All '/' and '\' characters are + * replaced by File.separatorChar. So the separator used need + * not match File.separatorChar.

+ * + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param excludes list of exclude patterns + */ + public void setExcludes( String[] excludes ) + { + if( excludes == null ) + { + this.excludes = null; + } + else + { + this.excludes = new String[excludes.length]; + for( int i = 0; i < excludes.length; i++ ) + { + String pattern; + pattern = excludes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + if( pattern.endsWith( File.separator ) ) + { + pattern += "**"; + } + this.excludes[i] = pattern; + } + } + } + + /** + * Sets the set of include patterns to use. All '/' and '\' characters are + * replaced by File.separatorChar. So the separator used need + * not match File.separatorChar.

+ * + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param includes list of include patterns + */ + public void setIncludes( String[] includes ) + { + if( includes == null ) + { + this.includes = null; + } + else + { + this.includes = new String[includes.length]; + for( int i = 0; i < includes.length; i++ ) + { + String pattern; + pattern = includes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + if( pattern.endsWith( File.separator ) ) + { + pattern += "**"; + } + this.includes[i] = pattern; + } + } + } + + + /** + * Gets the basedir that is used for scanning. This is the directory that is + * scanned recursively. + * + * @return the basedir that is used for scanning + */ + public File getBasedir() + { + return basedir; + } + + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the directories + */ + public String[] getExcludedDirectories() + { + slowScan(); + int count = dirsExcluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsExcluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the files + */ + public String[] getExcludedFiles() + { + slowScan(); + int count = filesExcluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesExcluded.elementAt( i ); + } + return files; + } + + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the directories + */ + public String[] getIncludedDirectories() + { + int count = dirsIncluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsIncluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at least one of the include + * patterns, and matched none of the exclude patterns. The names are + * relative to the basedir. + * + * @return the names of the files + */ + public String[] getIncludedFiles() + { + int count = filesIncluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesIncluded.elementAt( i ); + } + return files; + } + + + /** + * Get the names of the directories that matched at none of the include + * patterns. The names are relative to the basedir. + * + * @return the names of the directories + */ + public String[] getNotIncludedDirectories() + { + slowScan(); + int count = dirsNotIncluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsNotIncluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at none of the include patterns. + * The names are relative to the basedir. + * + * @return the names of the files + */ + public String[] getNotIncludedFiles() + { + slowScan(); + int count = filesNotIncluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesNotIncluded.elementAt( i ); + } + return files; + } + + /** + * Has the scanner excluded or omitted any files or directories it came + * accross? + * + * @return true if all files and directories that have been found, are + * included. + */ + public boolean isEverythingIncluded() + { + return everythingIncluded; + } + + + /** + * Adds the array with default exclusions to the current exclusions set. + */ + public void addDefaultExcludes() + { + int excludesLength = excludes == null ? 0 : excludes.length; + String[] newExcludes; + newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length]; + if( excludesLength > 0 ) + { + System.arraycopy( excludes, 0, newExcludes, 0, excludesLength ); + } + for( int i = 0; i < DEFAULTEXCLUDES.length; i++ ) + { + newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + } + excludes = newExcludes; + } + + + /** + * Scans the base directory for files that match at least one include + * pattern, and don't match any exclude patterns. + * + */ + public void scan() + { + if( basedir == null ) + { + throw new IllegalStateException( "No basedir set" ); + } + if( !basedir.exists() ) + { + throw new IllegalStateException( "basedir " + basedir + + " does not exist" ); + } + if( !basedir.isDirectory() ) + { + throw new IllegalStateException( "basedir " + basedir + + " is not a directory" ); + } + + if( includes == null ) + { + // No includes supplied, so set it to 'matches all' + includes = new String[1]; + includes[0] = "**"; + } + if( excludes == null ) + { + excludes = new String[0]; + } + + filesIncluded = new Vector(); + filesNotIncluded = new Vector(); + filesExcluded = new Vector(); + dirsIncluded = new Vector(); + dirsNotIncluded = new Vector(); + dirsExcluded = new Vector(); + + if( isIncluded( "" ) ) + { + if( !isExcluded( "" ) ) + { + dirsIncluded.addElement( "" ); + } + else + { + dirsExcluded.addElement( "" ); + } + } + else + { + dirsNotIncluded.addElement( "" ); + } + scandir( basedir, "", true ); + } + + /** + * Tests whether a name matches against at least one exclude pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * exclude pattern, false otherwise. + */ + protected boolean isExcluded( String name ) + { + for( int i = 0; i < excludes.length; i++ ) + { + if( matchPath( excludes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + + /** + * Tests whether a name matches against at least one include pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * include pattern, false otherwise. + */ + protected boolean isIncluded( String name ) + { + for( int i = 0; i < includes.length; i++ ) + { + if( matchPath( includes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + /** + * Tests whether a name matches the start of at least one include pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * include pattern, false otherwise. + */ + protected boolean couldHoldIncluded( String name ) + { + for( int i = 0; i < includes.length; i++ ) + { + if( matchPatternStart( includes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + + /** + * Scans the passed dir for files and directories. Found files and + * directories are placed in their respective collections, based on the + * matching of includes and excludes. When a directory is found, it is + * scanned recursively. + * + * @param dir the directory to scan + * @param vpath the path relative to the basedir (needed to prevent problems + * with an absolute path when using dir) + * @param fast Description of Parameter + * @see #filesIncluded + * @see #filesNotIncluded + * @see #filesExcluded + * @see #dirsIncluded + * @see #dirsNotIncluded + * @see #dirsExcluded + */ + protected void scandir( File dir, String vpath, boolean fast ) + { + String[] newfiles = dir.list(); + + if( newfiles == null ) + { + /* + * two reasons are mentioned in the API docs for File.list + * (1) dir is not a directory. This is impossible as + * we wouldn't get here in this case. + * (2) an IO error occurred (why doesn't it throw an exception + * then???) + */ + throw new BuildException( "IO error scanning directory " + + dir.getAbsolutePath() ); + } + + for( int i = 0; i < newfiles.length; i++ ) + { + String name = vpath + newfiles[i]; + File file = new File( dir, newfiles[i] ); + if( file.isDirectory() ) + { + if( isIncluded( name ) ) + { + if( !isExcluded( name ) ) + { + dirsIncluded.addElement( name ); + if( fast ) + { + scandir( file, name + File.separator, fast ); + } + } + else + { + everythingIncluded = false; + dirsExcluded.addElement( name ); + if( fast && couldHoldIncluded( name ) ) + { + scandir( file, name + File.separator, fast ); + } + } + } + else + { + everythingIncluded = false; + dirsNotIncluded.addElement( name ); + if( fast && couldHoldIncluded( name ) ) + { + scandir( file, name + File.separator, fast ); + } + } + if( !fast ) + { + scandir( file, name + File.separator, fast ); + } + } + else if( file.isFile() ) + { + if( isIncluded( name ) ) + { + if( !isExcluded( name ) ) + { + filesIncluded.addElement( name ); + } + else + { + everythingIncluded = false; + filesExcluded.addElement( name ); + } + } + else + { + everythingIncluded = false; + filesNotIncluded.addElement( name ); + } + } + } + } + + /** + * Toplevel invocation for the scan.

+ * + * Returns immediately if a slow scan has already been requested. + */ + protected void slowScan() + { + if( haveSlowResults ) + { + return; + } + + String[] excl = new String[dirsExcluded.size()]; + dirsExcluded.copyInto( excl ); + + String[] notIncl = new String[dirsNotIncluded.size()]; + dirsNotIncluded.copyInto( notIncl ); + + for( int i = 0; i < excl.length; i++ ) + { + if( !couldHoldIncluded( excl[i] ) ) + { + scandir( new File( basedir, excl[i] ), + excl[i] + File.separator, false ); + } + } + + for( int i = 0; i < notIncl.length; i++ ) + { + if( !couldHoldIncluded( notIncl[i] ) ) + { + scandir( new File( basedir, notIncl[i] ), + notIncl[i] + File.separator, false ); + } + } + + haveSlowResults = true; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/ExitException.java b/proposal/myrmidon/src/main/org/apache/tools/ant/ExitException.java new file mode 100644 index 000000000..8e812b1f2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/ExitException.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Used to report exit status of classes which call System.exit() + * + * @author Conor MacNeill + * @see NoExitSecurityManager + */ +public class ExitException extends SecurityException +{ + + private int status; + + /** + * Constructs an exit exception. + * + * @param status the status code returned via System.exit() + */ + public ExitException( int status ) + { + super( "ExitException: status " + status ); + this.status = status; + } + + /** + * @return the status code return via System.exit() + */ + public int getStatus() + { + return status; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/FileScanner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/FileScanner.java new file mode 100644 index 000000000..a23d95e26 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/FileScanner.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; + +/** + * An interface used to describe the actions required by any type of directory + * scanner. + * + * @author RT + */ +public interface FileScanner +{ + /** + * Adds an array with default exclusions to the current exclusions set. + */ + void addDefaultExcludes(); + + /** + * Gets the basedir that is used for scanning. This is the directory that is + * scanned recursively. + * + * @return the basedir that is used for scanning + */ + File getBasedir(); + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the directories + */ + String[] getExcludedDirectories(); + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the files + */ + String[] getExcludedFiles(); + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the directories + */ + String[] getIncludedDirectories(); + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the files + */ + String[] getIncludedFiles(); + + /** + * Get the names of the directories that matched at none of the include + * patterns. The names are relative to the basedir. + * + * @return the names of the directories + */ + String[] getNotIncludedDirectories(); + + /** + * Get the names of the files that matched at none of the include patterns. + * The names are relative to the basedir. + * + * @return the names of the files + */ + String[] getNotIncludedFiles(); + + /** + * Scans the base directory for files that match at least one include + * pattern, and don't match any exclude patterns. + * + */ + void scan(); + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the (non-null) basedir for scanning + */ + void setBasedir( String basedir ); + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the basedir for scanning + */ + void setBasedir( File basedir ); + + /** + * Sets the set of exclude patterns to use. + * + * @param excludes list of exclude patterns + */ + void setExcludes( String[] excludes ); + + /** + * Sets the set of include patterns to use. + * + * @param includes list of include patterns + */ + void setIncludes( String[] includes ); + + /** + * Sets the case sensitivity of the file system + * + * @param isCaseSensitive The new CaseSensitive value + */ + void setCaseSensitive( boolean isCaseSensitive ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/IntrospectionHelper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/IntrospectionHelper.java new file mode 100644 index 000000000..8b7160c9f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/IntrospectionHelper.java @@ -0,0 +1,848 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; + +/** + * Helper class that collects the methods a task or nested element holds to set + * attributes, create nested elements or hold PCDATA elements. + * + * @author Stefan Bodewig + */ +public class IntrospectionHelper implements BuildListener +{ + + /** + * instances we've already created + */ + private static Hashtable helpers = new Hashtable(); + + /** + * The method to add PCDATA stuff. + */ + private Method addText = null; + + /** + * holds the attribute setter methods. + */ + private Hashtable attributeSetters; + + /** + * holds the types of the attributes that could be set. + */ + private Hashtable attributeTypes; + + /** + * The Class that's been introspected. + */ + private Class bean; + + /** + * Holds methods to create nested elements. + */ + private Hashtable nestedCreators; + + /** + * Holds methods to store configured nested elements. + */ + private Hashtable nestedStorers; + + /** + * Holds the types of nested elements that could be created. + */ + private Hashtable nestedTypes; + + private IntrospectionHelper( final Class bean ) + { + attributeTypes = new Hashtable(); + attributeSetters = new Hashtable(); + nestedTypes = new Hashtable(); + nestedCreators = new Hashtable(); + nestedStorers = new Hashtable(); + + this.bean = bean; + + Method[] methods = bean.getMethods(); + for( int i = 0; i < methods.length; i++ ) + { + final Method m = methods[i]; + final String name = m.getName(); + Class returnType = m.getReturnType(); + Class[] args = m.getParameterTypes(); + + // not really user settable properties on tasks + if( org.apache.tools.ant.Task.class.isAssignableFrom( bean ) + && args.length == 1 && + ( + ( + "setLocation".equals( name ) && org.apache.tools.ant.Location.class.equals( args[0] ) + ) || ( + "setTaskType".equals( name ) && java.lang.String.class.equals( args[0] ) + ) + ) ) + { + continue; + } + + // hide addTask for TaskContainers + if( org.apache.tools.ant.TaskContainer.class.isAssignableFrom( bean ) + && args.length == 1 && "addTask".equals( name ) + && org.apache.tools.ant.Task.class.equals( args[0] ) ) + { + continue; + } + + if( "addText".equals( name ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && java.lang.String.class.equals( args[0] ) ) + { + + addText = methods[i]; + + } + else if( name.startsWith( "set" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !args[0].isArray() ) + { + + String propName = getPropertyName( name, "set" ); + if( attributeSetters.get( propName ) != null ) + { + if( java.lang.String.class.equals( args[0] ) ) + { + /* + * Ignore method m, as there is an overloaded + * form of this method that takes in a + * non-string argument, which gains higher + * priority. + */ + continue; + } + /* + * If the argument is not a String, and if there + * is an overloaded form of this method already defined, + * we just override that with the new one. + * This mechanism does not guarantee any specific order + * in which the methods will be selected: so any code + * that depends on the order in which "set" methods have + * been defined, is not guaranteed to be selected in any + * particular order. + */ + } + AttributeSetter as = createAttributeSetter( m, args[0] ); + if( as != null ) + { + attributeTypes.put( propName, args[0] ); + attributeSetters.put( propName, as ); + } + + } + else if( name.startsWith( "create" ) + && !returnType.isArray() + && !returnType.isPrimitive() + && args.length == 0 ) + { + + String propName = getPropertyName( name, "create" ); + nestedTypes.put( propName, returnType ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, + IllegalAccessException + { + + return m.invoke( parent, new Object[]{} ); + } + + } ); + + } + else if( name.startsWith( "addConfigured" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !java.lang.String.class.equals( args[0] ) + && !args[0].isArray() + && !args[0].isPrimitive() ) + { + + try + { + final Constructor c = + args[0].getConstructor( new Class[]{} ); + String propName = getPropertyName( name, "addConfigured" ); + nestedTypes.put( propName, args[0] ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + Object o = c.newInstance( new Object[]{} ); + return o; + } + + } ); + nestedStorers.put( propName, + new NestedStorer() + { + + public void store( Object parent, Object child ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + m.invoke( parent, new Object[]{child} ); + } + + } ); + } + catch( NoSuchMethodException nse ) + { + } + } + else if( name.startsWith( "add" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !java.lang.String.class.equals( args[0] ) + && !args[0].isArray() + && !args[0].isPrimitive() ) + { + + try + { + final Constructor c = + args[0].getConstructor( new Class[]{} ); + String propName = getPropertyName( name, "add" ); + nestedTypes.put( propName, args[0] ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + Object o = c.newInstance( new Object[]{} ); + m.invoke( parent, new Object[]{o} ); + return o; + } + + } ); + } + catch( NoSuchMethodException nse ) + { + } + } + } + } + + /** + * Factory method for helper objects. + * + * @param c Description of Parameter + * @return The Helper value + */ + public static synchronized IntrospectionHelper getHelper( Class c ) + { + IntrospectionHelper ih = ( IntrospectionHelper )helpers.get( c ); + if( ih == null ) + { + ih = new IntrospectionHelper( c ); + helpers.put( c, ih ); + } + return ih; + } + + /** + * Sets the named attribute. + * + * @param p The new Attribute value + * @param element The new Attribute value + * @param attributeName The new Attribute value + * @param value The new Attribute value + * @exception BuildException Description of Exception + */ + public void setAttribute( Project p, Object element, String attributeName, + String value ) + throws BuildException + { + AttributeSetter as = ( AttributeSetter )attributeSetters.get( attributeName ); + if( as == null ) + { + String msg = getElementName( p, element ) + + //String msg = "Class " + element.getClass().getName() + + " doesn't support the \"" + attributeName + "\" attribute."; + throw new BuildException( msg ); + } + try + { + as.set( p, element, value ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + /** + * returns the type of a named attribute. + * + * @param attributeName Description of Parameter + * @return The AttributeType value + * @exception BuildException Description of Exception + */ + public Class getAttributeType( String attributeName ) + throws BuildException + { + Class at = ( Class )attributeTypes.get( attributeName ); + if( at == null ) + { + String msg = "Class " + bean.getName() + + " doesn't support the \"" + attributeName + "\" attribute."; + throw new BuildException( msg ); + } + return at; + } + + /** + * Return all attribues supported by the introspected class. + * + * @return The Attributes value + */ + public Enumeration getAttributes() + { + return attributeSetters.keys(); + } + + /** + * returns the type of a named nested element. + * + * @param elementName Description of Parameter + * @return The ElementType value + * @exception BuildException Description of Exception + */ + public Class getElementType( String elementName ) + throws BuildException + { + Class nt = ( Class )nestedTypes.get( elementName ); + if( nt == null ) + { + String msg = "Class " + bean.getName() + + " doesn't support the nested \"" + elementName + "\" element."; + throw new BuildException( msg ); + } + return nt; + } + + /** + * Return all nested elements supported by the introspected class. + * + * @return The NestedElements value + */ + public Enumeration getNestedElements() + { + return nestedTypes.keys(); + } + + /** + * Adds PCDATA areas. + * + * @param project The feature to be added to the Text attribute + * @param element The feature to be added to the Text attribute + * @param text The feature to be added to the Text attribute + */ + public void addText( Project project, Object element, String text ) + { + if( addText == null ) + { + String msg = getElementName( project, element ) + + //String msg = "Class " + element.getClass().getName() + + " doesn't support nested text data."; + throw new BuildException( msg ); + } + try + { + addText.invoke( element, new String[]{text} ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + public void buildFinished( BuildEvent event ) + { + attributeTypes.clear(); + attributeSetters.clear(); + nestedTypes.clear(); + nestedCreators.clear(); + addText = null; + helpers.clear(); + } + + public void buildStarted( BuildEvent event ) { } + + /** + * Creates a named nested element. + * + * @param project Description of Parameter + * @param element Description of Parameter + * @param elementName Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Object createElement( Project project, Object element, String elementName ) + throws BuildException + { + NestedCreator nc = ( NestedCreator )nestedCreators.get( elementName ); + if( nc == null ) + { + String msg = getElementName( project, element ) + + " doesn't support the nested \"" + elementName + "\" element."; + throw new BuildException( msg ); + } + try + { + Object nestedElement = nc.create( element ); + if( nestedElement instanceof ProjectComponent ) + { + ( ( ProjectComponent )nestedElement ).setProject( project ); + } + return nestedElement; + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InstantiationException ine ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ine ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + public void messageLogged( BuildEvent event ) { } + + /** + * Creates a named nested element. + * + * @param project Description of Parameter + * @param element Description of Parameter + * @param child Description of Parameter + * @param elementName Description of Parameter + * @exception BuildException Description of Exception + */ + public void storeElement( Project project, Object element, Object child, String elementName ) + throws BuildException + { + if( elementName == null ) + { + return; + } + NestedStorer ns = ( NestedStorer )nestedStorers.get( elementName ); + if( ns == null ) + { + return; + } + try + { + ns.store( element, child ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InstantiationException ine ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ine ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + /** + * Does the introspected class support PCDATA? + * + * @return Description of the Returned Value + */ + public boolean supportsCharacters() + { + return addText != null; + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) { } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + protected String getElementName( Project project, Object element ) + { + Hashtable elements = project.getTaskDefinitions(); + String typeName = "task"; + if( !elements.contains( element.getClass() ) ) + { + elements = project.getDataTypeDefinitions(); + typeName = "data type"; + if( !elements.contains( element.getClass() ) ) + { + elements = null; + } + } + + if( elements != null ) + { + Enumeration e = elements.keys(); + while( e.hasMoreElements() ) + { + String elementName = ( String )e.nextElement(); + Class elementClass = ( Class )elements.get( elementName ); + if( element.getClass().equals( elementClass ) ) + { + return "The <" + elementName + "> " + typeName; + } + } + } + + return "Class " + element.getClass().getName(); + } + + /** + * extract the name of a property from a method name - subtracting a given + * prefix. + * + * @param methodName Description of Parameter + * @param prefix Description of Parameter + * @return The PropertyName value + */ + private String getPropertyName( String methodName, String prefix ) + { + int start = prefix.length(); + return methodName.substring( start ).toLowerCase( Locale.US ); + } + + /** + * Create a proper implementation of AttributeSetter for the given attribute + * type. + * + * @param m Description of Parameter + * @param arg Description of Parameter + * @return Description of the Returned Value + */ + private AttributeSetter createAttributeSetter( final Method m, + final Class arg ) + { + + // simplest case - setAttribute expects String + if( java.lang.String.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new String[]{value} ); + } + }; + // now for the primitive types, use their wrappers + } + else if( java.lang.Character.class.equals( arg ) + || java.lang.Character.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Character[]{new Character( value.charAt( 0 ) )} ); + } + + }; + } + else if( java.lang.Byte.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Byte[]{new Byte( value )} ); + } + + }; + } + else if( java.lang.Short.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Short[]{new Short( value )} ); + } + + }; + } + else if( java.lang.Integer.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Integer[]{new Integer( value )} ); + } + + }; + } + else if( java.lang.Long.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Long[]{new Long( value )} ); + } + + }; + } + else if( java.lang.Float.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Float[]{new Float( value )} ); + } + + }; + } + else if( java.lang.Double.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Double[]{new Double( value )} ); + } + + }; + // boolean gets an extra treatment, because we have a nice method + // in Project + } + else if( java.lang.Boolean.class.equals( arg ) + || java.lang.Boolean.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, + new Boolean[]{new Boolean( Project.toBoolean( value ) )} ); + } + + }; + // Class doesn't have a String constructor but a decent factory method + } + else if( java.lang.Class.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + m.invoke( parent, new Class[]{Class.forName( value )} ); + } + catch( ClassNotFoundException ce ) + { + throw new BuildException( ce ); + } + } + }; + // resolve relative paths through Project + } + else if( java.io.File.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new File[]{p.resolveFile( value )} ); + } + + }; + // resolve relative paths through Project + } + else if( org.apache.tools.ant.types.Path.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Path[]{new Path( p, value )} ); + } + + }; + // EnumeratedAttributes have their own helper class + } + else if( org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + org.apache.tools.ant.types.EnumeratedAttribute ea = ( org.apache.tools.ant.types.EnumeratedAttribute )arg.newInstance(); + ea.setValue( value ); + m.invoke( parent, new EnumeratedAttribute[]{ea} ); + } + catch( InstantiationException ie ) + { + throw new BuildException( ie ); + } + } + }; + + // worst case. look for a public String constructor and use it + } + else + { + + try + { + final Constructor c = + arg.getConstructor( new Class[]{java.lang.String.class} ); + + return + new AttributeSetter() + { + public void set( Project p, Object parent, + String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + Object attribute = c.newInstance( new String[]{value} ); + if( attribute instanceof ProjectComponent ) + { + ( ( ProjectComponent )attribute ).setProject( p ); + } + m.invoke( parent, new Object[]{attribute} ); + } + catch( InstantiationException ie ) + { + throw new BuildException( ie ); + } + } + }; + } + catch( NoSuchMethodException nme ) + { + } + } + + return null; + } + + private interface AttributeSetter + { + void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, + BuildException; + } + + private interface NestedCreator + { + Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException; + } + + private interface NestedStorer + { + void store( Object parent, Object child ) + throws InvocationTargetException, IllegalAccessException, InstantiationException; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Launcher.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Launcher.java new file mode 100644 index 000000000..0a220ec89 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Launcher.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FilenameFilter; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + * This is the Ant command line front end to end. This front end works out where + * ant is installed and loads the ant libraries before starting Ant proper. + * + * @author Conor MacNeill + */ +public class Launcher +{ + + public static void main( String[] args ) + { + File antHome = null; + ClassLoader systemClassLoader = Launcher.class.getClassLoader(); + if( systemClassLoader == null ) + { + antHome = determineAntHome11(); + } + else + { + antHome = determineAntHome( systemClassLoader ); + } + if( antHome == null ) + { + System.err.println( "Unable to determine ANT_HOME" ); + System.exit( 1 ); + } + + System.out.println( "ANT_HOME is " + antHome ); + + // We now create the class loader with which we are going to launch ant + AntClassLoader antLoader = new AntClassLoader( systemClassLoader, false ); + + // need to find tools.jar + addToolsJar( antLoader ); + + // add everything in the lib directory to this classloader + File libDir = new File( antHome, "lib" ); + addDirJars( antLoader, libDir ); + + File optionalDir = new File( antHome, "lib/optional" ); + addDirJars( antLoader, optionalDir ); + + Properties launchProperties = new Properties(); + launchProperties.put( "ant.home", antHome.getAbsolutePath() ); + + try + { + Class mainClass = antLoader.loadClass( "org.apache.tools.ant.Main" ); + antLoader.initializeClass( mainClass ); + + final Class[] param = {Class.forName( "[Ljava.lang.String;" ), + Properties.class, ClassLoader.class}; + final Method startMethod = mainClass.getMethod( "start", param ); + final Object[] argument = {args, launchProperties, systemClassLoader}; + startMethod.invoke( null, argument ); + } + catch( Exception e ) + { + System.out.println( "Exception running Ant: " + e.getClass().getName() + ": " + e.getMessage() ); + e.printStackTrace(); + } + } + + private static void addDirJars( AntClassLoader classLoader, File jarDir ) + { + String[] fileList = jarDir.list( + new FilenameFilter() + { + public boolean accept( File dir, String name ) + { + return name.endsWith( ".jar" ); + } + } ); + + if( fileList != null ) + { + for( int i = 0; i < fileList.length; ++i ) + { + File jarFile = new File( jarDir, fileList[i] ); + classLoader.addPathElement( jarFile.getAbsolutePath() ); + } + } + } + + private static void addToolsJar( AntClassLoader antLoader ) + { + String javaHome = System.getProperty( "java.home" ); + if( javaHome.endsWith( "jre" ) ) + { + javaHome = javaHome.substring( 0, javaHome.length() - 4 ); + } + System.out.println( "Java home is " + javaHome ); + File toolsJar = new File( javaHome, "lib/tools.jar" ); + if( !toolsJar.exists() ) + { + System.out.println( "Unable to find tools.jar at " + toolsJar.getPath() ); + } + else + { + antLoader.addPathElement( toolsJar.getAbsolutePath() ); + } + } + + private static File determineAntHome( ClassLoader systemClassLoader ) + { + try + { + String className = Launcher.class.getName().replace( '.', '/' ) + ".class"; + URL classResource = systemClassLoader.getResource( className ); + String fileComponent = classResource.getFile(); + if( classResource.getProtocol().equals( "file" ) ) + { + // Class comes from a directory of class files rather than + // from a jar. + int classFileIndex = fileComponent.lastIndexOf( className ); + if( classFileIndex != -1 ) + { + fileComponent = fileComponent.substring( 0, classFileIndex ); + } + File classFilesDir = new File( fileComponent ); + File buildDir = new File( classFilesDir.getParent() ); + File devAntHome = new File( buildDir.getParent() ); + return devAntHome; + } + else if( classResource.getProtocol().equals( "jar" ) ) + { + // Class is coming from a jar. The file component of the URL + // is actually the URL of the jar file + int classSeparatorIndex = fileComponent.lastIndexOf( "!" ); + if( classSeparatorIndex != -1 ) + { + fileComponent = fileComponent.substring( 0, classSeparatorIndex ); + } + URL antJarURL = new URL( fileComponent ); + File antJarFile = new File( antJarURL.getFile() ); + File libDirectory = new File( antJarFile.getParent() ); + File antHome = new File( libDirectory.getParent() ); + return antHome; + } + } + catch( MalformedURLException e ) + { + e.printStackTrace(); + } + return null; + } + + private static File determineAntHome11() + { + String classpath = System.getProperty( "java.class.path" ); + StringTokenizer tokenizer = new StringTokenizer( classpath, System.getProperty( "path.separator" ) ); + while( tokenizer.hasMoreTokens() ) + { + String path = tokenizer.nextToken(); + if( path.endsWith( "ant.jar" ) ) + { + File antJarFile = new File( path ); + File libDirectory = new File( antJarFile.getParent() ); + File antHome = new File( libDirectory.getParent() ); + return antHome; + } + } + return null; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Location.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Location.java new file mode 100644 index 000000000..e48a844dc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Location.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Stores the file name and line number in a file. + * + * @author RT + */ +public class Location +{ + + public final static Location UNKNOWN_LOCATION = new Location(); + private int columnNumber; + private String fileName; + private int lineNumber; + + /** + * Creates a location consisting of a file name but no line number. + * + * @param fileName Description of Parameter + */ + public Location( String fileName ) + { + this( fileName, 0, 0 ); + } + + /** + * Creates a location consisting of a file name and line number. + * + * @param fileName Description of Parameter + * @param lineNumber Description of Parameter + * @param columnNumber Description of Parameter + */ + public Location( String fileName, int lineNumber, int columnNumber ) + { + this.fileName = fileName; + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + } + + /** + * Creates an "unknown" location. + */ + private Location() + { + this( null, 0, 0 ); + } + + /** + * Returns the file name, line number and a trailing space. An error message + * can be appended easily. For unknown locations, returns an empty string. + * + * @return Description of the Returned Value + */ + public String toString() + { + StringBuffer buf = new StringBuffer(); + + if( fileName != null ) + { + buf.append( fileName ); + + if( lineNumber != 0 ) + { + buf.append( ":" ); + buf.append( lineNumber ); + } + + buf.append( ": " ); + } + + return buf.toString(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Main.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Main.java new file mode 100644 index 000000000..0c605f732 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Main.java @@ -0,0 +1,842 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Enumeration; +import java.util.Properties; +import java.util.Vector; + +/** + * Command line entry point into Ant. This class is entered via the cannonical + * `public static void main` entry point and reads the command line arguments. + * It then assembles and executes an Ant project.

+ * + * If you integrating Ant into some other tool, this is not the class to use as + * an entry point. Please see the source code of this class to see how it + * manipulates the Ant project classes. + * + * @author duncan@x180.com + */ +public class Main +{ + + /** + * The default build file name + */ + public final static String DEFAULT_BUILD_FILENAME = "build.xml"; + + private static String antVersion = null; + + /** + * Our current message output status. Follows Project.MSG_XXX + */ + private int msgOutputLevel = Project.MSG_INFO; + /** + * null + */ + + /** + * Stream that we are using for logging + */ + private PrintStream out = System.out; + + /** + * Stream that we are using for logging error messages + */ + private PrintStream err = System.err; + + /** + * The build targets + */ + private Vector targets = new Vector( 5 ); + + /** + * Set of properties that can be used by tasks + */ + private Properties definedProps = new Properties(); + + /** + * Names of classes to add as listeners to project + */ + private Vector listeners = new Vector( 5 ); + + /** + * The Ant logger class. There may be only one logger. It will have the + * right to use the 'out' PrintStream. The class must implements the + * BuildLogger interface + */ + private String loggerClassname = null; + + /** + * Indicates whether output to the log is to be unadorned. + */ + private boolean emacsMode = false; + + /** + * Indicates if this ant should be run. + */ + private boolean readyToRun = false; + + /** + * Indicates we should only parse and display the project help information + */ + private boolean projectHelp = false; + + /** + * File that we are using for configuration + */ + private File buildFile; + + protected Main( String[] args ) + throws BuildException + { + + String searchForThis = null; + + // cycle through given args + + for( int i = 0; i < args.length; i++ ) + { + String arg = args[i]; + + if( arg.equals( "-help" ) ) + { + printUsage(); + return; + } + else if( arg.equals( "-version" ) ) + { + printVersion(); + return; + } + else if( arg.equals( "-quiet" ) || arg.equals( "-q" ) ) + { + msgOutputLevel = Project.MSG_WARN; + } + else if( arg.equals( "-verbose" ) || arg.equals( "-v" ) ) + { + printVersion(); + msgOutputLevel = Project.MSG_VERBOSE; + } + else if( arg.equals( "-debug" ) ) + { + printVersion(); + msgOutputLevel = Project.MSG_DEBUG; + } + else if( arg.equals( "-logfile" ) || arg.equals( "-l" ) ) + { + try + { + File logFile = new File( args[i + 1] ); + i++; + out = new PrintStream( new FileOutputStream( logFile ) ); + err = out; + System.setOut( out ); + System.setErr( out ); + } + catch( IOException ioe ) + { + String msg = "Cannot write on the specified log file. " + + "Make sure the path exists and you have write permissions."; + System.out.println( msg ); + return; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a log file when " + + "using the -log argument"; + System.out.println( msg ); + return; + } + } + else if( arg.equals( "-buildfile" ) || arg.equals( "-file" ) || arg.equals( "-f" ) ) + { + try + { + buildFile = new File( args[i + 1] ); + i++; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a buildfile when " + + "using the -buildfile argument"; + System.out.println( msg ); + return; + } + } + else if( arg.equals( "-listener" ) ) + { + try + { + listeners.addElement( args[i + 1] ); + i++; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a classname when " + + "using the -listener argument"; + System.out.println( msg ); + return; + } + } + else if( arg.startsWith( "-D" ) ) + { + + /* + * Interestingly enough, we get to here when a user + * uses -Dname=value. However, in some cases, the JDK + * goes ahead * and parses this out to args + * {"-Dname", "value"} + * so instead of parsing on "=", we just make the "-D" + * characters go away and skip one argument forward. + * + * I don't know how to predict when the JDK is going + * to help or not, so we simply look for the equals sign. + */ + String name = arg.substring( 2, arg.length() ); + String value = null; + int posEq = name.indexOf( "=" ); + if( posEq > 0 ) + { + value = name.substring( posEq + 1 ); + name = name.substring( 0, posEq ); + } + else if( i < args.length - 1 ) + value = args[++i]; + + definedProps.put( name, value ); + } + else if( arg.equals( "-logger" ) ) + { + if( loggerClassname != null ) + { + System.out.println( "Only one logger class may be specified." ); + return; + } + try + { + loggerClassname = args[++i]; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + System.out.println( "You must specify a classname when " + + "using the -logger argument" ); + return; + } + } + else if( arg.equals( "-emacs" ) ) + { + emacsMode = true; + } + else if( arg.equals( "-projecthelp" ) ) + { + // set the flag to display the targets and quit + projectHelp = true; + } + else if( arg.equals( "-find" ) ) + { + // eat up next arg if present, default to build.xml + if( i < args.length - 1 ) + { + searchForThis = args[++i]; + } + else + { + searchForThis = DEFAULT_BUILD_FILENAME; + } + } + else if( arg.startsWith( "-" ) ) + { + // we don't have any more args to recognize! + String msg = "Unknown argument: " + arg; + System.out.println( msg ); + printUsage(); + return; + } + else + { + // if it's no other arg, it may be the target + targets.addElement( arg ); + } + + } + + // if buildFile was not specified on the command line, + if( buildFile == null ) + { + // but -find then search for it + if( searchForThis != null ) + { + buildFile = findBuildFile( System.getProperty( "user.dir" ), + searchForThis ); + } + else + { + buildFile = new File( DEFAULT_BUILD_FILENAME ); + } + } + + // make sure buildfile exists + if( !buildFile.exists() ) + { + System.out.println( "Buildfile: " + buildFile + " does not exist!" ); + throw new BuildException( "Build failed" ); + } + + // make sure it's not a directory (this falls into the ultra + // paranoid lets check everything catagory + + if( buildFile.isDirectory() ) + { + System.out.println( "What? Buildfile: " + buildFile + " is a dir!" ); + throw new BuildException( "Build failed" ); + } + + readyToRun = true; + } + + public static synchronized String getAntVersion() + throws BuildException + { + if( antVersion == null ) + { + try + { + Properties props = new Properties(); + InputStream in = + Main.class.getResourceAsStream( "/org/apache/tools/ant/version.txt" ); + props.load( in ); + in.close(); + + String lSep = System.getProperty( "line.separator" ); + StringBuffer msg = new StringBuffer(); + msg.append( "Apache Ant version " ); + msg.append( props.getProperty( "VERSION" ) ); + msg.append( " compiled on " ); + msg.append( props.getProperty( "DATE" ) ); + antVersion = msg.toString(); + } + catch( IOException ioe ) + { + throw new BuildException( "Could not load the version information:" + + ioe.getMessage() ); + } + catch( NullPointerException npe ) + { + throw new BuildException( "Could not load the version information." ); + } + } + return antVersion; + } + + + /** + * Command line entry point. This method kicks off the building of a project + * object and executes a build using either a given target or the default + * target. + * + * @param args Command line args. + */ + public static void main( String[] args ) + { + start( args, null, null ); + } + + /** + * Entry point method. + * + * @param args Description of Parameter + * @param additionalUserProperties Description of Parameter + * @param coreLoader Description of Parameter + */ + public static void start( String[] args, Properties additionalUserProperties, + ClassLoader coreLoader ) + { + Main m = null; + + try + { + m = new Main( args ); + } + catch( Throwable exc ) + { + printMessage( exc ); + System.exit( 1 ); + } + + if( additionalUserProperties != null ) + { + for( Enumeration e = additionalUserProperties.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + String property = additionalUserProperties.getProperty( key ); + m.definedProps.put( key, property ); + } + } + + try + { + m.runBuild( coreLoader ); + System.exit( 0 ); + } + catch( BuildException be ) + { + if( m.err != System.err ) + { + printMessage( be ); + } + System.exit( 1 ); + } + catch( Throwable exc ) + { + exc.printStackTrace(); + printMessage( exc ); + System.exit( 1 ); + } + } + + /** + * Search for the insert position to keep names a sorted list of Strings + * + * @param names Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private static int findTargetPosition( Vector names, String name ) + { + int res = names.size(); + for( int i = 0; i < names.size() && res == names.size(); i++ ) + { + if( name.compareTo( ( String )names.elementAt( i ) ) < 0 ) + { + res = i; + } + } + return res; + } + + /** + * Print the project description, if any + * + * @param project Description of Parameter + */ + private static void printDescription( Project project ) + { + if( project.getDescription() != null ) + { + System.out.println( project.getDescription() ); + } + } + + /** + * Prints the message of the Throwable if it's not null. + * + * @param t Description of Parameter + */ + private static void printMessage( Throwable t ) + { + String message = t.getMessage(); + if( message != null ) + { + System.err.println( message ); + } + } + + /** + * Print out a list of all targets in the current buildfile + * + * @param project Description of Parameter + * @param printSubTargets Description of Parameter + */ + private static void printTargets( Project project, boolean printSubTargets ) + { + // find the target with the longest name + int maxLength = 0; + Enumeration ptargets = project.getTargets().elements(); + String targetName; + String targetDescription; + Target currentTarget; + // split the targets in top-level and sub-targets depending + // on the presence of a description + Vector topNames = new Vector(); + Vector topDescriptions = new Vector(); + Vector subNames = new Vector(); + + while( ptargets.hasMoreElements() ) + { + currentTarget = ( Target )ptargets.nextElement(); + targetName = currentTarget.getName(); + targetDescription = currentTarget.getDescription(); + // maintain a sorted list of targets + if( targetDescription == null ) + { + int pos = findTargetPosition( subNames, targetName ); + subNames.insertElementAt( targetName, pos ); + } + else + { + int pos = findTargetPosition( topNames, targetName ); + topNames.insertElementAt( targetName, pos ); + topDescriptions.insertElementAt( targetDescription, pos ); + if( targetName.length() > maxLength ) + { + maxLength = targetName.length(); + } + } + } + + printTargets( topNames, topDescriptions, "Main targets:", maxLength ); + + if( printSubTargets ) + { + printTargets( subNames, null, "Subtargets:", 0 ); + } + + String defaultTarget = project.getDefaultTarget(); + if( defaultTarget != null && !"".equals( defaultTarget ) ) + {// shouldn't need to check but... + System.out.println( "Default target: " + defaultTarget ); + } + } + + /** + * Output a formatted list of target names with an optional description + * + * @param names Description of Parameter + * @param descriptions Description of Parameter + * @param heading Description of Parameter + * @param maxlen Description of Parameter + */ + private static void printTargets( Vector names, Vector descriptions, String heading, int maxlen ) + { + // now, start printing the targets and their descriptions + String lSep = System.getProperty( "line.separator" ); + // got a bit annoyed that I couldn't find a pad function + String spaces = " "; + while( spaces.length() < maxlen ) + { + spaces += spaces; + } + StringBuffer msg = new StringBuffer(); + msg.append( heading + lSep + lSep ); + for( int i = 0; i < names.size(); i++ ) + { + msg.append( " " ); + msg.append( names.elementAt( i ) ); + if( descriptions != null ) + { + msg.append( spaces.substring( 0, maxlen - ( ( String )names.elementAt( i ) ).length() + 2 ) ); + msg.append( descriptions.elementAt( i ) ); + } + msg.append( lSep ); + } + System.out.println( msg.toString() ); + } + + /** + * Prints the usage of how to use this class to System.out + */ + private static void printUsage() + { + String lSep = System.getProperty( "line.separator" ); + StringBuffer msg = new StringBuffer(); + msg.append( "ant [options] [target [target2 [target3] ...]]" + lSep ); + msg.append( "Options: " + lSep ); + msg.append( " -help print this message" + lSep ); + msg.append( " -projecthelp print project help information" + lSep ); + msg.append( " -version print the version information and exit" + lSep ); + msg.append( " -quiet be extra quiet" + lSep ); + msg.append( " -verbose be extra verbose" + lSep ); + msg.append( " -debug print debugging information" + lSep ); + msg.append( " -emacs produce logging information without adornments" + lSep ); + msg.append( " -logfile use given file for log" + lSep ); + msg.append( " -logger the class which is to perform logging" + lSep ); + msg.append( " -listener add an instance of class as a project listener" + lSep ); + msg.append( " -buildfile use given buildfile" + lSep ); + msg.append( " -D= use value for given property" + lSep ); + msg.append( " -find search for buildfile towards the root of the" + lSep ); + msg.append( " filesystem and use it" + lSep ); + System.out.println( msg.toString() ); + } + + private static void printVersion() + throws BuildException + { + System.out.println( getAntVersion() ); + } + + protected void addBuildListeners( Project project ) + { + + // Add the default listener + project.addBuildListener( createLogger() ); + + for( int i = 0; i < listeners.size(); i++ ) + { + String className = ( String )listeners.elementAt( i ); + try + { + BuildListener listener = + ( BuildListener )Class.forName( className ).newInstance(); + project.addBuildListener( listener ); + } + catch( Throwable exc ) + { + throw new BuildException( "Unable to instantiate listener " + className, exc ); + } + } + } + + /** + * Helper to get the parent file for a given file.

+ * + * Added to simulate File.getParentFile() from JDK 1.2. + * + * @param file File + * @return Parent file or null if none + */ + private File getParentFile( File file ) + { + String filename = file.getAbsolutePath(); + file = new File( filename ); + filename = file.getParent(); + + if( filename != null && msgOutputLevel >= Project.MSG_VERBOSE ) + { + System.out.println( "Searching in " + filename ); + } + + return ( filename == null ) ? null : new File( filename ); + } + + /** + * Creates the default build logger for sending build events to the ant log. + * + * @return Description of the Returned Value + */ + private BuildLogger createLogger() + { + BuildLogger logger = null; + if( loggerClassname != null ) + { + try + { + logger = ( BuildLogger )( Class.forName( loggerClassname ).newInstance() ); + } + catch( ClassCastException e ) + { + System.err.println( "The specified logger class " + loggerClassname + + " does not implement the BuildLogger interface" ); + throw new RuntimeException(); + } + catch( Exception e ) + { + System.err.println( "Unable to instantiate specified logger class " + + loggerClassname + " : " + e.getClass().getName() ); + throw new RuntimeException(); + } + } + else + { + logger = new DefaultLogger(); + } + + logger.setMessageOutputLevel( msgOutputLevel ); + logger.setOutputPrintStream( out ); + logger.setErrorPrintStream( err ); + logger.setEmacsMode( emacsMode ); + + return logger; + } + + /** + * Search parent directories for the build file.

+ * + * Takes the given target as a suffix to append to each parent directory in + * seach of a build file. Once the root of the file-system has been reached + * an exception is thrown. + * + * @param suffix Suffix filename to look for in parents. + * @param start Description of Parameter + * @return A handle to the build file + * @exception BuildException Failed to locate a build file + */ + private File findBuildFile( String start, String suffix ) + throws BuildException + { + if( msgOutputLevel >= Project.MSG_INFO ) + { + System.out.println( "Searching for " + suffix + " ..." ); + } + + File parent = new File( new File( start ).getAbsolutePath() ); + File file = new File( parent, suffix ); + + // check if the target file exists in the current directory + while( !file.exists() ) + { + // change to parent directory + parent = getParentFile( parent ); + + // if parent is null, then we are at the root of the fs, + // complain that we can't find the build file. + if( parent == null ) + { + throw new BuildException( "Could not locate a build file!" ); + } + + // refresh our file handle + file = new File( parent, suffix ); + } + + return file; + } + + /** + * Executes the build. + * + * @param coreLoader Description of Parameter + * @exception BuildException Description of Exception + */ + private void runBuild( ClassLoader coreLoader ) + throws BuildException + { + + if( !readyToRun ) + { + return; + } + + // track when we started + + if( msgOutputLevel >= Project.MSG_INFO ) + { + System.out.println( "Buildfile: " + buildFile ); + } + + final Project project = new Project(); + project.setCoreLoader( coreLoader ); + + Throwable error = null; + + try + { + addBuildListeners( project ); + + PrintStream err = System.err; + PrintStream out = System.out; + + // use a system manager that prevents from System.exit() + // only in JDK > 1.1 + SecurityManager oldsm = null; + if( !Project.JAVA_1_0.equals( Project.getJavaVersion() ) && + !Project.JAVA_1_1.equals( Project.getJavaVersion() ) ) + { + oldsm = System.getSecurityManager(); + + //SecurityManager can not be installed here for backwards + //compatability reasons (PD). Needs to be loaded prior to + //ant class if we are going to implement it. + //System.setSecurityManager(new NoExitSecurityManager()); + } + try + { + System.setOut( new PrintStream( new DemuxOutputStream( project, false ) ) ); + System.setErr( new PrintStream( new DemuxOutputStream( project, true ) ) ); + + if( !projectHelp ) + { + project.fireBuildStarted(); + } + project.init(); + project.setUserProperty( "ant.version", getAntVersion() ); + + // set user-define properties + Enumeration e = definedProps.keys(); + while( e.hasMoreElements() ) + { + String arg = ( String )e.nextElement(); + String value = ( String )definedProps.get( arg ); + project.setUserProperty( arg, value ); + } + + project.setUserProperty( "ant.file", buildFile.getAbsolutePath() ); + + // first use the ProjectHelper to create the project object + // from the given build file. + String noParserMessage = + "No JAXP compliant XML parser found. Please visit http://xml.apache.org for a suitable parser"; + try + { + Class.forName( "javax.xml.parsers.SAXParserFactory" ); + ProjectHelper.configureProject( project, buildFile ); + } + catch( NoClassDefFoundError ncdfe ) + { + throw new BuildException( noParserMessage, ncdfe ); + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( noParserMessage, cnfe ); + } + catch( NullPointerException npe ) + { + throw new BuildException( noParserMessage, npe ); + } + + if( projectHelp ) + { + printDescription( project ); + printTargets( project, msgOutputLevel > Project.MSG_INFO ); + return; + } + + // make sure that we have a target to execute + if( targets.size() == 0 ) + { + targets.addElement( project.getDefaultTarget() ); + } + + project.executeTargets( targets ); + } + finally + { + // put back the original security manager + //The following will never eval to true. (PD) + if( oldsm != null ) + { + System.setSecurityManager( oldsm ); + } + + System.setOut( out ); + System.setErr( err ); + } + } + catch( RuntimeException exc ) + { + error = exc; + throw exc; + } + catch( Error err ) + { + error = err; + throw err; + } + finally + { + if( !projectHelp ) + { + project.fireBuildFinished( error ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/NoBannerLogger.java b/proposal/myrmidon/src/main/org/apache/tools/ant/NoBannerLogger.java new file mode 100644 index 000000000..9311265f5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/NoBannerLogger.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Extends DefaultLogger to strip out empty targets. + * + * @author Peter Donald + */ +public class NoBannerLogger extends DefaultLogger +{ + + private final static String lSep = System.getProperty( "line.separator" ); + + protected String targetName; + + public void messageLogged( BuildEvent event ) + { + + if( event.getPriority() > msgOutputLevel || + null == event.getMessage() || + "".equals( event.getMessage().trim() ) ) + { + return; + } + + if( null != targetName ) + { + out.println( lSep + targetName + ":" ); + targetName = null; + } + + super.messageLogged( event ); + } + + public void targetFinished( BuildEvent event ) + { + targetName = null; + } + + public void targetStarted( BuildEvent event ) + { + targetName = event.getTarget().getName(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/PathTokenizer.java b/proposal/myrmidon/src/main/org/apache/tools/ant/PathTokenizer.java new file mode 100644 index 000000000..c66d94a07 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/PathTokenizer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +/** + * A Path tokenizer takes a path and returns the components that make up that + * path. The path can use path separators of either ':' or ';' and file + * separators of either '/' or '\' + * + * @author Conor MacNeill (conor@ieee.org) + */ +public class PathTokenizer +{ + + /** + * A String which stores any path components which have been read ahead. + */ + private String lookahead = null; + + /** + * Flag to indicate whether we are running on a platform with a DOS style + * filesystem + */ + private boolean dosStyleFilesystem; + /** + * A tokenizer to break the string up based on the ':' or ';' separators. + */ + private StringTokenizer tokenizer; + + public PathTokenizer( String path ) + { + tokenizer = new StringTokenizer( path, ":;", false ); + dosStyleFilesystem = File.pathSeparatorChar == ';'; + } + + public boolean hasMoreTokens() + { + if( lookahead != null ) + { + return true; + } + + return tokenizer.hasMoreTokens(); + } + + public String nextToken() + throws NoSuchElementException + { + String token = null; + if( lookahead != null ) + { + token = lookahead; + lookahead = null; + } + else + { + token = tokenizer.nextToken().trim(); + } + + if( token.length() == 1 && Character.isLetter( token.charAt( 0 ) ) + && dosStyleFilesystem + && tokenizer.hasMoreTokens() ) + { + // we are on a dos style system so this path could be a drive + // spec. We look at the next token + String nextToken = tokenizer.nextToken().trim(); + if( nextToken.startsWith( "\\" ) || nextToken.startsWith( "/" ) ) + { + // we know we are on a DOS style platform and the next path starts with a + // slash or backslash, so we know this is a drive spec + token += ":" + nextToken; + } + else + { + // store the token just read for next time + lookahead = nextToken; + } + } + + return token; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Project.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Project.java new file mode 100644 index 000000000..c509b8795 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Project.java @@ -0,0 +1,1575 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Modifier; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Stack; +import java.util.Vector; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; +import org.apache.tools.ant.util.FileUtils; + +/** + * Central representation of an Ant project. This class defines a Ant project + * with all of it's targets and tasks. It also provides the mechanism to kick + * off a build using a particular target name.

+ * + * This class also encapsulates methods which allow Files to be refered to using + * abstract path names which are translated to native system file paths at + * runtime as well as defining various project properties. + * + * @author duncan@x180.com + */ + +public class Project +{ + + public final static int MSG_ERR = 0; + public final static int MSG_WARN = 1; + public final static int MSG_INFO = 2; + public final static int MSG_VERBOSE = 3; + public final static int MSG_DEBUG = 4; + + // private set of constants to represent the state + // of a DFS of the Target dependencies + private final static String VISITING = "VISITING"; + private final static String VISITED = "VISITED"; + + public final static String JAVA_1_0 = "1.0"; + public final static String JAVA_1_1 = "1.1"; + public final static String JAVA_1_2 = "1.2"; + public final static String JAVA_1_3 = "1.3"; + public final static String JAVA_1_4 = "1.4"; + + public final static String TOKEN_START = FilterSet.DEFAULT_TOKEN_START; + public final static String TOKEN_END = FilterSet.DEFAULT_TOKEN_END; + + private static String javaVersion; + + private Hashtable properties = new Hashtable(); + private Hashtable userProperties = new Hashtable(); + private Hashtable references = new Hashtable(); + private Hashtable dataClassDefinitions = new Hashtable(); + private Hashtable taskClassDefinitions = new Hashtable(); + private Hashtable createdTasks = new Hashtable(); + private Hashtable targets = new Hashtable(); + private FilterSet globalFilterSet = new FilterSet(); + private FilterSetCollection globalFilters = new FilterSetCollection( globalFilterSet ); + + private Vector listeners = new Vector(); + + /** + * The Ant core classloader - may be null if using system loader + */ + private ClassLoader coreLoader = null; + + /** + * Records the latest task on a thread + */ + private Hashtable threadTasks = new Hashtable(); + private File baseDir; + private String defaultTarget; + private String description; + + private FileUtils fileUtils; + + private String name; + + static + { + + // Determine the Java version by looking at available classes + // java.lang.StrictMath was introduced in JDK 1.3 + // java.lang.ThreadLocal was introduced in JDK 1.2 + // java.lang.Void was introduced in JDK 1.1 + // Count up version until a NoClassDefFoundError ends the try + + try + { + javaVersion = JAVA_1_0; + Class.forName( "java.lang.Void" ); + javaVersion = JAVA_1_1; + Class.forName( "java.lang.ThreadLocal" ); + javaVersion = JAVA_1_2; + Class.forName( "java.lang.StrictMath" ); + javaVersion = JAVA_1_3; + Class.forName( "java.lang.CharSequence" ); + javaVersion = JAVA_1_4; + } + catch( ClassNotFoundException cnfe ) + { + // swallow as we've hit the max class version that + // we have + } + } + + /** + * create a new ant project + */ + public Project() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * static query of the java version + * + * @return something like "1.1" or "1.3" + */ + public static String getJavaVersion() + { + return javaVersion; + } + + /** + * returns the boolean equivalent of a string, which is considered true if + * either "on", "true", or "yes" is found, ignoring case. + * + * @param s Description of Parameter + * @return Description of the Returned Value + */ + public static boolean toBoolean( String s ) + { + return ( s.equalsIgnoreCase( "on" ) || + s.equalsIgnoreCase( "true" ) || + s.equalsIgnoreCase( "yes" ) ); + } + + /** + * Translate a path into its native (platform specific) format.

+ * + * This method uses the PathTokenizer class to separate the input path into + * its components. This handles DOS style paths in a relatively sensible + * way. The file separators are then converted to their platform specific + * versions. + * + * @param to_process the path to be converted + * @return the native version of to_process or an empty string if to_process + * is null or empty + */ + public static String translatePath( String to_process ) + { + if( to_process == null || to_process.length() == 0 ) + { + return ""; + } + + StringBuffer path = new StringBuffer( to_process.length() + 50 ); + PathTokenizer tokenizer = new PathTokenizer( to_process ); + while( tokenizer.hasMoreTokens() ) + { + String pathComponent = tokenizer.nextToken(); + pathComponent = pathComponent.replace( '/', File.separatorChar ); + pathComponent = pathComponent.replace( '\\', File.separatorChar ); + if( path.length() != 0 ) + { + path.append( File.pathSeparatorChar ); + } + path.append( pathComponent ); + } + + return path.toString(); + } + + private static BuildException makeCircularException( String end, Stack stk ) + { + StringBuffer sb = new StringBuffer( "Circular dependency: " ); + sb.append( end ); + String c; + do + { + c = ( String )stk.pop(); + sb.append( " <- " ); + sb.append( c ); + }while ( !c.equals( end ) ); + return new BuildException( new String( sb ) ); + } + + /** + * set the base directory; XML attribute. checks for the directory existing + * and being a directory type + * + * @param baseDir project base directory. + * @throws BuildException if the directory was invalid + */ + public void setBaseDir( File baseDir ) + throws BuildException + { + baseDir = fileUtils.normalize( baseDir.getAbsolutePath() ); + if( !baseDir.exists() ) + throw new BuildException( "Basedir " + baseDir.getAbsolutePath() + " does not exist" ); + if( !baseDir.isDirectory() ) + throw new BuildException( "Basedir " + baseDir.getAbsolutePath() + " is not a directory" ); + this.baseDir = baseDir; + setPropertyInternal( "basedir", this.baseDir.getPath() ); + String msg = "Project base dir set to: " + this.baseDir; + log( msg, MSG_VERBOSE ); + } + + /** + * match basedir attribute in xml + * + * @param baseD project base directory. + * @throws BuildException if the directory was invalid + */ + public void setBasedir( String baseD ) + throws BuildException + { + setBaseDir( new File( baseD ) ); + } + + public void setCoreLoader( ClassLoader coreLoader ) + { + this.coreLoader = coreLoader; + } + + + /** + * set the default target of the project XML attribute name. + * + * @param defaultTarget The new Default value + */ + public void setDefault( String defaultTarget ) + { + this.defaultTarget = defaultTarget; + } + + /** + * set the default target of the project + * + * @param defaultTarget The new DefaultTarget value + * @see #setDefault(String) + * @deprecated, use setDefault + */ + public void setDefaultTarget( String defaultTarget ) + { + this.defaultTarget = defaultTarget; + } + + /** + * set the project description + * + * @param description text + */ + public void setDescription( String description ) + { + this.description = description; + } + + /** + * Calls File.setLastModified(long time) in a Java 1.1 compatible way. + * + * @param file The new FileLastModified value + * @param time The new FileLastModified value + * @exception BuildException Description of Exception + * @deprecated + */ + public void setFileLastModified( File file, long time ) + throws BuildException + { + if( getJavaVersion() == JAVA_1_1 ) + { + log( "Cannot change the modification time of " + file + + " in JDK 1.1", Project.MSG_WARN ); + return; + } + fileUtils.setFileLastModified( file, time ); + log( "Setting modification time for " + file, MSG_VERBOSE ); + } + + /** + * set the ant.java.version property, also tests for unsupported JVM + * versions, prints the verbose log messages + * + * @throws BuildException if this Java version is not supported + */ + public void setJavaVersionProperty() + throws BuildException + { + setPropertyInternal( "ant.java.version", javaVersion ); + + // sanity check + if( javaVersion == JAVA_1_0 ) + { + throw new BuildException( "Ant cannot work on Java 1.0" ); + } + + log( "Detected Java version: " + javaVersion + " in: " + System.getProperty( "java.home" ), MSG_VERBOSE ); + + log( "Detected OS: " + System.getProperty( "os.name" ), MSG_VERBOSE ); + } + + /** + * ant xml property. Set the project name as an attribute of this class, and + * of the property ant.project.name + * + * @param name The new Name value + */ + public void setName( String name ) + { + setUserProperty( "ant.project.name", name ); + this.name = name; + } + + /** + * set a property. An existing property of the same name will not be + * overwritten. + * + * @param name name of property + * @param value new value of the property + * @since 1.5 + */ + public void setNewProperty( String name, String value ) + { + if( null != properties.get( name ) ) + { + log( "Override ignored for property " + name, MSG_VERBOSE ); + return; + } + log( "Setting project property: " + name + " -> " + + value, MSG_DEBUG ); + properties.put( name, value ); + } + + /** + * set a property. Any existing property of the same name is overwritten, + * unless it is a user property. + * + * @param name name of property + * @param value new value of the property + */ + public void setProperty( String name, String value ) + { + // command line properties take precedence + if( null != userProperties.get( name ) ) + { + log( "Override ignored for user property " + name, MSG_VERBOSE ); + return; + } + + if( null != properties.get( name ) ) + { + log( "Overriding previous definition of property " + name, + MSG_VERBOSE ); + } + + log( "Setting project property: " + name + " -> " + + value, MSG_DEBUG ); + properties.put( name, value ); + } + + /** + * turn all the system properties into ant properties. user properties still + * override these values + */ + public void setSystemProperties() + { + Properties systemP = System.getProperties(); + Enumeration e = systemP.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + String value = systemP.get( name ).toString(); + this.setPropertyInternal( name.toString(), value ); + } + } + + /** + * set a user property, which can not be overwritten by set/unset property + * calls + * + * @param name The new UserProperty value + * @param value The new UserProperty value + * @see #setProperty(String,String) + */ + public void setUserProperty( String name, String value ) + { + log( "Setting ro project property: " + name + " -> " + + value, MSG_DEBUG ); + userProperties.put( name, value ); + properties.put( name, value ); + } + + /** + * get the base directory of the project as a file object + * + * @return the base directory. If this is null, then the base dir is not + * valid + */ + public File getBaseDir() + { + if( baseDir == null ) + { + try + { + setBasedir( "." ); + } + catch( BuildException ex ) + { + ex.printStackTrace(); + } + } + return baseDir; + } + + public Vector getBuildListeners() + { + return listeners; + } + + public ClassLoader getCoreLoader() + { + return coreLoader; + } + + /** + * get the current task definition hashtable + * + * @return The DataTypeDefinitions value + */ + public Hashtable getDataTypeDefinitions() + { + return dataClassDefinitions; + } + + /** + * get the default target of the project + * + * @return default target or null + */ + public String getDefaultTarget() + { + return defaultTarget; + } + + /** + * get the project description + * + * @return description or null if no description has been set + */ + public String getDescription() + { + return description; + } + + /** + * @return The Filters value + * @deprecated + */ + public Hashtable getFilters() + { + // we need to build the hashtable dynamically + return globalFilterSet.getFilterHash(); + } + + + public FilterSet getGlobalFilterSet() + { + return globalFilterSet; + } + + /** + * get the project name + * + * @return name string + */ + public String getName() + { + return name; + } + + /** + * get a copy of the property hashtable + * + * @return the hashtable containing all properties, user included + */ + public Hashtable getProperties() + { + Hashtable propertiesCopy = new Hashtable(); + + Enumeration e = properties.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + Object value = properties.get( name ); + propertiesCopy.put( name, value ); + } + + return propertiesCopy; + } + + /** + * query a property. + * + * @param name the name of the property + * @return the property value, or null for no match + */ + public String getProperty( String name ) + { + if( name == null ) + return null; + String property = ( String )properties.get( name ); + return property; + } + + /** + * @param key Description of Parameter + * @return The object with the "id" key. + */ + public Object getReference( String key ) + { + return references.get( key ); + } + + public Hashtable getReferences() + { + return references; + } + + /** + * get the target hashtable + * + * @return hashtable, the contents of which can be cast to Target + */ + public Hashtable getTargets() + { + return targets; + } + + /** + * get the current task definition hashtable + * + * @return The TaskDefinitions value + */ + public Hashtable getTaskDefinitions() + { + return taskClassDefinitions; + } + + /** + * get a copy of the user property hashtable + * + * @return the hashtable user properties only + */ + public Hashtable getUserProperties() + { + Hashtable propertiesCopy = new Hashtable(); + + Enumeration e = userProperties.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + Object value = properties.get( name ); + propertiesCopy.put( name, value ); + } + + return propertiesCopy; + } + + /** + * query a user property. + * + * @param name the name of the property + * @return the property value, or null for no match + */ + public String getUserProperty( String name ) + { + if( name == null ) + return null; + String property = ( String )userProperties.get( name ); + return property; + } + + /** + * Topologically sort a set of Targets. + * + * @param root is the (String) name of the root Target. The sort is created + * in such a way that the sequence of Targets uptil the root target is + * the minimum possible such sequence. + * @param targets is a Hashtable representing a "name to Target" mapping + * @return a Vector of Strings with the names of the targets in sorted + * order. + * @exception BuildException if there is a cyclic dependency among the + * Targets, or if a Target does not exist. + */ + public final Vector topoSort( String root, Hashtable targets ) + throws BuildException + { + Vector ret = new Vector(); + Hashtable state = new Hashtable(); + Stack visiting = new Stack(); + + // We first run a DFS based sort using the root as the starting node. + // This creates the minimum sequence of Targets to the root node. + // We then do a sort on any remaining unVISITED targets. + // This is unnecessary for doing our build, but it catches + // circular dependencies or missing Targets on the entire + // dependency tree, not just on the Targets that depend on the + // build Target. + + tsort( root, targets, state, visiting, ret ); + log( "Build sequence for target `" + root + "' is " + ret, MSG_VERBOSE ); + for( Enumeration en = targets.keys(); en.hasMoreElements(); ) + { + String curTarget = ( String )( en.nextElement() ); + String st = ( String )state.get( curTarget ); + if( st == null ) + { + tsort( curTarget, targets, state, visiting, ret ); + } + else if( st == VISITING ) + { + throw new RuntimeException( "Unexpected node in visiting state: " + curTarget ); + } + } + log( "Complete build sequence is " + ret, MSG_VERBOSE ); + return ret; + } + + public void addBuildListener( BuildListener listener ) + { + listeners.addElement( listener ); + } + + /** + * add a new datatype + * + * @param typeName name of the datatype + * @param typeClass full datatype classname + */ + public void addDataTypeDefinition( String typeName, Class typeClass ) + { + if( null != dataClassDefinitions.get( typeName ) ) + { + log( "Trying to override old definition of datatype " + typeName, + MSG_WARN ); + } + + String msg = " +User datatype: " + typeName + " " + typeClass.getName(); + log( msg, MSG_DEBUG ); + dataClassDefinitions.put( typeName, typeClass ); + } + + /** + * @param token The feature to be added to the Filter attribute + * @param value The feature to be added to the Filter attribute + * @deprecated + */ + public void addFilter( String token, String value ) + { + if( token == null ) + { + return; + } + + globalFilterSet.addFilter( new FilterSet.Filter( token, value ) ); + } + + /** + * @param target is the Target to be added or replaced in the current + * Project. + */ + public void addOrReplaceTarget( Target target ) + { + addOrReplaceTarget( target.getName(), target ); + } + + /** + * @param target is the Target to be added/replaced in the current Project. + * @param targetName is the name to use for the Target + */ + public void addOrReplaceTarget( String targetName, Target target ) + { + String msg = " +Target: " + targetName; + log( msg, MSG_DEBUG ); + target.setProject( this ); + targets.put( targetName, target ); + } + + public void addReference( String name, Object value ) + { + if( null != references.get( name ) ) + { + log( "Overriding previous definition of reference to " + name, + MSG_WARN ); + } + log( "Adding reference: " + name + " -> " + value, MSG_DEBUG ); + references.put( name, value ); + } + + /** + * This call expects to add a new Target. + * + * @param target is the Target to be added to the current Project. + * @see Project#addOrReplaceTarget to replace existing Targets. + */ + public void addTarget( Target target ) + { + String name = target.getName(); + if( targets.get( name ) != null ) + { + throw new BuildException( "Duplicate target: `" + name + "'" ); + } + addOrReplaceTarget( name, target ); + } + + /** + * This call expects to add a new Target. + * + * @param target is the Target to be added to the current Project. + * @param targetName is the name to use for the Target + * @exception BuildException if the Target already exists in the project. + * @see Project#addOrReplaceTarget to replace existing Targets. + */ + public void addTarget( String targetName, Target target ) + throws BuildException + { + if( targets.get( targetName ) != null ) + { + throw new BuildException( "Duplicate target: `" + targetName + "'" ); + } + addOrReplaceTarget( targetName, target ); + } + + /** + * add a new task definition, complain if there is an overwrite attempt + * + * @param taskName name of the task + * @param taskClass full task classname + * @throws BuildException and logs as Project.MSG_ERR for conditions, that + * will cause the task execution to fail. + */ + public void addTaskDefinition( String taskName, Class taskClass ) + throws BuildException + { + Class old = ( Class )taskClassDefinitions.get( taskName ); + if( null != old ) + { + if( old.equals( taskClass ) ) + { + log( "Ignoring override for task " + taskName + + ", it is already defined by the same class.", + MSG_VERBOSE ); + return; + } + else + { + log( "Trying to override old definition of task " + taskName, + MSG_WARN ); + invalidateCreatedTasks( taskName ); + } + } + + String msg = " +User task: " + taskName + " " + taskClass.getName(); + log( msg, MSG_DEBUG ); + checkTaskClass( taskClass ); + taskClassDefinitions.put( taskName, taskClass ); + } + + /** + * Checks a class, whether it is suitable for serving as ant task. + * + * @param taskClass Description of Parameter + * @throws BuildException and logs as Project.MSG_ERR for conditions, that + * will cause the task execution to fail. + */ + public void checkTaskClass( final Class taskClass ) + throws BuildException + { + if( !Modifier.isPublic( taskClass.getModifiers() ) ) + { + final String message = taskClass + " is not public"; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + if( Modifier.isAbstract( taskClass.getModifiers() ) ) + { + final String message = taskClass + " is abstract"; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + try + { + taskClass.getConstructor( null ); + // don't have to check for public, since + // getConstructor finds public constructors only. + } + catch( NoSuchMethodException e ) + { + final String message = "No public default constructor in " + taskClass; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + if( !Task.class.isAssignableFrom( taskClass ) ) + TaskAdapter.checkTaskClass( taskClass, this ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering, + boolean overwrite ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, overwrite ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, + overwrite, preserveLastModified ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering, + boolean overwrite ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, overwrite ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, + overwrite, preserveLastModified ); + } + + /** + * create a new DataType instance + * + * @param typeName name of the datatype + * @return null if the datatype name is unknown + * @throws BuildException when datatype creation goes bad + */ + public Object createDataType( String typeName ) + throws BuildException + { + Class c = ( Class )dataClassDefinitions.get( typeName ); + + if( c == null ) + return null; + + try + { + java.lang.reflect.Constructor ctor = null; + boolean noArg = false; + // DataType can have a "no arg" constructor or take a single + // Project argument. + try + { + ctor = c.getConstructor( new Class[0] ); + noArg = true; + } + catch( NoSuchMethodException nse ) + { + ctor = c.getConstructor( new Class[]{Project.class} ); + noArg = false; + } + + Object o = null; + if( noArg ) + { + o = ctor.newInstance( new Object[0] ); + } + else + { + o = ctor.newInstance( new Object[]{this} ); + } + if( o instanceof ProjectComponent ) + { + ( ( ProjectComponent )o ).setProject( this ); + } + String msg = " +DataType: " + typeName; + log( msg, MSG_DEBUG ); + return o; + } + catch( java.lang.reflect.InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + String msg = "Could not create datatype of type: " + + typeName + " due to " + t; + throw new BuildException( msg, t ); + } + catch( Throwable t ) + { + String msg = "Could not create datatype of type: " + + typeName + " due to " + t; + throw new BuildException( msg, t ); + } + } + + /** + * create a new task instance + * + * @param taskType name of the task + * @return null if the task name is unknown + * @throws BuildException when task creation goes bad + */ + public Task createTask( String taskType ) + throws BuildException + { + Class c = ( Class )taskClassDefinitions.get( taskType ); + + if( c == null ) + return null; + try + { + Object o = c.newInstance(); + Task task = null; + if( o instanceof Task ) + { + task = ( Task )o; + } + else + { + // "Generic" Bean - use the setter pattern + // and an Adapter + TaskAdapter taskA = new TaskAdapter(); + taskA.setProxy( o ); + task = taskA; + } + task.setProject( this ); + task.setTaskType( taskType ); + + // set default value, can be changed by the user + task.setTaskName( taskType ); + + String msg = " +Task: " + taskType; + log( msg, MSG_DEBUG ); + addCreatedTask( taskType, task ); + return task; + } + catch( Throwable t ) + { + String msg = "Could not create task of type: " + + taskType + " due to " + t; + throw new BuildException( msg, t ); + } + } + + public void demuxOutput( String line, boolean isError ) + { + Task task = ( Task )threadTasks.get( Thread.currentThread() ); + if( task == null ) + { + fireMessageLogged( this, line, isError ? MSG_ERR : MSG_INFO ); + } + else + { + if( isError ) + { + task.handleErrorOutput( line ); + } + else + { + task.handleOutput( line ); + } + } + } + + /** + * execute the targets and any targets it depends on + * + * @param targetName the target to execute + * @throws BuildException if the build failed + */ + public void executeTarget( String targetName ) + throws BuildException + { + + // sanity check ourselves, if we've been asked to build nothing + // then we should complain + + if( targetName == null ) + { + String msg = "No target specified"; + throw new BuildException( msg ); + } + + // Sort the dependency tree, and run everything from the + // beginning until we hit our targetName. + // Sorting checks if all the targets (and dependencies) + // exist, and if there is any cycle in the dependency + // graph. + Vector sortedTargets = topoSort( targetName, targets ); + + int curidx = 0; + Target curtarget; + + do + { + curtarget = ( Target )sortedTargets.elementAt( curidx++ ); + curtarget.performTasks(); + }while ( !curtarget.getName().equals( targetName ) ); + } + + /** + * execute the sequence of targets, and the targets they depend on + * + * @param targetNames Description of Parameter + * @throws BuildException if the build failed + */ + public void executeTargets( Vector targetNames ) + throws BuildException + { + Throwable error = null; + + for( int i = 0; i < targetNames.size(); i++ ) + { + executeTarget( ( String )targetNames.elementAt( i ) ); + } + } + + /** + * Initialise the project. This involves setting the default task + * definitions and loading the system properties. + * + * @exception BuildException Description of Exception + */ + public void init() + throws BuildException + { + setJavaVersionProperty(); + + String defs = "/org/apache/tools/ant/taskdefs/defaults.properties"; + + try + { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream( defs ); + if( in == null ) + { + throw new BuildException( "Can't load default task list" ); + } + props.load( in ); + in.close(); + + Enumeration enum = props.propertyNames(); + while( enum.hasMoreElements() ) + { + String key = ( String )enum.nextElement(); + String value = props.getProperty( key ); + try + { + Class taskClass = Class.forName( value ); + addTaskDefinition( key, taskClass ); + } + catch( NoClassDefFoundError ncdfe ) + { + // ignore... + } + catch( ClassNotFoundException cnfe ) + { + // ignore... + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Can't load default task list" ); + } + + String dataDefs = "/org/apache/tools/ant/types/defaults.properties"; + + try + { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream( dataDefs ); + if( in == null ) + { + throw new BuildException( "Can't load default datatype list" ); + } + props.load( in ); + in.close(); + + Enumeration enum = props.propertyNames(); + while( enum.hasMoreElements() ) + { + String key = ( String )enum.nextElement(); + String value = props.getProperty( key ); + try + { + Class dataClass = Class.forName( value ); + addDataTypeDefinition( key, dataClass ); + } + catch( NoClassDefFoundError ncdfe ) + { + // ignore... + } + catch( ClassNotFoundException cnfe ) + { + // ignore... + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Can't load default datatype list" ); + } + + setSystemProperties(); + } + + /** + * Output a message to the log with the default log level of MSG_INFO + * + * @param msg text to log + */ + + public void log( String msg ) + { + log( msg, MSG_INFO ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of project + * + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( String msg, int msgLevel ) + { + fireMessageLogged( this, msg, msgLevel ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of a task + * + * @param task task to use in the log + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( Task task, String msg, int msgLevel ) + { + fireMessageLogged( task, msg, msgLevel ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of a target + * + * @param target target to use in the log + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( Target target, String msg, int msgLevel ) + { + fireMessageLogged( target, msg, msgLevel ); + } + + public void removeBuildListener( BuildListener listener ) + { + listeners.removeElement( listener ); + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public String replaceProperties( String value ) + throws BuildException + { + return ProjectHelper.replaceProperties( this, value ); + } + + /** + * Return the canonical form of fileName as an absolute path.

+ * + * If fileName is a relative file name, resolve it relative to rootDir.

+ * + * @param fileName Description of Parameter + * @param rootDir Description of Parameter + * @return Description of the Returned Value + * @deprecated + */ + public File resolveFile( String fileName, File rootDir ) + { + return fileUtils.resolveFile( rootDir, fileName ); + } + + public File resolveFile( String fileName ) + { + return fileUtils.resolveFile( baseDir, fileName ); + } + + /** + * send build finished event to the listeners + * + * @param exception exception which indicates failure if not null + */ + protected void fireBuildFinished( Throwable exception ) + { + BuildEvent event = new BuildEvent( this ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.buildFinished( event ); + } + } + + /** + * send build started event to the listeners + */ + protected void fireBuildStarted() + { + BuildEvent event = new BuildEvent( this ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.buildStarted( event ); + } + } + + protected void fireMessageLogged( Project project, String message, int priority ) + { + BuildEvent event = new BuildEvent( project ); + fireMessageLoggedEvent( event, message, priority ); + } + + protected void fireMessageLogged( Target target, String message, int priority ) + { + BuildEvent event = new BuildEvent( target ); + fireMessageLoggedEvent( event, message, priority ); + } + + protected void fireMessageLogged( Task task, String message, int priority ) + { + BuildEvent event = new BuildEvent( task ); + fireMessageLoggedEvent( event, message, priority ); + } + + /** + * send build finished event to the listeners + * + * @param exception exception which indicates failure if not null + * @param target Description of Parameter + */ + protected void fireTargetFinished( Target target, Throwable exception ) + { + BuildEvent event = new BuildEvent( target ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.targetFinished( event ); + } + } + + + /** + * send target started event to the listeners + * + * @param target Description of Parameter + */ + protected void fireTargetStarted( Target target ) + { + BuildEvent event = new BuildEvent( target ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.targetStarted( event ); + } + } + + protected void fireTaskFinished( Task task, Throwable exception ) + { + threadTasks.remove( Thread.currentThread() ); + System.out.flush(); + System.err.flush(); + BuildEvent event = new BuildEvent( task ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.taskFinished( event ); + } + } + + protected void fireTaskStarted( Task task ) + { + // register this as the current task on the current thread. + threadTasks.put( Thread.currentThread(), task ); + BuildEvent event = new BuildEvent( task ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.taskStarted( event ); + } + } + + /** + * Allows Project and subclasses to set a property unless its already + * defined as a user property. There are a few cases internally to Project + * that need to do this currently. + * + * @param name The new PropertyInternal value + * @param value The new PropertyInternal value + */ + private void setPropertyInternal( String name, String value ) + { + if( null != userProperties.get( name ) ) + { + return; + } + properties.put( name, value ); + } + + // one step in a recursive DFS traversal of the Target dependency tree. + // - The Hashtable "state" contains the state (VISITED or VISITING or null) + // of all the target names. + // - The Stack "visiting" contains a stack of target names that are + // currently on the DFS stack. (NB: the target names in "visiting" are + // exactly the target names in "state" that are in the VISITING state.) + // 1. Set the current target to the VISITING state, and push it onto + // the "visiting" stack. + // 2. Throw a BuildException if any child of the current node is + // in the VISITING state (implies there is a cycle.) It uses the + // "visiting" Stack to construct the cycle. + // 3. If any children have not been VISITED, tsort() the child. + // 4. Add the current target to the Vector "ret" after the children + // have been visited. Move the current target to the VISITED state. + // "ret" now contains the sorted sequence of Targets upto the current + // Target. + + private final void tsort( String root, Hashtable targets, + Hashtable state, Stack visiting, + Vector ret ) + throws BuildException + { + state.put( root, VISITING ); + visiting.push( root ); + + Target target = ( Target )( targets.get( root ) ); + + // Make sure we exist + if( target == null ) + { + StringBuffer sb = new StringBuffer( "Target `" ); + sb.append( root ); + sb.append( "' does not exist in this project. " ); + visiting.pop(); + if( !visiting.empty() ) + { + String parent = ( String )visiting.peek(); + sb.append( "It is used from target `" ); + sb.append( parent ); + sb.append( "'." ); + } + + throw new BuildException( new String( sb ) ); + } + + for( Enumeration en = target.getDependencies(); en.hasMoreElements(); ) + { + String cur = ( String )en.nextElement(); + String m = ( String )state.get( cur ); + if( m == null ) + { + // Not been visited + tsort( cur, targets, state, visiting, ret ); + } + else if( m == VISITING ) + { + // Currently visiting this node, so have a cycle + throw makeCircularException( cur, visiting ); + } + } + + String p = ( String )visiting.pop(); + if( root != p ) + { + throw new RuntimeException( "Unexpected internal error: expected to pop " + root + " but got " + p ); + } + state.put( root, VISITED ); + ret.addElement( target ); + } + + /** + * Keep a record of all tasks that have been created so that they can be + * invalidated if a taskdef overrides the definition. + * + * @param type The feature to be added to the CreatedTask attribute + * @param task The feature to be added to the CreatedTask attribute + */ + private void addCreatedTask( String type, Task task ) + { + synchronized( createdTasks ) + { + Vector v = ( Vector )createdTasks.get( type ); + if( v == null ) + { + v = new Vector(); + createdTasks.put( type, v ); + } + v.addElement( task ); + } + } + + private void fireMessageLoggedEvent( BuildEvent event, String message, int priority ) + { + event.setMessage( message, priority ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.messageLogged( event ); + } + } + + /** + * Mark tasks as invalid which no longer are of the correct type for a given + * taskname. + * + * @param type Description of Parameter + */ + private void invalidateCreatedTasks( String type ) + { + synchronized( createdTasks ) + { + Vector v = ( Vector )createdTasks.get( type ); + if( v != null ) + { + Enumeration enum = v.elements(); + while( enum.hasMoreElements() ) + { + Task t = ( Task )enum.nextElement(); + t.markInvalid(); + } + v.removeAllElements(); + createdTasks.remove( type ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectComponent.java b/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectComponent.java new file mode 100644 index 000000000..c7ddf7f96 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectComponent.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.AbstractTask; + +/** + * Base class for components of a project, including tasks and data types. + * Provides common facilities. + * + * @author Conor MacNeill + */ + +public abstract class ProjectComponent + extends AbstractTask +{ + protected Project project = null; + + /** + * Sets the project object of this component. This method is used by project + * when a component is added to it so that the component has access to the + * functions of the project. It should not be used for any other purpose. + * + * @param project Project in whose scope this component belongs. + */ + public void setProject( Project project ) + { + this.project = project; + } + + /** + * Get the Project to which this component belongs + * + * @return the components's project. + */ + public Project getProject() + { + return project; + } + + /** + * Log a message with the default (INFO) priority. + * + * @param msg Description of Parameter + */ + public void log( String msg ) + { + log( msg, Project.MSG_INFO ); + } + + /** + * Log a mesage with the give priority. + * + * @param msgLevel the message priority at which this message is to be + * logged. + * @param msg Description of Parameter + */ + public void log( String msg, int msgLevel ) + { + if( project != null ) + { + project.log( msg, msgLevel ); + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectHelper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectHelper.java new file mode 100644 index 000000000..896a0d8f8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectHelper.java @@ -0,0 +1,1061 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Vector; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.AttributeList; +import org.xml.sax.DocumentHandler; +import org.xml.sax.HandlerBase; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * Configures a Project (complete with Targets and Tasks) based on a XML build + * file. + * + * @author duncan@x180.com + */ + +public class ProjectHelper +{ + + private static SAXParserFactory parserFactory = null; + private File buildFile; + private File buildFileParent; + private Locator locator; + + private org.xml.sax.Parser parser; + private Project project; + + /** + * Constructs a new Ant parser for the specified XML file. + * + * @param project Description of Parameter + * @param buildFile Description of Parameter + */ + private ProjectHelper( Project project, File buildFile ) + { + this.project = project; + this.buildFile = new File( buildFile.getAbsolutePath() ); + buildFileParent = new File( this.buildFile.getParent() ); + } + + /** + * Adds the content of #PCDATA sections to an element. + * + * @param project The feature to be added to the Text attribute + * @param target The feature to be added to the Text attribute + * @param buf The feature to be added to the Text attribute + * @param start The feature to be added to the Text attribute + * @param end The feature to be added to the Text attribute + * @exception BuildException Description of Exception + */ + public static void addText( Project project, Object target, char[] buf, int start, int end ) + throws BuildException + { + addText( project, target, new String( buf, start, end ) ); + } + + /** + * Adds the content of #PCDATA sections to an element. + * + * @param project The feature to be added to the Text attribute + * @param target The feature to be added to the Text attribute + * @param text The feature to be added to the Text attribute + * @exception BuildException Description of Exception + */ + public static void addText( Project project, Object target, String text ) + throws BuildException + { + + if( text == null || text.trim().length() == 0 ) + { + return; + } + + if( target instanceof TaskAdapter ) + target = ( ( TaskAdapter )target ).getProxy(); + + IntrospectionHelper.getHelper( target.getClass() ).addText( project, target, text ); + } + + public static void configure( Object target, AttributeList attrs, + Project project ) + throws BuildException + { + if( target instanceof TaskAdapter ) + target = ( ( TaskAdapter )target ).getProxy(); + + IntrospectionHelper ih = + IntrospectionHelper.getHelper( target.getClass() ); + + project.addBuildListener( ih ); + + for( int i = 0; i < attrs.getLength(); i++ ) + { + // reflect these into the target + String value = replaceProperties( project, attrs.getValue( i ), + project.getProperties() ); + try + { + ih.setAttribute( project, target, + attrs.getName( i ).toLowerCase( Locale.US ), value ); + + } + catch( BuildException be ) + { + // id attribute must be set externally + if( !attrs.getName( i ).equals( "id" ) ) + { + throw be; + } + } + } + } + + /** + * Configures the Project with the contents of the specified XML file. + * + * @param project Description of Parameter + * @param buildFile Description of Parameter + * @exception BuildException Description of Exception + */ + public static void configureProject( Project project, File buildFile ) + throws BuildException + { + new ProjectHelper( project, buildFile ).parse(); + } + + /** + * This method will parse a string containing ${value} style property values + * into two lists. The first list is a collection of text fragments, while + * the other is a set of string property names null entries in the first + * list indicate a property reference from the second list. + * + * @param value Description of Parameter + * @param fragments Description of Parameter + * @param propertyRefs Description of Parameter + * @exception BuildException Description of Exception + */ + public static void parsePropertyString( String value, Vector fragments, Vector propertyRefs ) + throws BuildException + { + int prev = 0; + int pos; + while( ( pos = value.indexOf( "$", prev ) ) >= 0 ) + { + if( pos > 0 ) + { + fragments.addElement( value.substring( prev, pos ) ); + } + + if( pos == ( value.length() - 1 ) ) + { + fragments.addElement( "$" ); + prev = pos + 1; + } + else if( value.charAt( pos + 1 ) != '{' ) + { + fragments.addElement( value.substring( pos + 1, pos + 2 ) ); + prev = pos + 2; + } + else + { + int endName = value.indexOf( '}', pos ); + if( endName < 0 ) + { + throw new BuildException( "Syntax error in property: " + + value ); + } + String propertyName = value.substring( pos + 2, endName ); + fragments.addElement( null ); + propertyRefs.addElement( propertyName ); + prev = endName + 1; + } + } + + if( prev < value.length() ) + { + fragments.addElement( value.substring( prev ) ); + } + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @param project Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @since 1.5 + */ + public static String replaceProperties( Project project, String value ) + throws BuildException + { + return replaceProperties( project, value, project.getProperties() ); + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @param project Description of Parameter + * @param keys Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public static String replaceProperties( Project project, String value, Hashtable keys ) + throws BuildException + { + if( value == null ) + { + return null; + } + + Vector fragments = new Vector(); + Vector propertyRefs = new Vector(); + parsePropertyString( value, fragments, propertyRefs ); + + StringBuffer sb = new StringBuffer(); + Enumeration i = fragments.elements(); + Enumeration j = propertyRefs.elements(); + while( i.hasMoreElements() ) + { + String fragment = ( String )i.nextElement(); + if( fragment == null ) + { + String propertyName = ( String )j.nextElement(); + if( !keys.containsKey( propertyName ) ) + { + project.log( "Property ${" + propertyName + "} has not been set", Project.MSG_VERBOSE ); + } + fragment = ( keys.containsKey( propertyName ) ) ? ( String )keys.get( propertyName ) + : "${" + propertyName + "}"; + } + sb.append( fragment ); + } + + return sb.toString(); + } + + /** + * Stores a configured child element into its parent object + * + * @param project Description of Parameter + * @param parent Description of Parameter + * @param child Description of Parameter + * @param tag Description of Parameter + */ + public static void storeChild( Project project, Object parent, Object child, String tag ) + { + IntrospectionHelper ih = IntrospectionHelper.getHelper( parent.getClass() ); + ih.storeElement( project, parent, child, tag ); + } + + private static SAXParserFactory getParserFactory() + { + if( parserFactory == null ) + { + parserFactory = SAXParserFactory.newInstance(); + } + + return parserFactory; + } + + /** + * Scan AttributeList for the id attribute and maybe add a reference to + * project.

+ * + * Moved out of {@link #configure configure} to make it happen at parser + * time.

+ * + * @param target Description of Parameter + * @param attr Description of Parameter + */ + private void configureId( Object target, AttributeList attr ) + { + String id = attr.getValue( "id" ); + if( id != null ) + { + project.addReference( id, target ); + } + } + + /** + * Parses the project file. + * + * @exception BuildException Description of Exception + */ + private void parse() + throws BuildException + { + FileInputStream inputStream = null; + InputSource inputSource = null; + + try + { + SAXParser saxParser = getParserFactory().newSAXParser(); + parser = saxParser.getParser(); + + String uri = "file:" + buildFile.getAbsolutePath().replace( '\\', '/' ); + for( int index = uri.indexOf( '#' ); index != -1; index = uri.indexOf( '#' ) ) + { + uri = uri.substring( 0, index ) + "%23" + uri.substring( index + 1 ); + } + + inputStream = new FileInputStream( buildFile ); + inputSource = new InputSource( inputStream ); + inputSource.setSystemId( uri ); + project.log( "parsing buildfile " + buildFile + " with URI = " + uri, Project.MSG_VERBOSE ); + saxParser.parse( inputSource, new RootHandler() ); + } + catch( ParserConfigurationException exc ) + { + throw new BuildException( "Parser has not been configured correctly", exc ); + } + catch( SAXParseException exc ) + { + Location location = + new Location( buildFile.toString(), exc.getLineNumber(), exc.getColumnNumber() ); + + Throwable t = exc.getException(); + if( t instanceof BuildException ) + { + BuildException be = ( BuildException )t; + if( be.getLocation() == Location.UNKNOWN_LOCATION ) + { + be.setLocation( location ); + } + throw be; + } + + throw new BuildException( exc.getMessage(), t, location ); + } + catch( SAXException exc ) + { + Throwable t = exc.getException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( exc.getMessage(), t ); + } + catch( FileNotFoundException exc ) + { + throw new BuildException( exc ); + } + catch( IOException exc ) + { + throw new BuildException( "Error reading project file", exc ); + } + finally + { + if( inputStream != null ) + { + try + { + inputStream.close(); + } + catch( IOException ioe ) + { + // ignore this + } + } + } + } + + /** + * The common superclass for all sax event handlers in Ant. Basically throws + * an exception in each method, so subclasses should override what they can + * handle. Each type of xml element (task, target, etc) in ant will have its + * own subclass of AbstractHandler. In the constructor, this class takes + * over the handling of sax events from the parent handler, and returns + * control back to the parent in the endElement method. + * + * @author RT + */ + private class AbstractHandler extends HandlerBase + { + protected DocumentHandler parentHandler; + + public AbstractHandler( DocumentHandler parentHandler ) + { + this.parentHandler = parentHandler; + + // Start handling SAX events + parser.setDocumentHandler( this ); + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + String s = new String( buf, start, end ).trim(); + + if( s.length() > 0 ) + { + throw new SAXParseException( "Unexpected text \"" + s + "\"", locator ); + } + } + + public void endElement( String name ) + throws SAXException + { + + finished(); + // Let parent resume handling SAX events + parser.setDocumentHandler( parentHandler ); + } + + public void startElement( String tag, AttributeList attrs ) + throws SAXParseException + { + throw new SAXParseException( "Unexpected element \"" + tag + "\"", locator ); + } + + /** + * Called when this element and all elements nested into it have been + * handled. + */ + protected void finished() { } + } + + /** + * Handler for all data types at global level. + * + * @author RT + */ + private class DataTypeHandler extends AbstractHandler + { + private RuntimeConfigurable wrapper = null; + private Object element; + private Target target; + + public DataTypeHandler( DocumentHandler parentHandler ) + { + this( parentHandler, null ); + } + + public DataTypeHandler( DocumentHandler parentHandler, Target target ) + { + super( parentHandler ); + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + try + { + addText( project, element, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void init( String propType, AttributeList attrs ) + throws SAXParseException + { + try + { + element = project.createDataType( propType ); + if( element == null ) + { + throw new BuildException( "Unknown data type " + propType ); + } + + if( target != null ) + { + wrapper = new RuntimeConfigurable( element, propType ); + wrapper.setAttributes( attrs ); + target.addDataType( wrapper ); + } + else + { + configure( element, attrs, project ); + configureId( element, attrs ); + } + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + new NestedElementHandler( this, element, wrapper, target ).init( name, attrs ); + } + } + + /** + * Handler for all nested properties. + * + * @author RT + */ + private class NestedElementHandler extends AbstractHandler + { + private RuntimeConfigurable childWrapper = null; + private Object child; + private Object parent; + private RuntimeConfigurable parentWrapper; + private Target target; + + public NestedElementHandler( DocumentHandler parentHandler, + Object parent, + RuntimeConfigurable parentWrapper, + Target target ) + { + super( parentHandler ); + + if( parent instanceof TaskAdapter ) + { + this.parent = ( ( TaskAdapter )parent ).getProxy(); + } + else + { + this.parent = parent; + } + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + if( parentWrapper == null ) + { + try + { + addText( project, child, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + else + { + childWrapper.addText( buf, start, end ); + } + } + + public void init( String propType, AttributeList attrs ) + throws SAXParseException + { + Class parentClass = parent.getClass(); + IntrospectionHelper ih = + IntrospectionHelper.getHelper( parentClass ); + + try + { + String elementName = propType.toLowerCase( Locale.US ); + if( parent instanceof UnknownElement ) + { + UnknownElement uc = new UnknownElement( elementName ); + uc.setProject( project ); + ( ( UnknownElement )parent ).addChild( uc ); + child = uc; + } + else + { + child = ih.createElement( project, parent, elementName ); + } + + configureId( child, attrs ); + + if( parentWrapper != null ) + { + childWrapper = new RuntimeConfigurable( child, propType ); + childWrapper.setAttributes( attrs ); + parentWrapper.addChild( childWrapper ); + } + else + { + configure( child, attrs, project ); + ih.storeElement( project, parent, child, elementName ); + } + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( child instanceof TaskContainer ) + { + // taskcontainer nested element can contain other tasks - no other + // nested elements possible + new TaskHandler( this, ( TaskContainer )child, childWrapper, target ).init( name, attrs ); + } + else + { + new NestedElementHandler( this, child, childWrapper, target ).init( name, attrs ); + } + } + } + + /** + * Handler for the top level "project" element. + * + * @author RT + */ + private class ProjectHandler extends AbstractHandler + { + public ProjectHandler( DocumentHandler parentHandler ) + { + super( parentHandler ); + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + String def = null; + String name = null; + String id = null; + String baseDir = null; + + for( int i = 0; i < attrs.getLength(); i++ ) + { + String key = attrs.getName( i ); + String value = attrs.getValue( i ); + + if( key.equals( "default" ) ) + { + def = value; + } + else if( key.equals( "name" ) ) + { + name = value; + } + else if( key.equals( "id" ) ) + { + id = value; + } + else if( key.equals( "basedir" ) ) + { + baseDir = value; + } + else + { + throw new SAXParseException( "Unexpected attribute \"" + attrs.getName( i ) + "\"", locator ); + } + } + + if( def == null ) + { + throw new SAXParseException( "The default attribute of project is required", + locator ); + } + + project.setDefaultTarget( def ); + + if( name != null ) + { + project.setName( name ); + project.addReference( name, project ); + } + + if( id != null ) + project.addReference( id, project ); + + if( project.getProperty( "basedir" ) != null ) + { + project.setBasedir( project.getProperty( "basedir" ) ); + } + else + { + if( baseDir == null ) + { + project.setBasedir( buildFileParent.getAbsolutePath() ); + } + else + { + // check whether the user has specified an absolute path + if( ( new File( baseDir ) ).isAbsolute() ) + { + project.setBasedir( baseDir ); + } + else + { + project.setBaseDir( project.resolveFile( baseDir, buildFileParent ) ); + } + } + } + + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( name.equals( "taskdef" ) ) + { + handleTaskdef( name, attrs ); + } + else if( name.equals( "typedef" ) ) + { + handleTypedef( name, attrs ); + } + else if( name.equals( "property" ) ) + { + handleProperty( name, attrs ); + } + else if( name.equals( "target" ) ) + { + handleTarget( name, attrs ); + } + else if( project.getDataTypeDefinitions().get( name ) != null ) + { + handleDataType( name, attrs ); + } + else + { + throw new SAXParseException( "Unexpected element \"" + name + "\"", locator ); + } + } + + private void handleDataType( String name, AttributeList attrs ) + throws SAXParseException + { + new DataTypeHandler( this ).init( name, attrs ); + } + + private void handleProperty( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + private void handleTarget( String tag, AttributeList attrs ) + throws SAXParseException + { + new TargetHandler( this ).init( tag, attrs ); + } + + private void handleTaskdef( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + private void handleTypedef( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + } + + /** + * Handler for the root element. It's only child must be the "project" + * element. + * + * @author RT + */ + private class RootHandler extends HandlerBase + { + + public void setDocumentLocator( Locator locator ) + { + ProjectHelper.this.locator = locator; + } + + /** + * resolve file: URIs as relative to the build file. + * + * @param publicId Description of Parameter + * @param systemId Description of Parameter + * @return Description of the Returned Value + */ + public InputSource resolveEntity( String publicId, + String systemId ) + { + + project.log( "resolving systemId: " + systemId, Project.MSG_VERBOSE ); + + if( systemId.startsWith( "file:" ) ) + { + String path = systemId.substring( 5 ); + int index = path.indexOf( "file:" ); + + // we only have to handle these for backward compatibility + // since they are in the FAQ. + while( index != -1 ) + { + path = path.substring( 0, index ) + path.substring( index + 5 ); + index = path.indexOf( "file:" ); + } + + String entitySystemId = path; + index = path.indexOf( "%23" ); + // convert these to # + while( index != -1 ) + { + path = path.substring( 0, index ) + "#" + path.substring( index + 3 ); + index = path.indexOf( "%23" ); + } + + File file = new File( path ); + if( !file.isAbsolute() ) + { + file = new File( buildFileParent, path ); + } + + try + { + InputSource inputSource = new InputSource( new FileInputStream( file ) ); + inputSource.setSystemId( "file:" + entitySystemId ); + return inputSource; + } + catch( FileNotFoundException fne ) + { + project.log( file.getAbsolutePath() + " could not be found", + Project.MSG_WARN ); + } + } + // use default if not file or file not found + return null; + } + + public void startElement( String tag, AttributeList attrs ) + throws SAXParseException + { + if( tag.equals( "project" ) ) + { + new ProjectHandler( this ).init( tag, attrs ); + } + else + { + throw new SAXParseException( "Config file is not of expected XML type", locator ); + } + } + } + + /** + * Handler for "target" elements. + * + * @author RT + */ + private class TargetHandler extends AbstractHandler + { + private Target target; + + public TargetHandler( DocumentHandler parentHandler ) + { + super( parentHandler ); + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + String name = null; + String depends = ""; + String ifCond = null; + String unlessCond = null; + String id = null; + String description = null; + + for( int i = 0; i < attrs.getLength(); i++ ) + { + String key = attrs.getName( i ); + String value = attrs.getValue( i ); + + if( key.equals( "name" ) ) + { + name = value; + } + else if( key.equals( "depends" ) ) + { + depends = value; + } + else if( key.equals( "if" ) ) + { + ifCond = value; + } + else if( key.equals( "unless" ) ) + { + unlessCond = value; + } + else if( key.equals( "id" ) ) + { + id = value; + } + else if( key.equals( "description" ) ) + { + description = value; + } + else + { + throw new SAXParseException( "Unexpected attribute \"" + key + "\"", locator ); + } + } + + if( name == null ) + { + throw new SAXParseException( "target element appears without a name attribute", locator ); + } + + target = new Target(); + target.setName( name ); + target.setIf( ifCond ); + target.setUnless( unlessCond ); + target.setDescription( description ); + project.addTarget( name, target ); + + if( id != null && !id.equals( "" ) ) + project.addReference( id, target ); + + // take care of dependencies + + if( depends.length() > 0 ) + { + target.setDepends( depends ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( project.getDataTypeDefinitions().get( name ) != null ) + { + new DataTypeHandler( this, target ).init( name, attrs ); + } + else + { + new TaskHandler( this, target, null, target ).init( name, attrs ); + } + } + } + + /** + * Handler for all task elements. + * + * @author RT + */ + private class TaskHandler extends AbstractHandler + { + private RuntimeConfigurable wrapper = null; + private TaskContainer container; + private RuntimeConfigurable parentWrapper; + private Target target; + private Task task; + + public TaskHandler( DocumentHandler parentHandler, TaskContainer container, RuntimeConfigurable parentWrapper, Target target ) + { + super( parentHandler ); + this.container = container; + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + if( wrapper == null ) + { + try + { + addText( project, task, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + else + { + wrapper.addText( buf, start, end ); + } + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + try + { + task = project.createTask( tag ); + } + catch( BuildException e ) + { + // swallow here, will be thrown again in + // UnknownElement.maybeConfigure if the problem persists. + } + + if( task == null ) + { + task = new UnknownElement( tag ); + task.setProject( project ); + task.setTaskType( tag ); + task.setTaskName( tag ); + } + + task.setLocation( new Location( buildFile.toString(), locator.getLineNumber(), locator.getColumnNumber() ) ); + configureId( task, attrs ); + + // Top level tasks don't have associated targets + if( target != null ) + { + task.setOwningTarget( target ); + container.addTask( task ); + task.init(); + wrapper = task.getRuntimeConfigurableWrapper(); + wrapper.setAttributes( attrs ); + if( parentWrapper != null ) + { + parentWrapper.addChild( wrapper ); + } + } + else + { + task.init(); + configure( task, attrs, project ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( task instanceof TaskContainer ) + { + // task can contain other tasks - no other nested elements possible + new TaskHandler( this, ( TaskContainer )task, wrapper, target ).init( name, attrs ); + } + else + { + new NestedElementHandler( this, task, wrapper, target ).init( name, attrs ); + } + } + + protected void finished() + { + if( task != null && target == null ) + { + task.execute(); + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/RuntimeConfigurable.java b/proposal/myrmidon/src/main/org/apache/tools/ant/RuntimeConfigurable.java new file mode 100644 index 000000000..4acd55108 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/RuntimeConfigurable.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Vector; +import org.xml.sax.AttributeList; +import org.xml.sax.helpers.AttributeListImpl; + +/** + * Wrapper class that holds the attributes of a Task (or elements nested below + * that level) and takes care of configuring that element at runtime. + * + * @author Stefan Bodewig + */ +public class RuntimeConfigurable +{ + + private String elementTag = null; + private Vector children = new Vector(); + private Object wrappedObject = null; + private StringBuffer characters = new StringBuffer(); + private AttributeList attributes; + + /** + * @param proxy The element to wrap. + * @param elementTag Description of Parameter + */ + public RuntimeConfigurable( Object proxy, String elementTag ) + { + wrappedObject = proxy; + this.elementTag = elementTag; + } + + /** + * Set's the attributes for the wrapped element. + * + * @param attributes The new Attributes value + */ + public void setAttributes( AttributeList attributes ) + { + this.attributes = new AttributeListImpl( attributes ); + } + + /** + * Returns the AttributeList of the wrapped element. + * + * @return The Attributes value + */ + public AttributeList getAttributes() + { + return attributes; + } + + public String getElementTag() + { + return elementTag; + } + + /** + * Adds child elements to the wrapped element. + * + * @param child The feature to be added to the Child attribute + */ + public void addChild( RuntimeConfigurable child ) + { + children.addElement( child ); + } + + /** + * Add characters from #PCDATA areas to the wrapped element. + * + * @param data The feature to be added to the Text attribute + */ + public void addText( String data ) + { + characters.append( data ); + } + + /** + * Add characters from #PCDATA areas to the wrapped element. + * + * @param buf The feature to be added to the Text attribute + * @param start The feature to be added to the Text attribute + * @param end The feature to be added to the Text attribute + */ + public void addText( char[] buf, int start, int end ) + { + addText( new String( buf, start, end ) ); + } + + + /** + * Configure the wrapped element and all children. + * + * @param p Description of Parameter + * @exception BuildException Description of Exception + */ + public void maybeConfigure( Project p ) + throws BuildException + { + String id = null; + + if( attributes != null ) + { + ProjectHelper.configure( wrappedObject, attributes, p ); + id = attributes.getValue( "id" ); + attributes = null; + } + if( characters.length() != 0 ) + { + ProjectHelper.addText( p, wrappedObject, characters.toString() ); + characters.setLength( 0 ); + } + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + RuntimeConfigurable child = ( RuntimeConfigurable )enum.nextElement(); + if( child.wrappedObject instanceof Task ) + { + Task childTask = ( Task )child.wrappedObject; + childTask.setRuntimeConfigurableWrapper( child ); + childTask.maybeConfigure(); + } + else + { + child.maybeConfigure( p ); + } + ProjectHelper.storeChild( p, wrappedObject, child.wrappedObject, child.getElementTag().toLowerCase( Locale.US ) ); + } + + if( id != null ) + { + p.addReference( id, wrappedObject ); + } + } + + void setProxy( Object proxy ) + { + wrappedObject = proxy; + } + + /** + * Returns the child with index index. + * + * @param index Description of Parameter + * @return The Child value + */ + RuntimeConfigurable getChild( int index ) + { + return ( RuntimeConfigurable )children.elementAt( index ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Target.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Target.java new file mode 100644 index 000000000..78cb4f980 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Target.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * This class implements a target object with required parameters. + * + * @author James Davidson duncan@x180.com + */ + +public class Target implements TaskContainer +{ + private String ifCondition = ""; + private String unlessCondition = ""; + private Vector dependencies = new Vector( 2 ); + private Vector children = new Vector( 5 ); + private String description = null; + + private String name; + private Project project; + + public void setDepends( String depS ) + { + if( depS.length() > 0 ) + { + StringTokenizer tok = + new StringTokenizer( depS, ",", true ); + while( tok.hasMoreTokens() ) + { + String token = tok.nextToken().trim(); + + //Make sure the dependency is not empty string + if( token.equals( "" ) || token.equals( "," ) ) + { + throw new BuildException( "Syntax Error: Depend attribute " + + "for target \"" + getName() + + "\" has an empty string for dependency." ); + } + + addDependency( token ); + + //Make sure that depends attribute does not + //end in a , + if( tok.hasMoreTokens() ) + { + token = tok.nextToken(); + if( !tok.hasMoreTokens() || !token.equals( "," ) ) + { + throw new BuildException( "Syntax Error: Depend attribute " + + "for target \"" + getName() + + "\" ends with a , character" ); + } + } + } + } + } + + public void setDescription( String description ) + { + this.description = description; + } + + public void setIf( String property ) + { + this.ifCondition = ( property == null ) ? "" : property; + } + + public void setName( String name ) + { + this.name = name; + } + + public void setProject( Project project ) + { + this.project = project; + } + + public void setUnless( String property ) + { + this.unlessCondition = ( property == null ) ? "" : property; + } + + public Enumeration getDependencies() + { + return dependencies.elements(); + } + + public String getDescription() + { + return description; + } + + public String getName() + { + return name; + } + + public Project getProject() + { + return project; + } + + /** + * Get the current set of tasks to be executed by this target. + * + * @return The current set of tasks. + */ + public Task[] getTasks() + { + Vector tasks = new Vector( children.size() ); + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + Object o = enum.nextElement(); + if( o instanceof Task ) + { + tasks.addElement( o ); + } + } + + Task[] retval = new Task[tasks.size()]; + tasks.copyInto( retval ); + return retval; + } + + public final void performTasks() + { + try + { + project.fireTargetStarted( this ); + execute(); + project.fireTargetFinished( this, null ); + } + catch( RuntimeException exc ) + { + project.fireTargetFinished( this, exc ); + throw exc; + } + } + + public void addDataType( RuntimeConfigurable r ) + { + children.addElement( r ); + } + + public void addDependency( String dependency ) + { + dependencies.addElement( dependency ); + } + + public void addTask( Task task ) + { + children.addElement( task ); + } + + public void execute() + throws BuildException + { + if( testIfCondition() && testUnlessCondition() ) + { + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + Object o = enum.nextElement(); + if( o instanceof Task ) + { + Task task = ( Task )o; + task.perform(); + } + else + { + RuntimeConfigurable r = ( RuntimeConfigurable )o; + r.maybeConfigure( project ); + } + } + } + else if( !testIfCondition() ) + { + project.log( this, "Skipped because property '" + this.ifCondition + "' not set.", + Project.MSG_VERBOSE ); + } + else + { + project.log( this, "Skipped because property '" + this.unlessCondition + "' set.", + Project.MSG_VERBOSE ); + } + } + + public String toString() + { + return name; + } + + void replaceChild( Task el, Object o ) + { + int index = -1; + while( ( index = children.indexOf( el ) ) >= 0 ) + { + children.setElementAt( o, index ); + } + } + + private boolean testIfCondition() + { + if( "".equals( ifCondition ) ) + { + return true; + } + + String test = project.replaceProperties( ifCondition ); + return project.getProperty( test ) != null; + } + + private boolean testUnlessCondition() + { + if( "".equals( unlessCondition ) ) + { + return true; + } + String test = project.replaceProperties( unlessCondition ); + return project.getProperty( test ) == null; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Task.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Task.java new file mode 100644 index 000000000..7a08963ea --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Task.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.TaskException; + +public abstract class Task + extends ProjectComponent + implements org.apache.myrmidon.api.Task +{ + protected Target target; + protected String description; + protected Location location = Location.UNKNOWN_LOCATION; + protected String taskName; + protected String taskType; + private boolean invalid; + protected RuntimeConfigurable wrapper; + + private UnknownElement replacement; + + /** + * Sets a description of the current action. It will be usefull in + * commenting what we are doing. + * + * @param desc The new Description value + */ + public void setDescription( String desc ) + { + description = desc; + } + + /** + * Sets the file location where this task was defined. + * + * @param location The new Location value + */ + public void setLocation( Location location ) + { + this.location = location; + } + + /** + * Sets the target object of this task. + * + * @param target Target in whose scope this task belongs. + */ + public void setOwningTarget( Target target ) + { + this.target = target; + } + + /** + * Set the name to use in logging messages. + * + * @param name the name to use in logging messages. + */ + public void setTaskName( String name ) + { + this.taskName = name; + } + + public String getDescription() + { + return description; + } + + /** + * Returns the file location where this task was defined. + * + * @return The Location value + */ + public Location getLocation() + { + return location; + } + + /** + * Get the Target to which this task belongs + * + * @return the task's target. + */ + public Target getOwningTarget() + { + return target; + } + + /** + * Returns the wrapper class for runtime configuration. + * + * @return The RuntimeConfigurableWrapper value + */ + public RuntimeConfigurable getRuntimeConfigurableWrapper() + { + if( wrapper == null ) + { + wrapper = new RuntimeConfigurable( this, getTaskName() ); + } + return wrapper; + } + + /** + * Get the name to use in logging messages. + * + * @return the name to use in logging messages. + */ + public String getTaskName() + { + return taskName; + } + + /** + * Perform this task + */ + public final void perform() + throws TaskException + { + if( !invalid ) + { + try + { + project.fireTaskStarted( this ); + maybeConfigure(); + execute(); + project.fireTaskFinished( this, null ); + } + catch( TaskException te ) + { + if( te instanceof BuildException ) + { + BuildException be = (BuildException)te; + if( be.getLocation() == Location.UNKNOWN_LOCATION ) + { + be.setLocation( getLocation() ); + } + } + project.fireTaskFinished( this, te ); + throw te; + } + catch( RuntimeException re ) + { + project.fireTaskFinished( this, re ); + throw re; + } + } + else + { + UnknownElement ue = getReplacement(); + Task task = ue.getTask(); + task.perform(); + } + } + + /** + * Called by the project to let the task do it's work. This method may be + * called more than once, if the task is invoked more than once. For + * example, if target1 and target2 both depend on target3, then running "ant + * target1 target2" will run all tasks in target3 twice. + * + * @throws BuildException if someting goes wrong with the build + */ + public void execute() + throws TaskException + { + } + + /** + * Called by the project to let the task initialize properly. + * + * @throws BuildException if someting goes wrong with the build + */ + public void init() + throws TaskException + { + } + + /** + * Log a message with the default (INFO) priority. + * + * @param msg Description of Parameter + */ + public void log( String msg ) + { + log( msg, Project.MSG_INFO ); + } + + /** + * Log a mesage with the give priority. + * + * @param msgLevel the message priority at which this message is to be + * logged. + * @param msg Description of Parameter + */ + public void log( String msg, int msgLevel ) + { + project.log( this, msg, msgLevel ); + } + + /** + * Configure this task - if it hasn't been done already. + * + * @exception BuildException Description of Exception + */ + public void maybeConfigure() + throws TaskException + { + if( !invalid ) + { + if( wrapper != null ) + { + wrapper.maybeConfigure( project ); + } + } + else + { + getReplacement(); + } + } + + protected void setRuntimeConfigurableWrapper( RuntimeConfigurable wrapper ) + { + this.wrapper = wrapper; + } + + protected void handleErrorOutput( String line ) + { + log( line, Project.MSG_ERR ); + } + + protected void handleOutput( String line ) + { + log( line, Project.MSG_INFO ); + } + + /** + * Set the name with which the task has been invoked. + * + * @param type the name the task has been invoked as. + */ + void setTaskType( String type ) + { + this.taskType = type; + } + + /** + * Mark this task as invalid. + */ + final void markInvalid() + { + invalid = true; + } + + /** + * Create an UnknownElement that can be used to replace this task. + * + * @return The Replacement value + */ + private UnknownElement getReplacement() + throws TaskException + { + if( replacement == null ) + { + replacement = new UnknownElement( taskType ); + replacement.setProject( project ); + replacement.setTaskType( taskType ); + replacement.setTaskName( taskName ); + replacement.setLocation( location ); + replacement.setOwningTarget( target ); + replacement.setRuntimeConfigurableWrapper( wrapper ); + wrapper.setProxy( replacement ); + target.replaceChild( this, replacement ); + replacement.maybeConfigure(); + } + return replacement; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/TaskAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/TaskAdapter.java new file mode 100644 index 000000000..6f7ebcbf1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/TaskAdapter.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + + +/** + * Use introspection to "adapt" an arbitrary Bean ( not extending Task, but with + * similar patterns). + * + * @author costin@dnt.ro + */ +public class TaskAdapter extends Task +{ + + Object proxy; + + /** + * Checks a class, whether it is suitable to be adapted by TaskAdapter. + * Checks conditions only, which are additionally required for a tasks + * adapted by TaskAdapter. Thus, this method should be called by {@link + * Project#checkTaskClass}. Throws a BuildException and logs as + * Project.MSG_ERR for conditions, that will cause the task execution to + * fail. Logs other suspicious conditions with Project.MSG_WARN. + * + * @param taskClass Description of Parameter + * @param project Description of Parameter + */ + public static void checkTaskClass( final Class taskClass, final Project project ) + { + // don't have to check for interface, since then + // taskClass would be abstract too. + try + { + final Method executeM = taskClass.getMethod( "execute", null ); + // don't have to check for public, since + // getMethod finds public method only. + // don't have to check for abstract, since then + // taskClass would be abstract too. + if( !Void.TYPE.equals( executeM.getReturnType() ) ) + { + final String message = "return type of execute() should be void but was \"" + executeM.getReturnType() + "\" in " + taskClass; + project.log( message, Project.MSG_WARN ); + } + } + catch( NoSuchMethodException e ) + { + final String message = "No public execute() in " + taskClass; + project.log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + } + + /** + * Set the target object class + * + * @param o The new Proxy value + */ + public void setProxy( Object o ) + { + this.proxy = o; + } + + public Object getProxy() + { + return this.proxy; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Method setProjectM = null; + try + { + Class c = proxy.getClass(); + setProjectM = + c.getMethod( "setProject", new Class[]{Project.class} ); + if( setProjectM != null ) + { + setProjectM.invoke( proxy, new Object[]{project} ); + } + } + catch( NoSuchMethodException e ) + { + // ignore this if the class being used as a task does not have + // a set project method. + } + catch( Exception ex ) + { + log( "Error setting project in " + proxy.getClass(), + Project.MSG_ERR ); + throw new BuildException( ex ); + } + + Method executeM = null; + try + { + Class c = proxy.getClass(); + executeM = c.getMethod( "execute", new Class[0] ); + if( executeM == null ) + { + log( "No public execute() in " + proxy.getClass(), Project.MSG_ERR ); + throw new BuildException( "No public execute() in " + proxy.getClass() ); + } + executeM.invoke( proxy, null ); + return; + } + catch( Exception ex ) + { + log( "Error in " + proxy.getClass(), Project.MSG_ERR ); + throw new BuildException( ex ); + } + + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/TaskContainer.java b/proposal/myrmidon/src/main/org/apache/tools/ant/TaskContainer.java new file mode 100644 index 000000000..0802b18c4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/TaskContainer.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Interface for objects which can contain tasks

+ * + * It is recommended that implementations call {@link Task#perform perform} + * instead of {@link Task#execute execute} for the tasks they contain, as this + * method ensures that {@link BuildEvent BuildEvents} will be generated.

+ * + * @author Conor MacNeill + */ +public interface TaskContainer +{ + /** + * Add a task to this task container + * + * @param task the task to be added to this container + */ + void addTask( Task task ); +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/UnknownElement.java b/proposal/myrmidon/src/main/org/apache/tools/ant/UnknownElement.java new file mode 100644 index 000000000..efe3b6046 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/UnknownElement.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Vector; + +/** + * Wrapper class that holds all information necessary to create a task or data + * type that did not exist when Ant started. + * + * @author Stefan Bodewig + */ +public class UnknownElement extends Task +{ + + /** + * Childelements, holds UnknownElement instances. + */ + private Vector children = new Vector(); + + /** + * Holds the name of the task/type or nested child element of a task/type + * that hasn't been defined at parser time. + */ + private String elementName; + + /** + * The real object after it has been loaded. + */ + private Object realThing; + + public UnknownElement( String elementName ) + { + this.elementName = elementName; + } + + /** + * return the corresponding XML element name. + * + * @return The Tag value + */ + public String getTag() + { + return elementName; + } + + /** + * Return the task instance after it has been created (and if it is a task. + * + * @return The Task value + */ + public Task getTask() + { + if( realThing != null && realThing instanceof Task ) + { + return ( Task )realThing; + } + return null; + } + + /** + * Get the name to use in logging messages. + * + * @return the name to use in logging messages. + */ + public String getTaskName() + { + return realThing == null || !( realThing instanceof Task ) ? + super.getTaskName() : ( ( Task )realThing ).getTaskName(); + } + + /** + * Adds a child element to this element. + * + * @param child The feature to be added to the Child attribute + */ + public void addChild( UnknownElement child ) + { + children.addElement( child ); + } + + /** + * Called when the real task has been configured for the first time. + */ + public void execute() + { + if( realThing == null ) + { + // plain impossible to get here, maybeConfigure should + // have thrown an exception. + throw new BuildException( "Could not create task of type: " + + elementName, location ); + } + + if( realThing instanceof Task ) + { + ( ( Task )realThing ).perform(); + } + } + + /** + * creates the real object instance, creates child elements, configures the + * attributes of the real object. + * + * @exception BuildException Description of Exception + */ + public void maybeConfigure() + throws BuildException + { + realThing = makeObject( this, wrapper ); + + wrapper.setProxy( realThing ); + if( realThing instanceof Task ) + { + ( ( Task )realThing ).setRuntimeConfigurableWrapper( wrapper ); + } + + handleChildren( realThing, wrapper ); + + wrapper.maybeConfigure( project ); + if( realThing instanceof Task ) + { + target.replaceChild( this, realThing ); + } + else + { + target.replaceChild( this, wrapper ); + } + } + + protected BuildException getNotFoundException( String what, + String elementName ) + { + String lSep = System.getProperty( "line.separator" ); + String msg = "Could not create " + what + " of type: " + elementName + + "." + lSep + + "Ant could not find the task or a class this" + lSep + + "task relies upon." + lSep + + "Common solutions are to use taskdef to declare" + lSep + + "your task, or, if this is an optional task," + lSep + + "to put the optional.jar and all required libraries of" + lSep + + "this task in the lib directory of" + lSep + + "your ant installation (ANT_HOME)." + lSep + + "There is also the possibility that your build file " + lSep + + "is written to work with a more recent version of ant " + lSep + + "than the one you are using, in which case you have to " + lSep + + "upgrade."; + return new BuildException( msg, location ); + } + + /** + * Creates child elements, creates children of the children, sets attributes + * of the child elements. + * + * @param parent Description of Parameter + * @param parentWrapper Description of Parameter + * @exception BuildException Description of Exception + */ + protected void handleChildren( Object parent, + RuntimeConfigurable parentWrapper ) + throws BuildException + { + + if( parent instanceof TaskAdapter ) + { + parent = ( ( TaskAdapter )parent ).getProxy(); + } + + Class parentClass = parent.getClass(); + IntrospectionHelper ih = IntrospectionHelper.getHelper( parentClass ); + + for( int i = 0; i < children.size(); i++ ) + { + RuntimeConfigurable childWrapper = parentWrapper.getChild( i ); + UnknownElement child = ( UnknownElement )children.elementAt( i ); + Object realChild = null; + + if( parent instanceof TaskContainer ) + { + realChild = makeTask( child, childWrapper, false ); + ( ( TaskContainer )parent ).addTask( ( Task )realChild ); + } + else + { + realChild = ih.createElement( project, parent, child.getTag() ); + } + + childWrapper.setProxy( realChild ); + if( parent instanceof TaskContainer ) + { + ( ( Task )realChild ).setRuntimeConfigurableWrapper( childWrapper ); + } + + child.handleChildren( realChild, childWrapper ); + + if( parent instanceof TaskContainer ) + { + ( ( Task )realChild ).maybeConfigure(); + } + } + } + + /** + * Creates a named task or data type - if it is a task, configure it up to + * the init() stage. + * + * @param ue Description of Parameter + * @param w Description of Parameter + * @return Description of the Returned Value + */ + protected Object makeObject( UnknownElement ue, RuntimeConfigurable w ) + { + Object o = makeTask( ue, w, true ); + if( o == null ) + { + o = project.createDataType( ue.getTag() ); + } + if( o == null ) + { + throw getNotFoundException( "task or type", ue.getTag() ); + } + return o; + } + + /** + * Create a named task and configure it up to the init() stage. + * + * @param ue Description of Parameter + * @param w Description of Parameter + * @param onTopLevel Description of Parameter + * @return Description of the Returned Value + */ + protected Task makeTask( UnknownElement ue, RuntimeConfigurable w, + boolean onTopLevel ) + { + Task task = project.createTask( ue.getTag() ); + if( task == null && !onTopLevel ) + { + throw getNotFoundException( "task", ue.getTag() ); + } + + if( task != null ) + { + task.setLocation( getLocation() ); + // UnknownElement always has an associated target + task.setOwningTarget( target ); + task.init(); + } + return task; + } + +}// UnknownElement diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/defaultManifest.mf b/proposal/myrmidon/src/main/org/apache/tools/ant/defaultManifest.mf new file mode 100644 index 000000000..1dc733da7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/defaultManifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Created-By: Apache Ant @VERSION@ + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ant.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ant.java new file mode 100644 index 000000000..937f44cd2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ant.java @@ -0,0 +1,550 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.DefaultLogger; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.util.FileUtils; + +/** + * Call Ant in a sub-project
+ *  <target name="foo" depends="init">
+ *    <ant antfile="build.xml" target="bar" >
+ *      <property name="property1" value="aaaaa" />
+ *      <property name="foo" value="baz" />
+ *    </ant> </target> <target name="bar"
+ * depends="init"> <echo message="prop is ${property1}
+ * ${foo}" /> </target> 
+ * + * @author costin@dnt.ro + */ +public class Ant extends Task +{ + + /** + * the basedir where is executed the build file + */ + private File dir = null; + + /** + * the build.xml file (can be absolute) in this case dir will be ignored + */ + private String antFile = null; + + /** + * the target to call if any + */ + private String target = null; + + /** + * the output + */ + private String output = null; + + /** + * should we inherit properties from the parent ? + */ + private boolean inheritAll = true; + + /** + * should we inherit references from the parent ? + */ + private boolean inheritRefs = false; + + /** + * the properties to pass to the new project + */ + private Vector properties = new Vector(); + + /** + * the references to pass to the new project + */ + private Vector references = new Vector(); + + /** + * the temporary project created to run the build file + */ + private Project newProject; + + /** + * set the build file, it can be either absolute or relative. If it is + * absolute, dir will be ignored, if it is relative it will be + * resolved relative to dir . + * + * @param s The new Antfile value + */ + public void setAntfile( String s ) + { + // @note: it is a string and not a file to handle relative/absolute + // otherwise a relative file will be resolved based on the current + // basedir. + this.antFile = s; + } + + /** + * ... + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * If true, inherit all properties from parent Project If false, inherit + * only userProperties and those defined inside the ant call itself + * + * @param value The new InheritAll value + */ + public void setInheritAll( boolean value ) + { + inheritAll = value; + } + + /** + * If true, inherit all references from parent Project If false, inherit + * only those defined inside the ant call itself + * + * @param value The new InheritRefs value + */ + public void setInheritRefs( boolean value ) + { + inheritRefs = value; + } + + public void setOutput( String s ) + { + this.output = s; + } + + /** + * set the target to execute. If none is defined it will execute the default + * target of the build file + * + * @param s The new Target value + */ + public void setTarget( String s ) + { + this.target = s; + } + + /** + * create a reference element that identifies a data type that should be + * carried over to the new project. + * + * @param r The feature to be added to the Reference attribute + */ + public void addReference( Reference r ) + { + references.addElement( r ); + } + + /** + * create a property to pass to the new project as a 'user property' + * + * @return Description of the Returned Value + */ + public Property createProperty() + { + if( newProject == null ) + { + reinit(); + } + Property p = new Property( true ); + p.setProject( newProject ); + p.setTaskName( "property" ); + properties.addElement( p ); + return p; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + if( newProject == null ) + { + reinit(); + } + + if( ( dir == null ) && ( inheritAll == true ) ) + { + dir = project.getBaseDir(); + } + + initializeProject(); + + if( dir != null ) + { + newProject.setBaseDir( dir ); + newProject.setUserProperty( "basedir", dir.getAbsolutePath() ); + } + else + { + dir = project.getBaseDir(); + } + + overrideProperties(); + + if( antFile == null ) + { + antFile = "build.xml"; + } + + File file = FileUtils.newFileUtils().resolveFile( dir, antFile ); + antFile = file.getAbsolutePath(); + + newProject.setUserProperty( "ant.file", antFile ); + ProjectHelper.configureProject( newProject, new File( antFile ) ); + + if( target == null ) + { + target = newProject.getDefaultTarget(); + } + + addReferences(); + + // Are we trying to call the target in which we are defined? + if( newProject.getBaseDir().equals( project.getBaseDir() ) && + newProject.getProperty( "ant.file" ).equals( project.getProperty( "ant.file" ) ) && + getOwningTarget() != null && + target.equals( this.getOwningTarget().getName() ) ) + { + + throw new BuildException( "ant task calling its own parent target" ); + } + + newProject.executeTarget( target ); + } + finally + { + // help the gc + newProject = null; + } + } + + public void init() + { + newProject = new Project(); + newProject.setJavaVersionProperty(); + newProject.addTaskDefinition( "property", + ( Class )project.getTaskDefinitions().get( "property" ) ); + } + + protected void handleErrorOutput( String line ) + { + if( newProject != null ) + { + newProject.demuxOutput( line, true ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( newProject != null ) + { + newProject.demuxOutput( line, false ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Add the references explicitly defined as nested elements to the new + * project. Also copy over all references that don't override existing + * references in the new project if inheritall has been requested. + * + * @exception BuildException Description of Exception + */ + private void addReferences() + throws BuildException + { + Hashtable thisReferences = ( Hashtable )project.getReferences().clone(); + Hashtable newReferences = newProject.getReferences(); + Enumeration e; + if( references.size() > 0 ) + { + for( e = references.elements(); e.hasMoreElements(); ) + { + Reference ref = ( Reference )e.nextElement(); + String refid = ref.getRefId(); + if( refid == null ) + { + throw new BuildException( "the refid attribute is required for reference elements" ); + } + if( !thisReferences.containsKey( refid ) ) + { + log( "Parent project doesn't contain any reference '" + + refid + "'", + Project.MSG_WARN ); + continue; + } + + Object o = thisReferences.remove( refid ); + String toRefid = ref.getToRefid(); + if( toRefid == null ) + { + toRefid = refid; + } + copyReference( refid, toRefid ); + } + } + + // Now add all references that are not defined in the + // subproject, if inheritRefs is true + if( inheritRefs ) + { + for( e = thisReferences.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + if( newReferences.containsKey( key ) ) + { + continue; + } + copyReference( key, key ); + } + } + } + + /** + * Try to clone and reconfigure the object referenced by oldkey in the + * parent project and add it to the new project with the key newkey.

+ * + * If we cannot clone it, copy the referenced object itself and keep our + * fingers crossed.

+ * + * @param oldKey Description of Parameter + * @param newKey Description of Parameter + */ + private void copyReference( String oldKey, String newKey ) + { + Object orig = project.getReference( oldKey ); + Class c = orig.getClass(); + Object copy = orig; + try + { + Method cloneM = c.getMethod( "clone", new Class[0] ); + if( cloneM != null ) + { + copy = cloneM.invoke( orig, new Object[0] ); + } + } + catch( Exception e ) + { + // not Clonable + } + + if( copy instanceof ProjectComponent ) + { + ( ( ProjectComponent )copy ).setProject( newProject ); + } + else + { + try + { + Method setProjectM = + c.getMethod( "setProject", new Class[]{Project.class} ); + if( setProjectM != null ) + { + setProjectM.invoke( copy, new Object[]{newProject} ); + } + } + catch( NoSuchMethodException e ) + { + // ignore this if the class being referenced does not have + // a set project method. + } + catch( Exception e2 ) + { + String msg = "Error setting new project instance for reference with id " + + oldKey; + throw new BuildException( msg, e2, location ); + } + } + newProject.addReference( newKey, copy ); + } + + private void initializeProject() + { + Vector listeners = project.getBuildListeners(); + for( int i = 0; i < listeners.size(); i++ ) + { + newProject.addBuildListener( ( BuildListener )listeners.elementAt( i ) ); + } + + if( output != null ) + { + try + { + PrintStream out = new PrintStream( new FileOutputStream( output ) ); + DefaultLogger logger = new DefaultLogger(); + logger.setMessageOutputLevel( Project.MSG_INFO ); + logger.setOutputPrintStream( out ); + logger.setErrorPrintStream( out ); + newProject.addBuildListener( logger ); + } + catch( IOException ex ) + { + log( "Ant: Can't set output to " + output ); + } + } + + Hashtable taskdefs = project.getTaskDefinitions(); + Enumeration et = taskdefs.keys(); + while( et.hasMoreElements() ) + { + String taskName = ( String )et.nextElement(); + if( taskName.equals( "property" ) ) + { + // we have already added this taskdef in #init + continue; + } + Class taskClass = ( Class )taskdefs.get( taskName ); + newProject.addTaskDefinition( taskName, taskClass ); + } + + Hashtable typedefs = project.getDataTypeDefinitions(); + Enumeration e = typedefs.keys(); + while( e.hasMoreElements() ) + { + String typeName = ( String )e.nextElement(); + Class typeClass = ( Class )typedefs.get( typeName ); + newProject.addDataTypeDefinition( typeName, typeClass ); + } + + // set user-defined or all properties from calling project + Hashtable prop1; + if( inheritAll == true ) + { + prop1 = project.getProperties(); + } + else + { + prop1 = project.getUserProperties(); + + // set Java built-in properties separately, + // b/c we won't inherit them. + newProject.setSystemProperties(); + } + + e = prop1.keys(); + while( e.hasMoreElements() ) + { + String arg = ( String )e.nextElement(); + if( "basedir".equals( arg ) || "ant.file".equals( arg ) ) + { + // basedir and ant.file get special treatment in execute() + continue; + } + + String value = ( String )prop1.get( arg ); + if( inheritAll == true ) + { + newProject.setProperty( arg, value ); + } + else + { + newProject.setUserProperty( arg, value ); + } + } + } + + /** + * Override the properties in the new project with the one explicitly + * defined as nested elements here. + * + * @exception BuildException Description of Exception + */ + private void overrideProperties() + throws BuildException + { + Enumeration e = properties.elements(); + while( e.hasMoreElements() ) + { + Property p = ( Property )e.nextElement(); + p.setProject( newProject ); + p.execute(); + } + } + + private void reinit() + { + init(); + for( int i = 0; i < properties.size(); i++ ) + { + Property p = ( Property )properties.elementAt( i ); + Property newP = ( Property )newProject.createTask( "property" ); + newP.setName( p.getName() ); + if( p.getValue() != null ) + { + newP.setValue( p.getValue() ); + } + if( p.getFile() != null ) + { + newP.setFile( p.getFile() ); + } + if( p.getResource() != null ) + { + newP.setResource( p.getResource() ); + } + properties.setElementAt( newP, i ); + } + } + + /** + * Helper class that implements the nested <reference> element of + * <ant> and <antcall>. + * + * @author RT + */ + public static class Reference + extends org.apache.tools.ant.types.Reference + { + + private String targetid = null; + + public Reference() + { + super(); + } + + public void setToRefid( String targetid ) + { + this.targetid = targetid; + } + + public String getToRefid() + { + return targetid; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/AntStructure.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/AntStructure.java new file mode 100644 index 000000000..1b1007267 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/AntStructure.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.IntrospectionHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Creates a partial DTD for Ant from the currently known tasks. + * + * @author Stefan Bodewig + * @version $Revision$ + */ + +public class AntStructure extends Task +{ + + private final String lSep = System.getProperty( "line.separator" ); + + private final String BOOLEAN = "%boolean;"; + private final String TASKS = "%tasks;"; + private final String TYPES = "%types;"; + + private Hashtable visited = new Hashtable(); + + private File output; + + /** + * The output file. + * + * @param output The new Output value + */ + public void setOutput( File output ) + { + this.output = output; + } + + public void execute() + throws BuildException + { + + if( output == null ) + { + throw new BuildException( "output attribute is required", location ); + } + + PrintWriter out = null; + try + { + try + { + out = new PrintWriter( new OutputStreamWriter( new FileOutputStream( output ), "UTF8" ) ); + } + catch( UnsupportedEncodingException ue ) + { + /* + * Plain impossible with UTF8, see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * + * fallback to platform specific anyway. + */ + out = new PrintWriter( new FileWriter( output ) ); + } + + printHead( out, project.getTaskDefinitions().keys(), + project.getDataTypeDefinitions().keys() ); + + printTargetDecl( out ); + + Enumeration dataTypes = project.getDataTypeDefinitions().keys(); + while( dataTypes.hasMoreElements() ) + { + String typeName = ( String )dataTypes.nextElement(); + printElementDecl( out, typeName, + ( Class )project.getDataTypeDefinitions().get( typeName ) ); + } + + Enumeration tasks = project.getTaskDefinitions().keys(); + while( tasks.hasMoreElements() ) + { + String taskName = ( String )tasks.nextElement(); + printElementDecl( out, taskName, + ( Class )project.getTaskDefinitions().get( taskName ) ); + } + + printTail( out ); + + } + catch( IOException ioe ) + { + throw new BuildException( "Error writing " + output.getAbsolutePath(), + ioe, location ); + } + finally + { + if( out != null ) + { + out.close(); + } + } + } + + /** + * Does this String match the XML-NMTOKEN production? + * + * @param s Description of Parameter + * @return The Nmtoken value + */ + protected boolean isNmtoken( String s ) + { + for( int i = 0; i < s.length(); i++ ) + { + char c = s.charAt( i ); + // XXX - we are ommitting CombiningChar and Extender here + if( !Character.isLetterOrDigit( c ) && + c != '.' && c != '-' && + c != '_' && c != ':' ) + { + return false; + } + } + return true; + } + + /** + * Do the Strings all match the XML-NMTOKEN production?

+ * + * Otherwise they are not suitable as an enumerated attribute, for example. + *

+ * + * @param s Description of Parameter + * @return Description of the Returned Value + */ + protected boolean areNmtokens( String[] s ) + { + for( int i = 0; i < s.length; i++ ) + { + if( !isNmtoken( s[i] ) ) + { + return false; + } + } + return true; + } + + private void printElementDecl( PrintWriter out, String name, Class element ) + throws BuildException + { + + if( visited.containsKey( name ) ) + { + return; + } + visited.put( name, "" ); + + IntrospectionHelper ih = null; + try + { + ih = IntrospectionHelper.getHelper( element ); + } + catch( Throwable t ) + { + /* + * XXX - failed to load the class properly. + * + * should we print a warning here? + */ + return; + } + + StringBuffer sb = new StringBuffer( "" ).append( lSep ); + sb.append( "" ).append( lSep ); + out.println( sb ); + return; + } + + Vector v = new Vector(); + if( ih.supportsCharacters() ) + { + v.addElement( "#PCDATA" ); + } + + if( TaskContainer.class.isAssignableFrom( element ) ) + { + v.addElement( TASKS ); + } + + Enumeration enum = ih.getNestedElements(); + while( enum.hasMoreElements() ) + { + v.addElement( ( String )enum.nextElement() ); + } + + if( v.isEmpty() ) + { + sb.append( "EMPTY" ); + } + else + { + sb.append( "(" ); + for( int i = 0; i < v.size(); i++ ) + { + if( i != 0 ) + { + sb.append( " | " ); + } + sb.append( v.elementAt( i ) ); + } + sb.append( ")" ); + if( v.size() > 1 || !v.elementAt( 0 ).equals( "#PCDATA" ) ) + { + sb.append( "*" ); + } + } + sb.append( ">" ); + out.println( sb ); + + sb.setLength( 0 ); + sb.append( "" ).append( lSep ); + out.println( sb ); + + for( int i = 0; i < v.size(); i++ ) + { + String nestedName = ( String )v.elementAt( i ); + if( !"#PCDATA".equals( nestedName ) && + !TASKS.equals( nestedName ) && + !TYPES.equals( nestedName ) + ) + { + printElementDecl( out, nestedName, ih.getElementType( nestedName ) ); + } + } + } + + private void printHead( PrintWriter out, Enumeration tasks, + Enumeration types ) + { + out.println( "" ); + out.println( "" ); + out.print( "" ); + out.print( "" ); + + out.println( "" ); + + out.print( "" ); + out.println( "" ); + out.println( "" ); + } + + private void printTail( PrintWriter out ) { } + + private void printTargetDecl( PrintWriter out ) + { + out.print( "" ); + out.println( "" ); + + out.println( "" ); + out.println( "" ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Available.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Available.java new file mode 100644 index 000000000..a4396a6a0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Available.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + +/** + * Will set the given property if the requested resource is available at + * runtime. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Magesh Umasankar + */ + +public class Available extends Task implements Condition +{ + private String value = "true"; + private String classname; + private Path classpath; + private String file; + private Path filepath; + private AntClassLoader loader; + + private String property; + private String resource; + private FileDir type; + + public void setClassname( String classname ) + { + if( !"".equals( classname ) ) + { + this.classname = classname; + } + } + + public void setClasspath( Path classpath ) + { + createClasspath().append( classpath ); + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setFile( String file ) + { + this.file = file; + } + + public void setFilepath( Path filepath ) + { + createFilepath().append( filepath ); + } + + public void setProperty( String property ) + { + this.property = property; + } + + public void setResource( String resource ) + { + this.resource = resource; + } + + /** + * @param type The new Type value + * @deprecated setType(String) is deprecated and is replaced with + * setType(Available.FileDir) to make Ant's Introspection mechanism do + * the work and also to encapsulate operations on the type in its own + * class. + */ + public void setType( String type ) + { + log( "DEPRECATED - The setType(String) method has been deprecated." + + " Use setType(Available.FileDir) instead." ); + this.type = new FileDir(); + this.type.setValue( type ); + } + + public void setType( FileDir type ) + { + this.type = type; + } + + public void setValue( String value ) + { + this.value = value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public Path createFilepath() + { + if( this.filepath == null ) + { + this.filepath = new Path( project ); + } + return this.filepath.createPath(); + } + + public boolean eval() + throws BuildException + { + if( classname == null && file == null && resource == null ) + { + throw new BuildException( "At least one of (classname|file|resource) is required", location ); + } + + if( type != null ) + { + if( file == null ) + { + throw new BuildException( "The type attribute is only valid when specifying the file attribute." ); + } + } + + if( classpath != null ) + { + classpath.setProject( project ); + this.loader = new AntClassLoader( project, classpath ); + } + + if( ( classname != null ) && !checkClass( classname ) ) + { + log( "Unable to load class " + classname + " to set property " + property, Project.MSG_VERBOSE ); + return false; + } + + if( ( file != null ) && !checkFile() ) + { + if( type != null ) + { + log( "Unable to find " + type + " " + file + " to set property " + property, Project.MSG_VERBOSE ); + } + else + { + log( "Unable to find " + file + " to set property " + property, Project.MSG_VERBOSE ); + } + return false; + } + + if( ( resource != null ) && !checkResource( resource ) ) + { + log( "Unable to load resource " + resource + " to set property " + property, Project.MSG_VERBOSE ); + return false; + } + + if( loader != null ) + { + loader.cleanup(); + } + + return true; + } + + public void execute() + throws BuildException + { + if( property == null ) + { + throw new BuildException( "property attribute is required", location ); + } + + if( eval() ) + { + String lSep = System.getProperty( "line.separator" ); + if( null != project.getProperty( property ) ) + { + log( "DEPRECATED - used to overide an existing property. " + + lSep + + " Build writer should not reuse the same property name for " + + lSep + "different values." ); + } + this.project.setProperty( property, value ); + } + } + + private boolean checkClass( String classname ) + { + try + { + if( loader != null ) + { + loader.loadClass( classname ); + } + else + { + ClassLoader l = this.getClass().getClassLoader(); + // Can return null to represent the bootstrap class loader. + // see API docs of Class.getClassLoader. + if( l != null ) + { + l.loadClass( classname ); + } + else + { + Class.forName( classname ); + } + } + return true; + } + catch( ClassNotFoundException e ) + { + return false; + } + catch( NoClassDefFoundError e ) + { + return false; + } + } + + private boolean checkFile() + { + if( filepath == null ) + { + return checkFile( project.resolveFile( file ), file ); + } + else + { + String[] paths = filepath.list(); + for( int i = 0; i < paths.length; ++i ) + { + log( "Searching " + paths[i], Project.MSG_DEBUG ); + /* + * filepath can be a list of directory and/or + * file names (gen'd via ) + * + * look for: + * full-pathname specified == path in list + * full-pathname specified == parent dir of path in list + * simple name specified == path in list + * simple name specified == path in list + name + * simple name specified == parent dir + name + * simple name specified == parent of parent dir + name + * + */ + File path = new File( paths[i] ); + + // ** full-pathname specified == path in list + // ** simple name specified == path in list + if( path.exists() && file.equals( paths[i] ) ) + { + if( type == null ) + { + log( "Found: " + path, Project.MSG_VERBOSE ); + return true; + } + else if( type.isDir() + && path.isDirectory() ) + { + log( "Found directory: " + path, Project.MSG_VERBOSE ); + return true; + } + else if( type.isFile() + && path.isFile() ) + { + log( "Found file: " + path, Project.MSG_VERBOSE ); + return true; + } + // not the requested type + return false; + } + + FileUtils fileUtils = FileUtils.newFileUtils(); + File parent = fileUtils.getParentFile( path ); + // ** full-pathname specified == parent dir of path in list + if( parent != null && parent.exists() + && file.equals( parent.getAbsolutePath() ) ) + { + if( type == null ) + { + log( "Found: " + parent, Project.MSG_VERBOSE ); + return true; + } + else if( type.isDir() ) + { + log( "Found directory: " + parent, Project.MSG_VERBOSE ); + return true; + } + // not the requested type + return false; + } + + // ** simple name specified == path in list + name + if( path.exists() && path.isDirectory() ) + { + if( checkFile( new File( path, file ), + file + " in " + path ) ) + { + return true; + } + } + + // ** simple name specified == parent dir + name + if( parent != null && parent.exists() ) + { + if( checkFile( new File( parent, file ), + file + " in " + parent ) ) + { + return true; + } + } + + // ** simple name specified == parent of parent dir + name + if( parent != null ) + { + File grandParent = fileUtils.getParentFile( parent ); + if( grandParent != null && grandParent.exists() ) + { + if( checkFile( new File( grandParent, file ), + file + " in " + grandParent ) ) + { + return true; + } + } + } + } + } + return false; + } + + private boolean checkFile( File f, String text ) + { + if( type != null ) + { + if( type.isDir() ) + { + if( f.isDirectory() ) + { + log( "Found directory: " + text, Project.MSG_VERBOSE ); + } + return f.isDirectory(); + } + else if( type.isFile() ) + { + if( f.isFile() ) + { + log( "Found file: " + text, Project.MSG_VERBOSE ); + } + return f.isFile(); + } + } + if( f.exists() ) + { + log( "Found: " + text, Project.MSG_VERBOSE ); + } + return f.exists(); + } + + private boolean checkResource( String resource ) + { + if( loader != null ) + { + return ( loader.getResourceAsStream( resource ) != null ); + } + else + { + ClassLoader cL = this.getClass().getClassLoader(); + if( cL != null ) + { + return ( cL.getResourceAsStream( resource ) != null ); + } + else + { + return + ( ClassLoader.getSystemResourceAsStream( resource ) != null ); + } + } + } + + public static class FileDir extends EnumeratedAttribute + { + + private final static String[] values = {"file", "dir"}; + + public String[] getValues() + { + return values; + } + + public boolean isDir() + { + return "dir".equalsIgnoreCase( getValue() ); + } + + public boolean isFile() + { + return "file".equalsIgnoreCase( getValue() ); + } + + public String toString() + { + return getValue(); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BUnzip2.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BUnzip2.java new file mode 100644 index 000000000..882af9655 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BUnzip2.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.bzip2.CBZip2InputStream; + +/** + * Expands a file that has been compressed with the BZIP2 algorithm. Normally + * used to compress non-compressed archives such as TAR files. + * + * @author Magesh Umasankar + */ + +public class BUnzip2 extends Unpack +{ + + private final static String DEFAULT_EXTENSION = ".bz2"; + + protected String getDefaultExtension() + { + return DEFAULT_EXTENSION; + } + + protected void extract() + { + if( source.lastModified() > dest.lastModified() ) + { + log( "Expanding " + source.getAbsolutePath() + " to " + + dest.getAbsolutePath() ); + + FileOutputStream out = null; + CBZip2InputStream zIn = null; + FileInputStream fis = null; + BufferedInputStream bis = null; + try + { + out = new FileOutputStream( dest ); + fis = new FileInputStream( source ); + bis = new BufferedInputStream( fis ); + int b = bis.read(); + if( b != 'B' ) + { + throw new BuildException( "Invalid bz2 file.", location ); + } + b = bis.read(); + if( b != 'Z' ) + { + throw new BuildException( "Invalid bz2 file.", location ); + } + zIn = new CBZip2InputStream( bis ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = zIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + catch( IOException ioe ) + { + String msg = "Problem expanding bzip2 " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( bis != null ) + { + try + { + bis.close(); + } + catch( IOException ioex ) + {} + } + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException ioex ) + {} + } + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + if( zIn != null ) + { + try + { + zIn.close(); + } + catch( IOException ioex ) + {} + } + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BZip2.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BZip2.java new file mode 100644 index 000000000..9c3cdc401 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BZip2.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Pack; +import org.apache.tools.bzip2.CBZip2OutputStream; + +/** + * Compresses a file with the BZip2 algorithm. Normally used to compress + * non-compressed archives such as TAR files. + * + * @author Magesh Umasankar + */ + +public class BZip2 extends Pack +{ + protected void pack() + { + CBZip2OutputStream zOut = null; + try + { + BufferedOutputStream bos = + new BufferedOutputStream( new FileOutputStream( zipFile ) ); + bos.write( 'B' ); + bos.write( 'Z' ); + zOut = new CBZip2OutputStream( bos ); + zipFile( source, zOut ); + } + catch( IOException ioe ) + { + String msg = "Problem creating bzip2 " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( zOut != null ) + { + try + { + // close up + zOut.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CVSPass.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CVSPass.java new file mode 100644 index 000000000..356579a4a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CVSPass.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * CVSLogin Adds an new entry to a CVS password file + * + * @author Jeff Martin + * @version $Revision$ + */ +public class CVSPass extends Task +{ + /** + * CVS Root + */ + private String cvsRoot = null; + /** + * Password file to add password to + */ + private File passFile = null; + /** + * Password to add to file + */ + private String password = null; + /** + * End of line character + */ + private final String EOL = System.getProperty( "line.separator" ); + + /** + * Array contain char conversion data + */ + private final char shifts[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 114, 120, 53, 79, 96, 109, 72, 108, 70, 64, 76, 67, 116, 74, 68, 87, + 111, 52, 75, 119, 49, 34, 82, 81, 95, 65, 112, 86, 118, 110, 122, 105, + 41, 57, 83, 43, 46, 102, 40, 89, 38, 103, 45, 50, 42, 123, 91, 35, + 125, 55, 54, 66, 124, 126, 59, 47, 92, 71, 115, 78, 88, 107, 106, 56, + 36, 121, 117, 104, 101, 100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, + 58, 113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85, 223, + 225, 216, 187, 166, 229, 189, 222, 188, 141, 249, 148, 200, 184, 136, 248, 190, + 199, 170, 181, 204, 138, 232, 218, 183, 255, 234, 220, 247, 213, 203, 226, 193, + 174, 172, 228, 252, 217, 201, 131, 230, 197, 211, 145, 238, 161, 179, 160, 212, + 207, 221, 254, 173, 202, 146, 224, 151, 140, 196, 205, 130, 135, 133, 143, 246, + 192, 159, 244, 239, 185, 168, 215, 144, 139, 165, 180, 157, 147, 186, 214, 176, + 227, 231, 219, 169, 175, 156, 206, 198, 129, 164, 150, 210, 154, 177, 134, 127, + 182, 128, 158, 208, 162, 132, 167, 209, 149, 241, 153, 251, 237, 236, 171, 195, + 243, 233, 253, 240, 194, 250, 191, 155, 142, 137, 245, 235, 163, 242, 178, 152}; + + public CVSPass() + { + passFile = new File( System.getProperty( "user.home" ) + "/.cvspass" ); + } + + /** + * Sets cvs root to be added to the password file + * + * @param cvsRoot The new Cvsroot value + */ + public void setCvsroot( String cvsRoot ) + { + this.cvsRoot = cvsRoot; + } + + /** + * Sets the password file attribute. + * + * @param passFile The new Passfile value + */ + public void setPassfile( File passFile ) + { + this.passFile = passFile; + } + + /** + * Sets the password attribute. + * + * @param password The new Password value + */ + public void setPassword( String password ) + { + this.password = password; + } + + /** + * Does the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public final void execute() + throws BuildException + { + if( cvsRoot == null ) + throw new BuildException( "cvsroot is required" ); + if( password == null ) + throw new BuildException( "password is required" ); + + log( "cvsRoot: " + cvsRoot, project.MSG_DEBUG ); + log( "password: " + password, project.MSG_DEBUG ); + log( "passFile: " + passFile, project.MSG_DEBUG ); + + try + { + StringBuffer buf = new StringBuffer(); + + if( passFile.exists() ) + { + BufferedReader reader = + new BufferedReader( new FileReader( passFile ) ); + + String line = null; + + while( ( line = reader.readLine() ) != null ) + { + if( !line.startsWith( cvsRoot ) ) + { + buf.append( line + EOL ); + } + } + + reader.close(); + } + + String pwdfile = buf.toString() + cvsRoot + " A" + mangle( password ); + + log( "Writing -> " + pwdfile, project.MSG_DEBUG ); + + PrintWriter writer = new PrintWriter( new FileWriter( passFile ) ); + + writer.println( pwdfile ); + + writer.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + } + + private final String mangle( String password ) + { + StringBuffer buf = new StringBuffer(); + for( int i = 0; i < password.length(); i++ ) + { + buf.append( shifts[password.charAt( i )] ); + } + return buf.toString(); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CallTarget.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CallTarget.java new file mode 100644 index 000000000..80f570e08 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CallTarget.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Call another target in the same project.
+ *    <target name="foo">
+ *      <antcall target="bar">
+ *        <param name="property1" value="aaaaa" />
+ *        <param name="foo" value="baz" />
+ *       </antcall>
+ *    </target>
+ *
+ *    <target name="bar" depends="init">
+ *      <echo message="prop is ${property1} ${foo}" />
+ *    </target>
+ * 

+ * + * This only works as expected if neither property1 nor foo are defined in the + * project itself. + * + * @author Stefan Bodewig + */ +public class CallTarget extends Task +{ + private boolean initialized = false; + private boolean inheritAll = true; + + private Ant callee; + private String subTarget; + + /** + * If true, inherit all properties from parent Project If false, inherit + * only userProperties and those defined inside the antcall call itself + * + * @param inherit The new InheritAll value + */ + public void setInheritAll( boolean inherit ) + { + inheritAll = inherit; + } + + public void setTarget( String target ) + { + subTarget = target; + } + + /** + * create a reference element that identifies a data type that should be + * carried over to the new project. + * + * @param r The feature to be added to the Reference attribute + */ + public void addReference( Ant.Reference r ) + { + callee.addReference( r ); + } + + public Property createParam() + { + return callee.createProperty(); + } + + public void execute() + { + if( !initialized ) + { + init(); + } + + if( subTarget == null ) + { + throw new BuildException( "Attribute target is required.", + location ); + } + + callee.setDir( project.getBaseDir() ); + callee.setAntfile( project.getProperty( "ant.file" ) ); + callee.setTarget( subTarget ); + callee.setInheritAll( inheritAll ); + callee.execute(); + }//-- setInheritAll + + public void init() + { + callee = ( Ant )project.createTask( "ant" ); + callee.setOwningTarget( target ); + callee.setTaskName( getTaskName() ); + callee.setLocation( location ); + callee.init(); + initialized = true; + } + + protected void handleErrorOutput( String line ) + { + if( callee != null ) + { + callee.handleErrorOutput( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( callee != null ) + { + callee.handleOutput( line ); + } + else + { + super.handleOutput( line ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Checksum.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Checksum.java new file mode 100644 index 000000000..a778428c5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Checksum.java @@ -0,0 +1,489 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.FileSet; + +/** + * This task can be used to create checksums for files. It can also be used to + * verify checksums. + * + * @author Magesh Umasankar + */ +public class Checksum extends MatchingTask implements Condition +{ + /** + * File for which checksum is to be calculated. + */ + private File file = null; + /** + * MessageDigest algorithm to be used. + */ + private String algorithm = "MD5"; + /** + * MessageDigest Algorithm provider + */ + private String provider = null; + /** + * Vector to hold source file sets. + */ + private Vector filesets = new Vector(); + /** + * Stores SourceFile, DestFile pairs and SourceFile, Property String pairs. + */ + private Hashtable includeFileMap = new Hashtable(); + /** + * File Extension that is be to used to create or identify destination file + */ + private String fileext; + /** + * Create new destination file? Defaults to false. + */ + private boolean forceOverwrite; + /** + * is this task being used as a nested condition element? + */ + private boolean isCondition; + /** + * Message Digest instance + */ + private MessageDigest messageDigest; + /** + * Holds generated checksum and gets set as a Project Property. + */ + private String property; + /** + * Contains the result of a checksum verification. ("true" or "false") + */ + private String verifyProperty; + + /** + * Sets the MessageDigest algorithm to be used to calculate the checksum. + * + * @param algorithm The new Algorithm value + */ + public void setAlgorithm( String algorithm ) + { + this.algorithm = algorithm; + } + + /** + * Sets the file for which the checksum is to be calculated. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Sets the File Extension that is be to used to create or identify + * destination file + * + * @param fileext The new Fileext value + */ + public void setFileext( String fileext ) + { + this.fileext = fileext; + } + + /** + * Overwrite existing file irrespective of whether it is newer than the + * source file? Defaults to false. + * + * @param forceOverwrite The new ForceOverwrite value + */ + public void setForceOverwrite( boolean forceOverwrite ) + { + this.forceOverwrite = forceOverwrite; + } + + /** + * Sets the property to hold the generated checksum + * + * @param property The new Property value + */ + public void setProperty( String property ) + { + this.property = property; + } + + /** + * Sets the MessageDigest algorithm provider to be used to calculate the + * checksum. + * + * @param provider The new Provider value + */ + public void setProvider( String provider ) + { + this.provider = provider; + } + + /** + * Sets verify property. This project property holds the result of a + * checksum verification - "true" or "false" + * + * @param verifyProperty The new Verifyproperty value + */ + public void setVerifyproperty( String verifyProperty ) + { + this.verifyProperty = verifyProperty; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Calculate the checksum(s) + * + * @return Returns true if the checksum verification test passed, false + * otherwise. + * @exception BuildException Description of Exception + */ + public boolean eval() + throws BuildException + { + isCondition = true; + return validateAndExecute(); + } + + /** + * Calculate the checksum(s). + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + boolean value = validateAndExecute(); + if( verifyProperty != null ) + { + project.setNewProperty( verifyProperty, + new Boolean( value ).toString() ); + } + } + + /** + * Add key-value pair to the hashtable upon which to later operate upon. + * + * @param file The feature to be added to the ToIncludeFileMap attribute + * @exception BuildException Description of Exception + */ + private void addToIncludeFileMap( File file ) + throws BuildException + { + if( file != null ) + { + if( file.exists() ) + { + if( property == null ) + { + File dest = new File( file.getParent(), file.getName() + fileext ); + if( forceOverwrite || isCondition || + ( file.lastModified() > dest.lastModified() ) ) + { + includeFileMap.put( file, dest ); + } + else + { + log( file + " omitted as " + dest + " is up to date.", + Project.MSG_VERBOSE ); + } + } + else + { + includeFileMap.put( file, property ); + } + } + else + { + String message = "Could not find file " + + file.getAbsolutePath() + + " to generate checksum for."; + log( message ); + throw new BuildException( message, location ); + } + } + } + + /** + * Generate checksum(s) using the message digest created earlier. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private boolean generateChecksums() + throws BuildException + { + boolean checksumMatches = true; + FileInputStream fis = null; + FileOutputStream fos = null; + try + { + for( Enumeration e = includeFileMap.keys(); e.hasMoreElements(); ) + { + messageDigest.reset(); + File src = ( File )e.nextElement(); + if( !isCondition ) + { + log( "Calculating " + algorithm + " checksum for " + src ); + } + fis = new FileInputStream( src ); + DigestInputStream dis = new DigestInputStream( fis, + messageDigest ); + while( dis.read() != -1 ) + ; + dis.close(); + fis.close(); + fis = null; + byte[] fileDigest = messageDigest.digest(); + String checksum = ""; + for( int i = 0; i < fileDigest.length; i++ ) + { + String hexStr = Integer.toHexString( 0x00ff & fileDigest[i] ); + if( hexStr.length() < 2 ) + { + checksum += "0"; + } + checksum += hexStr; + } + //can either be a property name string or a file + Object destination = includeFileMap.get( src ); + if( destination instanceof java.lang.String ) + { + String prop = ( String )destination; + if( isCondition ) + { + checksumMatches = checksum.equals( property ); + } + else + { + project.setProperty( prop, checksum ); + } + } + else if( destination instanceof java.io.File ) + { + if( isCondition ) + { + File existingFile = ( File )destination; + if( existingFile.exists() && + existingFile.length() == checksum.length() ) + { + fis = new FileInputStream( existingFile ); + InputStreamReader isr = new InputStreamReader( fis ); + BufferedReader br = new BufferedReader( isr ); + String suppliedChecksum = br.readLine(); + fis.close(); + fis = null; + br.close(); + isr.close(); + checksumMatches = + checksum.equals( suppliedChecksum ); + } + else + { + checksumMatches = false; + } + } + else + { + File dest = ( File )destination; + fos = new FileOutputStream( dest ); + fos.write( checksum.getBytes() ); + fos.close(); + fos = null; + } + } + } + } + catch( Exception e ) + { + throw new BuildException( e ); + } + finally + { + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException e ) + {} + } + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException e ) + {} + } + } + return checksumMatches; + } + + /** + * Validate attributes and get down to business. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private boolean validateAndExecute() + throws BuildException + { + + if( file == null && filesets.size() == 0 ) + { + throw new BuildException( + "Specify at least one source - a file or a fileset." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( + "Checksum cannot be generated for directories" ); + } + + if( property != null && fileext != null ) + { + throw new BuildException( + "Property and FileExt cannot co-exist." ); + } + + if( property != null ) + { + if( forceOverwrite ) + { + throw new BuildException( + "ForceOverwrite cannot be used when Property is specified" ); + } + + if( file != null ) + { + if( filesets.size() > 0 ) + { + throw new BuildException( + "Multiple files cannot be used when Property is specified" ); + } + } + else + { + if( filesets.size() > 1 ) + { + throw new BuildException( + "Multiple files cannot be used when Property is specified" ); + } + } + } + + if( verifyProperty != null ) + { + isCondition = true; + } + + if( verifyProperty != null && forceOverwrite ) + { + throw new BuildException( + "VerifyProperty and ForceOverwrite cannot co-exist." ); + } + + if( isCondition && forceOverwrite ) + { + throw new BuildException( + "ForceOverwrite cannot be used when conditions are being used." ); + } + + if( fileext == null ) + { + fileext = "." + algorithm; + } + else if( fileext.trim().length() == 0 ) + { + throw new BuildException( + "File extension when specified must not be an empty string" ); + } + + messageDigest = null; + if( provider != null ) + { + try + { + messageDigest = MessageDigest.getInstance( algorithm, provider ); + } + catch( NoSuchAlgorithmException noalgo ) + { + throw new BuildException( noalgo ); + } + catch( NoSuchProviderException noprovider ) + { + throw new BuildException( noprovider ); + } + } + else + { + try + { + messageDigest = MessageDigest.getInstance( algorithm ); + } + catch( NoSuchAlgorithmException noalgo ) + { + throw new BuildException( noalgo ); + } + } + + if( messageDigest == null ) + { + throw new BuildException( "Unable to create Message Digest", + location ); + } + + addToIncludeFileMap( file ); + + int sizeofFileSet = filesets.size(); + for( int i = 0; i < sizeofFileSet; i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + for( int j = 0; j < srcFiles.length; j++ ) + { + File src = new File( fs.getDir( project ), srcFiles[j] ); + addToIncludeFileMap( src ); + } + } + + return generateChecksums(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Chmod.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Chmod.java new file mode 100644 index 000000000..96f736e03 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Chmod.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + + +/** + * Chmod equivalent for unix-like environments. + * + * @author costin@eng.sun.com + * @author Mariusz Nowostawski (Marni) + * mnowostawski@infoscience.otago.ac.nz + * @author Stefan Bodewig + */ + +public class Chmod extends ExecuteOn +{ + + private FileSet defaultSet = new FileSet(); + private boolean defaultSetDefined = false; + private boolean havePerm = false; + + public Chmod() + { + super.setExecutable( "chmod" ); + super.setParallel( true ); + super.setSkipEmptyFilesets( true ); + } + + public void setCommand( String e ) + { + throw new BuildException( taskType + " doesn\'t support the command attribute", location ); + } + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + defaultSetDefined = true; + defaultSet.setDefaultexcludes( useDefaultExcludes ); + } + + public void setDir( File src ) + { + defaultSet.setDir( src ); + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + defaultSetDefined = true; + defaultSet.setExcludes( excludes ); + } + + + public void setExecutable( String e ) + { + throw new BuildException( taskType + " doesn\'t support the executable attribute", location ); + } + + public void setFile( File src ) + { + FileSet fs = new FileSet(); + fs.setDir( new File( src.getParent() ) ); + fs.createInclude().setName( src.getName() ); + addFileset( fs ); + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + defaultSetDefined = true; + defaultSet.setIncludes( includes ); + } + + public void setPerm( String perm ) + { + createArg().setValue( perm ); + havePerm = true; + } + + public void setSkipEmptyFilesets( boolean skip ) + { + throw new BuildException( taskType + " doesn\'t support the skipemptyfileset attribute", location ); + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + defaultSetDefined = true; + return defaultSet.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + defaultSetDefined = true; + return defaultSet.createInclude(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + defaultSetDefined = true; + return defaultSet.createPatternSet(); + } + + public void execute() + throws BuildException + { + if( defaultSetDefined || defaultSet.getDir( project ) == null ) + { + super.execute(); + } + else if( isValidOs() ) + { + // we are chmodding the given directory + createArg().setValue( defaultSet.getDir( project ).getPath() ); + Execute execute = prepareExec(); + try + { + execute.setCommandline( cmdl.getCommandline() ); + runExecute( execute ); + } + catch( IOException e ) + { + throw new BuildException( "Execute failed: " + e, e, location ); + } + finally + { + // close the output file if required + logFlush(); + } + } + } + + protected boolean isValidOs() + { + return Os.isFamily( "unix" ) && super.isValidOs(); + } + + protected void checkConfiguration() + { + if( !havePerm ) + { + throw new BuildException( "Required attribute perm not set in chmod", + location ); + } + + if( defaultSetDefined && defaultSet.getDir( project ) != null ) + { + addFileset( defaultSet ); + } + super.checkConfiguration(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CompileTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CompileTask.java new file mode 100644 index 000000000..787a43f73 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CompileTask.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.types.PatternSet; + +/** + * This task will compile and load a new taskdef all in one step. At times, this + * is useful for eliminating ordering dependencies which otherwise would require + * multiple executions of Ant. + * + * @author Sam Ruby rubys@us.ibm.com + * @deprecated use <taskdef> elements nested into <target>s instead + */ + +public class CompileTask extends Javac +{ + + protected Vector taskList = new Vector(); + + /** + * add a new task entry on the task list + * + * @return Description of the Returned Value + */ + public Taskdef createTaskdef() + { + Taskdef task = new Taskdef(); + taskList.addElement( task ); + return task; + } + + /** + * have execute do nothing + */ + public void execute() { } + + /** + * do all the real work in init + */ + public void init() + { + log( "!! CompileTask is deprecated. !!" ); + log( "Use elements nested into s instead" ); + + // create all the include entries from the task defs + for( Enumeration e = taskList.elements(); e.hasMoreElements(); ) + { + Taskdef task = ( Taskdef )e.nextElement(); + String source = task.getClassname().replace( '.', '/' ) + ".java"; + PatternSet.NameEntry include = super.createInclude(); + include.setName( "**/" + source ); + } + + // execute Javac + super.init(); + super.execute(); + + // now define all the new tasks + for( Enumeration e = taskList.elements(); e.hasMoreElements(); ) + { + Taskdef task = ( Taskdef )e.nextElement(); + task.init(); + } + + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ConditionTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ConditionTask.java new file mode 100644 index 000000000..da72ebc93 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ConditionTask.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.taskdefs.condition.ConditionBase; + +/** + * <condition> task as a generalization of <available> and + * <uptodate>

+ * + * This task supports boolean logic as well as pluggable conditions to decide, + * whether a property should be set.

+ * + * This task does not extend Task to take advantage of ConditionBase.

+ * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ConditionTask extends ConditionBase +{ + private String value = "true"; + + private String property; + + /** + * The name of the property to set. Required. + * + * @param p The new Property value + * @since 1.1 + */ + public void setProperty( String p ) + { + property = p; + } + + /** + * The value for the property to set. Defaults to "true". + * + * @param v The new Value value + * @since 1.1 + */ + public void setValue( String v ) + { + value = v; + } + + /** + * See whether our nested condition holds and set the property. + * + * @exception BuildException Description of Exception + * @since 1.1 + */ + public void execute() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + Condition c = ( Condition )getConditions().nextElement(); + if( c.eval() ) + { + getProject().setNewProperty( property, value ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copy.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copy.java new file mode 100644 index 000000000..055c978fd --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copy.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.FlatFileNameMapper; +import org.apache.tools.ant.util.IdentityMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * A consolidated copy task. Copies a file or directory to a new file or + * directory. Files are only copied if the source file is newer than the + * destination file, or when the destination file does not exist. It is possible + * to explicitly overwrite existing files.

+ * + * This implementation is based on Arnout Kuiper's initial design document, the + * following mailing list discussions, and the copyfile/copydir tasks.

+ * + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Stefan Bodewig + * @author Michael McCallum + * @author Magesh Umasankar + */ +public class Copy extends Task +{ + protected File file = null;// the source file + protected File destFile = null;// the destination file + protected File destDir = null;// the destination directory + protected Vector filesets = new Vector(); + + protected boolean filtering = false; + protected boolean preserveLastModified = false; + protected boolean forceOverwrite = false; + protected boolean flatten = false; + protected int verbosity = Project.MSG_VERBOSE; + protected boolean includeEmpty = true; + + protected Hashtable fileCopyMap = new Hashtable(); + protected Hashtable dirCopyMap = new Hashtable(); + protected Hashtable completeDirMap = new Hashtable(); + + protected Mapper mapperElement = null; + private Vector filterSets = new Vector(); + private FileUtils fileUtils; + + public Copy() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Sets a single source file to copy. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Sets filtering. + * + * @param filtering The new Filtering value + */ + public void setFiltering( boolean filtering ) + { + this.filtering = filtering; + } + + /** + * When copying directory trees, the files can be "flattened" into a single + * directory. If there are multiple files with the same name in the source + * directory tree, only the first file will be copied into the "flattened" + * directory, unless the forceoverwrite attribute is true. + * + * @param flatten The new Flatten value + */ + public void setFlatten( boolean flatten ) + { + this.flatten = flatten; + } + + /** + * Used to copy empty directories. + * + * @param includeEmpty The new IncludeEmptyDirs value + */ + public void setIncludeEmptyDirs( boolean includeEmpty ) + { + this.includeEmpty = includeEmpty; + } + + /** + * Overwrite any existing destination file(s). + * + * @param overwrite The new Overwrite value + */ + public void setOverwrite( boolean overwrite ) + { + this.forceOverwrite = overwrite; + } + + /** + * Give the copied files the same last modified time as the original files. + * + * @param preserve The new PreserveLastModified value + * @deprecated setPreserveLastModified(String) has been deprecated and + * replaced with setPreserveLastModified(boolean) to consistently let + * the Introspection mechanism work. + */ + public void setPreserveLastModified( String preserve ) + { + setPreserveLastModified( Project.toBoolean( preserve ) ); + } + + /** + * Give the copied files the same last modified time as the original files. + * + * @param preserve The new PreserveLastModified value + */ + public void setPreserveLastModified( boolean preserve ) + { + preserveLastModified = preserve; + } + + /** + * Sets the destination directory. + * + * @param destDir The new Todir value + */ + public void setTodir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Sets the destination file. + * + * @param destFile The new Tofile value + */ + public void setTofile( File destFile ) + { + this.destFile = destFile; + } + + /** + * Used to force listing of all names of copied files. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + if( verbose ) + { + this.verbosity = Project.MSG_INFO; + } + else + { + this.verbosity = Project.MSG_VERBOSE; + } + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Create a nested filterset + * + * @return Description of the Returned Value + */ + public FilterSet createFilterSet() + { + FilterSet filterSet = new FilterSet(); + filterSets.addElement( filterSet ); + return filterSet; + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Performs the copy operation. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // make sure we don't have an illegal set of options + validateAttributes(); + + // deal with the single file + if( file != null ) + { + if( file.exists() ) + { + if( destFile == null ) + { + destFile = new File( destDir, file.getName() ); + } + + if( forceOverwrite || + ( file.lastModified() > destFile.lastModified() ) ) + { + fileCopyMap.put( file.getAbsolutePath(), destFile.getAbsolutePath() ); + } + else + { + log( file + " omitted as " + destFile + " is up to date.", + Project.MSG_VERBOSE ); + } + } + else + { + String message = "Could not find file " + + file.getAbsolutePath() + " to copy."; + log( message ); + throw new BuildException( message ); + } + } + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + String[] srcDirs = ds.getIncludedDirectories(); + boolean isEverythingIncluded = ds.isEverythingIncluded(); + if( isEverythingIncluded + && !flatten && mapperElement == null ) + { + completeDirMap.put( fromDir, destDir ); + } + scan( fromDir, destDir, srcFiles, srcDirs ); + } + + // do all the copy operations now... + doFileOperations(); + + // clean up destDir again - so this instance can be used a second + // time without throwing an exception + if( destFile != null ) + { + destDir = null; + } + } + + protected FileUtils getFileUtils() + { + return fileUtils; + } + + /** + * Get the filtersets being applied to this operation. + * + * @return a vector of FilterSet objects + */ + protected Vector getFilterSets() + { + return filterSets; + } + + protected void buildMap( File fromDir, File toDir, String[] names, + FileNameMapper mapper, Hashtable map ) + { + + String[] toCopy = null; + if( forceOverwrite ) + { + Vector v = new Vector(); + for( int i = 0; i < names.length; i++ ) + { + if( mapper.mapFileName( names[i] ) != null ) + { + v.addElement( names[i] ); + } + } + toCopy = new String[v.size()]; + v.copyInto( toCopy ); + } + else + { + SourceFileScanner ds = new SourceFileScanner( this ); + toCopy = ds.restrict( names, fromDir, toDir, mapper ); + } + + for( int i = 0; i < toCopy.length; i++ ) + { + File src = new File( fromDir, toCopy[i] ); + File dest = new File( toDir, mapper.mapFileName( toCopy[i] )[0] ); + map.put( src.getAbsolutePath(), dest.getAbsolutePath() ); + } + } + + /** + * Actually does the file (and possibly empty directory) copies. This is a + * good method for subclasses to override. + */ + protected void doFileOperations() + { + if( fileCopyMap.size() > 0 ) + { + log( "Copying " + fileCopyMap.size() + + " file" + ( fileCopyMap.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + + Enumeration e = fileCopyMap.keys(); + while( e.hasMoreElements() ) + { + String fromFile = ( String )e.nextElement(); + String toFile = ( String )fileCopyMap.get( fromFile ); + + if( fromFile.equals( toFile ) ) + { + log( "Skipping self-copy of " + fromFile, verbosity ); + continue; + } + + try + { + log( "Copying " + fromFile + " to " + toFile, verbosity ); + + FilterSetCollection executionFilters = new FilterSetCollection(); + if( filtering ) + { + executionFilters.addFilterSet( project.getGlobalFilterSet() ); + } + for( Enumeration filterEnum = filterSets.elements(); filterEnum.hasMoreElements(); ) + { + executionFilters.addFilterSet( ( FilterSet )filterEnum.nextElement() ); + } + fileUtils.copyFile( fromFile, toFile, executionFilters, + forceOverwrite, preserveLastModified ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + + if( includeEmpty ) + { + Enumeration e = dirCopyMap.elements(); + int count = 0; + while( e.hasMoreElements() ) + { + File d = new File( ( String )e.nextElement() ); + if( !d.exists() ) + { + if( !d.mkdirs() ) + { + log( "Unable to create directory " + d.getAbsolutePath(), Project.MSG_ERR ); + } + else + { + count++; + } + } + } + + if( count > 0 ) + { + log( "Copied " + count + + " empty director" + + ( count == 1 ? "y" : "ies" ) + + " to " + destDir.getAbsolutePath() ); + } + } + } + + /** + * Compares source files to destination files to see if they should be + * copied. + * + * @param fromDir Description of Parameter + * @param toDir Description of Parameter + * @param files Description of Parameter + * @param dirs Description of Parameter + */ + protected void scan( File fromDir, File toDir, String[] files, String[] dirs ) + { + FileNameMapper mapper = null; + if( mapperElement != null ) + { + mapper = mapperElement.getImplementation(); + } + else if( flatten ) + { + mapper = new FlatFileNameMapper(); + } + else + { + mapper = new IdentityMapper(); + } + + buildMap( fromDir, toDir, files, mapper, fileCopyMap ); + + if( includeEmpty ) + { + buildMap( fromDir, toDir, dirs, mapper, dirCopyMap ); + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + /** + * Ensure we have a consistent and legal set of attributes, and set any + * internal flags necessary based on different combinations of attributes. + * + * @exception BuildException Description of Exception + */ + protected void validateAttributes() + throws BuildException + { + if( file == null && filesets.size() == 0 ) + { + throw new BuildException( "Specify at least one source - a file or a fileset." ); + } + + if( destFile != null && destDir != null ) + { + throw new BuildException( "Only one of tofile and todir may be set." ); + } + + if( destFile == null && destDir == null ) + { + throw new BuildException( "One of tofile or todir must be set." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( "Use a fileset to copy directories." ); + } + + if( destFile != null && filesets.size() > 0 ) + { + if( filesets.size() > 1 ) + { + throw new BuildException( + "Cannot concatenate multiple files into a single file." ); + } + else + { + FileSet fs = ( FileSet )filesets.elementAt( 0 ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + + if( srcFiles.length > 0 ) + { + if( file == null ) + { + file = new File( srcFiles[0] ); + filesets.removeElementAt( 0 ); + } + else + { + throw new BuildException( + "Cannot concatenate multiple files into a single file." ); + } + } + else + { + throw new BuildException( + "Cannot perform operation from directory to file." ); + } + } + } + + if( destFile != null ) + { + destDir = new File( destFile.getParent() );// be 1.1 friendly + } + + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copydir.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copydir.java new file mode 100644 index 000000000..f539fffe2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copydir.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; + +/** + * Copies a directory. + * + * @author James Davidson duncan@x180.com + * @deprecated The copydir task is deprecated. Use copy instead. + */ + +public class Copydir extends MatchingTask +{ + private boolean filtering = false; + private boolean flatten = false; + private boolean forceOverwrite = false; + private Hashtable filecopyList = new Hashtable(); + private File destDir; + + private File srcDir; + + public void setDest( File dest ) + { + destDir = dest; + } + + public void setFiltering( boolean filter ) + { + filtering = filter; + } + + public void setFlatten( boolean flatten ) + { + this.flatten = flatten; + } + + public void setForceoverwrite( boolean force ) + { + forceOverwrite = force; + } + + public void setSrc( File src ) + { + srcDir = src; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The copydir task is deprecated. Use copy instead." ); + + if( srcDir == null ) + { + throw new BuildException( "src attribute must be set!", + location ); + } + + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir " + srcDir.toString() + + " does not exist!", location ); + } + + if( destDir == null ) + { + throw new BuildException( "The dest attribute must be set.", location ); + } + + if( srcDir.equals( destDir ) ) + { + log( "Warning: src == dest" ); + } + + DirectoryScanner ds = super.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + scanDir( srcDir, destDir, files ); + if( filecopyList.size() > 0 ) + { + log( "Copying " + filecopyList.size() + " file" + + ( filecopyList.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + Enumeration enum = filecopyList.keys(); + while( enum.hasMoreElements() ) + { + String fromFile = ( String )enum.nextElement(); + String toFile = ( String )filecopyList.get( fromFile ); + try + { + project.copyFile( fromFile, toFile, filtering, + forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + } + + private void scanDir( File from, File to, String[] files ) + { + for( int i = 0; i < files.length; i++ ) + { + String filename = files[i]; + File srcFile = new File( from, filename ); + File destFile; + if( flatten ) + { + destFile = new File( to, new File( filename ).getName() ); + } + else + { + destFile = new File( to, filename ); + } + if( forceOverwrite || + ( srcFile.lastModified() > destFile.lastModified() ) ) + { + filecopyList.put( srcFile.getAbsolutePath(), + destFile.getAbsolutePath() ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copyfile.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copyfile.java new file mode 100644 index 000000000..8d3398975 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copyfile.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Copies a file. + * + * @author duncan@x180.com + * @deprecated The copyfile task is deprecated. Use copy instead. + */ + +public class Copyfile extends Task +{ + private boolean filtering = false; + private boolean forceOverwrite = false; + private File destFile; + + private File srcFile; + + public void setDest( File dest ) + { + destFile = dest; + } + + public void setFiltering( String filter ) + { + filtering = Project.toBoolean( filter ); + } + + public void setForceoverwrite( boolean force ) + { + forceOverwrite = force; + } + + public void setSrc( File src ) + { + srcFile = src; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The copyfile task is deprecated. Use copy instead." ); + + if( srcFile == null ) + { + throw new BuildException( "The src attribute must be present.", location ); + } + + if( !srcFile.exists() ) + { + throw new BuildException( "src " + srcFile.toString() + + " does not exist.", location ); + } + + if( destFile == null ) + { + throw new BuildException( "The dest attribute must be present.", location ); + } + + if( srcFile.equals( destFile ) ) + { + log( "Warning: src == dest" ); + } + + if( forceOverwrite || srcFile.lastModified() > destFile.lastModified() ) + { + try + { + project.copyFile( srcFile, destFile, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Error copying file: " + srcFile.getAbsolutePath() + + " due to " + ioe.getMessage(); + throw new BuildException( msg ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Cvs.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Cvs.java new file mode 100644 index 000000000..63bda456c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Cvs.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Environment; + +/** + * @author costin@dnt.ro + * @author stefano@apache.org + * @author Wolfgang Werner + * wwerner@picturesafe.de + */ + +public class Cvs extends Task +{ + + private Commandline cmd = new Commandline(); + + /** + * the CVS command to execute. + */ + private String command = "checkout"; + + /** + * suppress information messages. + */ + private boolean quiet = false; + + /** + * report only, don't change any files. + */ + private boolean noexec = false; + + /** + * CVS port + */ + private int port = 0; + + /** + * CVS password file + */ + private File passFile = null; + + /** + * If true it will stop the build if cvs exits with error. Default is false. + * (Iulian) + */ + private boolean failOnError = false; + + /** + * the CVSROOT variable. + */ + private String cvsRoot; + + /** + * the CVS_RSH variable. + */ + private String cvsRsh; + + /** + * the directory where the checked out files should be placed. + */ + private File dest; + + /** + * the file to direct standard error from the command. + */ + private File error; + + /** + * the file to direct standard output from the command. + */ + private File output; + + /** + * the package/module to check out. + */ + private String pack; + + public void setCommand( String c ) + { + this.command = c; + } + + public void setCvsRoot( String root ) + { + // Check if not real cvsroot => set it to null + if( root != null ) + { + if( root.trim().equals( "" ) ) + root = null; + } + + this.cvsRoot = root; + } + + public void setCvsRsh( String rsh ) + { + // Check if not real cvsrsh => set it to null + if( rsh != null ) + { + if( rsh.trim().equals( "" ) ) + rsh = null; + } + + this.cvsRsh = rsh; + } + + + public void setDate( String p ) + { + if( p != null && p.trim().length() > 0 ) + { + cmd.createArgument().setValue( "-D" ); + cmd.createArgument().setValue( p ); + } + } + + public void setDest( File dest ) + { + this.dest = dest; + } + + public void setError( File error ) + { + this.error = error; + } + + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + public void setNoexec( boolean ne ) + { + noexec = ne; + } + + public void setOutput( File output ) + { + this.output = output; + } + + public void setPackage( String p ) + { + this.pack = p; + } + + public void setPassfile( File passFile ) + { + this.passFile = passFile; + } + + public void setPort( int port ) + { + this.port = port; + } + + public void setQuiet( boolean q ) + { + quiet = q; + } + + public void setTag( String p ) + { + // Check if not real tag => set it to null + if( p != null && p.trim().length() > 0 ) + { + cmd.createArgument().setValue( "-r" ); + cmd.createArgument().setValue( p ); + } + } + + + public void execute() + throws BuildException + { + + // XXX: we should use JCVS (www.ice.com/JCVS) instead of command line + // execution so that we don't rely on having native CVS stuff around (SM) + + // We can't do it ourselves as jCVS is GPLed, a third party task + // outside of jakarta repositories would be possible though (SB). + + Commandline toExecute = new Commandline(); + + toExecute.setExecutable( "cvs" ); + if( cvsRoot != null ) + { + toExecute.createArgument().setValue( "-d" ); + toExecute.createArgument().setValue( cvsRoot ); + } + if( noexec ) + { + toExecute.createArgument().setValue( "-n" ); + } + if( quiet ) + { + toExecute.createArgument().setValue( "-q" ); + } + toExecute.createArgument().setLine( command ); + toExecute.addArguments( cmd.getCommandline() ); + + if( pack != null ) + { + toExecute.createArgument().setLine( pack ); + } + + Environment env = new Environment(); + + if( port > 0 ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_CLIENT_PORT" ); + var.setValue( String.valueOf( port ) ); + env.addVariable( var ); + } + + if( passFile != null ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_PASSFILE" ); + var.setValue( String.valueOf( passFile ) ); + env.addVariable( var ); + } + + if( cvsRsh != null ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_RSH" ); + var.setValue( String.valueOf( cvsRsh ) ); + env.addVariable( var ); + } + + ExecuteStreamHandler streamhandler = null; + OutputStream outputstream = null; + OutputStream errorstream = null; + if( error == null && output == null ) + { + streamhandler = new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ); + } + else + { + if( output != null ) + { + try + { + outputstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + outputstream = new LogOutputStream( this, Project.MSG_INFO ); + } + if( error != null ) + { + try + { + errorstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( error ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + errorstream = new LogOutputStream( this, Project.MSG_WARN ); + } + streamhandler = new PumpStreamHandler( outputstream, errorstream ); + } + + Execute exe = new Execute( streamhandler, + null ); + + exe.setAntRun( project ); + if( dest == null ) + dest = project.getBaseDir(); + exe.setWorkingDirectory( dest ); + + exe.setCommandline( toExecute.getCommandline() ); + exe.setEnvironment( env.getVariables() ); + try + { + int retCode = exe.execute(); + /* + * Throw an exception if cvs exited with error. (Iulian) + */ + if( failOnError && retCode != 0 ) + throw new BuildException( "cvs exited with error code " + retCode ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + if( output != null ) + { + try + { + outputstream.close(); + } + catch( IOException e ) + {} + } + if( error != null ) + { + try + { + errorstream.close(); + } + catch( IOException e ) + {} + } + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Definer.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Definer.java new file mode 100644 index 000000000..0186ee465 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Definer.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Properties; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Base class for Taskdef and Typedef - does all the classpath handling and and + * class loading. + * + * @author Costin Manolache + * @author Stefan Bodewig + */ +public abstract class Definer extends Task +{ + private boolean reverseLoader = false; + private Path classpath; + private File file; + private String name; + private String resource; + private String value; + + public void setClassname( String v ) + { + value = v; + } + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setName( String name ) + { + this.name = name; + } + + public void setResource( String res ) + { + this.resource = res; + } + + public void setReverseLoader( boolean reverseLoader ) + { + this.reverseLoader = reverseLoader; + log( "The reverseloader attribute is DEPRECATED. It will be removed", Project.MSG_WARN ); + } + + public String getClassname() + { + return value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public void execute() + throws BuildException + { + AntClassLoader al = createLoader(); + + if( file == null && resource == null ) + { + + // simple case - one definition + if( name == null || value == null ) + { + String msg = "name or classname attributes of " + + getTaskName() + " element " + + "are undefined"; + throw new BuildException( msg ); + } + addDefinition( al, name, value ); + + } + else + { + + try + { + if( name != null || value != null ) + { + String msg = "You must not specify name or value " + + "together with file or resource."; + throw new BuildException( msg, location ); + } + + if( file != null && resource != null ) + { + String msg = "You must not specify both, file and resource."; + throw new BuildException( msg, location ); + } + + Properties props = new Properties(); + InputStream is = null; + if( file != null ) + { + log( "Loading definitions from file " + file, + Project.MSG_VERBOSE ); + is = new FileInputStream( file ); + if( is == null ) + { + log( "Could not load definitions from file " + file + + ". It doesn\'t exist.", Project.MSG_WARN ); + } + } + if( resource != null ) + { + log( "Loading definitions from resource " + resource, + Project.MSG_VERBOSE ); + is = al.getResourceAsStream( resource ); + if( is == null ) + { + log( "Could not load definitions from resource " + + resource + ". It could not be found.", + Project.MSG_WARN ); + } + } + + if( is != null ) + { + props.load( is ); + Enumeration keys = props.keys(); + while( keys.hasMoreElements() ) + { + String n = ( String )keys.nextElement(); + String v = props.getProperty( n ); + addDefinition( al, n, v ); + } + } + } + catch( IOException ex ) + { + throw new BuildException( ex); + } + } + } + + protected abstract void addDefinition( String name, Class c ); + + private void addDefinition( ClassLoader al, String name, String value ) + throws BuildException + { + try + { + Class c = al.loadClass( value ); + AntClassLoader.initializeClass( c ); + addDefinition( name, c ); + } + catch( ClassNotFoundException cnfe ) + { + String msg = getTaskName() + " class " + value + + " cannot be found"; + throw new BuildException( msg, cnfe, location ); + } + catch( NoClassDefFoundError ncdfe ) + { + String msg = getTaskName() + " class " + value + + " cannot be found"; + throw new BuildException( msg, ncdfe, location ); + } + } + + + private AntClassLoader createLoader() + { + AntClassLoader al = null; + if( classpath != null ) + { + al = new AntClassLoader( project, classpath, !reverseLoader ); + } + else + { + al = new AntClassLoader( project, Path.systemClasspath, !reverseLoader ); + } + // need to load Task via system classloader or the new + // task we want to define will never be a Task but always + // be wrapped into a TaskAdapter. + al.addSystemPackageRoot( "org.apache.tools.ant" ); + return al; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Delete.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Delete.java new file mode 100644 index 000000000..e7df0b543 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Delete.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + +/** + * Deletes a file or directory, or set of files defined by a fileset. The + * original delete task would delete a file, or a set of files using the + * include/exclude syntax. The deltree task would delete a directory tree. This + * task combines the functionality of these two originally distinct tasks.

+ * + * Currently Delete extends MatchingTask. This is intend only to provide + * backwards compatibility for a release. The future position is to use nested + * filesets exclusively.

+ * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Tom Dimock tad1@cornell.edu + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Jon S. Stevens jon@latchkey.com + */ +public class Delete extends MatchingTask +{ + protected File file = null; + protected File dir = null; + protected Vector filesets = new Vector(); + protected boolean usedMatchingTask = false; + protected boolean includeEmpty = false;// by default, remove matching empty dirs + + private int verbosity = Project.MSG_VERBOSE; + private boolean quiet = false; + private boolean failonerror = true; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + usedMatchingTask = true; + super.setDefaultexcludes( useDefaultExcludes ); + } + + /** + * Set the directory from which files are to be deleted + * + * @param dir the directory path. + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + usedMatchingTask = true; + super.setExcludes( excludes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param excludesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setExcludesfile( File excludesfile ) + { + usedMatchingTask = true; + super.setExcludesfile( excludesfile ); + } + + /** + * this flag means 'note errors to the output, but keep going' + * + * @param failonerror true or false + */ + public void setFailOnError( boolean failonerror ) + { + this.failonerror = failonerror; + } + + /** + * Set the name of a single file to be removed. + * + * @param file the file to be deleted + */ + public void setFile( File file ) + { + this.file = file; + } + + + /** + * Used to delete empty directories. + * + * @param includeEmpty The new IncludeEmptyDirs value + */ + public void setIncludeEmptyDirs( boolean includeEmpty ) + { + this.includeEmpty = includeEmpty; + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + usedMatchingTask = true; + super.setIncludes( includes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param includesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setIncludesfile( File includesfile ) + { + usedMatchingTask = true; + super.setIncludesfile( includesfile ); + } + + /** + * If the file does not exist, do not display a diagnostic message or modify + * the exit status to reflect an error. This means that if a file or + * directory cannot be deleted, then no error is reported. This setting + * emulates the -f option to the Unix "rm" command. Default is + * false meaning things are "noisy" + * + * @param quiet "true" or "on" + */ + public void setQuiet( boolean quiet ) + { + this.quiet = quiet; + if( quiet ) + { + this.failonerror = false; + } + } + + /** + * Used to force listing of all names of deleted files. + * + * @param verbose "true" or "on" + */ + public void setVerbose( boolean verbose ) + { + if( verbose ) + { + this.verbosity = Project.MSG_INFO; + } + else + { + this.verbosity = Project.MSG_VERBOSE; + } + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + usedMatchingTask = true; + return super.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + usedMatchingTask = true; + return super.createInclude(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + usedMatchingTask = true; + return super.createPatternSet(); + } + + /** + * Delete the file(s). + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( usedMatchingTask ) + { + log( "DEPRECATED - Use of the implicit FileSet is deprecated. Use a nested fileset element instead." ); + } + + if( file == null && dir == null && filesets.size() == 0 ) + { + throw new BuildException( "At least one of the file or dir attributes, or a fileset element, must be set." ); + } + + if( quiet && failonerror ) + { + throw new BuildException( "quiet and failonerror cannot both be set to true", + location ); + } + + // delete the single file + if( file != null ) + { + if( file.exists() ) + { + if( file.isDirectory() ) + { + log( "Directory " + file.getAbsolutePath() + " cannot be removed using the file attribute. Use dir instead." ); + } + else + { + log( "Deleting: " + file.getAbsolutePath() ); + + if( !file.delete() ) + { + String message = "Unable to delete file " + file.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + else + { + log( "Could not find file " + file.getAbsolutePath() + " to delete.", + Project.MSG_VERBOSE ); + } + } + + // delete the directory + if( dir != null && dir.exists() && dir.isDirectory() && !usedMatchingTask ) + { + /* + * If verbosity is MSG_VERBOSE, that mean we are doing regular logging + * (backwards as that sounds). In that case, we want to print one + * message about deleting the top of the directory tree. Otherwise, + * the removeDir method will handle messages for _all_ directories. + */ + if( verbosity == Project.MSG_VERBOSE ) + { + log( "Deleting directory " + dir.getAbsolutePath() ); + } + removeDir( dir ); + } + + // delete the files in the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + try + { + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] files = ds.getIncludedFiles(); + String[] dirs = ds.getIncludedDirectories(); + removeFiles( fs.getDir( project ), files, dirs ); + } + catch( BuildException be ) + { + // directory doesn't exist or is not readable + if( failonerror ) + { + throw be; + } + else + { + log( be.getMessage(), + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + + // delete the files from the default fileset + if( usedMatchingTask && dir != null ) + { + try + { + DirectoryScanner ds = super.getDirectoryScanner( dir ); + String[] files = ds.getIncludedFiles(); + String[] dirs = ds.getIncludedDirectories(); + removeFiles( dir, files, dirs ); + } + catch( BuildException be ) + { + // directory doesn't exist or is not readable + if( failonerror ) + { + throw be; + } + else + { + log( be.getMessage(), + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + protected void removeDir( File d ) + { + String[] list = d.list(); + if( list == null ) + list = new String[0]; + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + removeDir( f ); + } + else + { + log( "Deleting " + f.getAbsolutePath(), verbosity ); + if( !f.delete() ) + { + String message = "Unable to delete file " + f.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + log( "Deleting directory " + d.getAbsolutePath(), verbosity ); + if( !d.delete() ) + { + String message = "Unable to delete directory " + dir.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + + /** + * remove an array of files in a directory, and a list of subdirectories + * which will only be deleted if 'includeEmpty' is true + * + * @param d directory to work from + * @param files array of files to delete; can be of zero length + * @param dirs array of directories to delete; can of zero length + */ + protected void removeFiles( File d, String[] files, String[] dirs ) + { + if( files.length > 0 ) + { + log( "Deleting " + files.length + " files from " + d.getAbsolutePath() ); + for( int j = 0; j < files.length; j++ ) + { + File f = new File( d, files[j] ); + log( "Deleting " + f.getAbsolutePath(), verbosity ); + if( !f.delete() ) + { + String message = "Unable to delete file " + f.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + + if( dirs.length > 0 && includeEmpty ) + { + int dirCount = 0; + for( int j = dirs.length - 1; j >= 0; j-- ) + { + File dir = new File( d, dirs[j] ); + String[] dirFiles = dir.list(); + if( dirFiles == null || dirFiles.length == 0 ) + { + log( "Deleting " + dir.getAbsolutePath(), verbosity ); + if( !dir.delete() ) + { + String message = "Unable to delete directory " + + dir.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + else + { + dirCount++; + } + } + } + + if( dirCount > 0 ) + { + log( "Deleted " + dirCount + " director" + + ( dirCount == 1 ? "y" : "ies" ) + + " from " + d.getAbsolutePath() ); + } + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Deltree.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Deltree.java new file mode 100644 index 000000000..f6e746e7b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Deltree.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * @author duncan@x180.com + * @deprecated The deltree task is deprecated. Use delete instead. + */ + +public class Deltree extends Task +{ + + private File dir; + + public void setDir( File dir ) + { + this.dir = dir; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The deltree task is deprecated. Use delete instead." ); + + if( dir == null ) + { + throw new BuildException( "dir attribute must be set!", location ); + } + + if( dir.exists() ) + { + if( !dir.isDirectory() ) + { + if( !dir.delete() ) + { + throw new BuildException( "Unable to delete directory " + + dir.getAbsolutePath(), + location ); + } + return; + // String msg = "Given dir: " + dir.getAbsolutePath() + + // " is not a dir"; + // throw new BuildException(msg); + } + + log( "Deleting: " + dir.getAbsolutePath() ); + + try + { + removeDir( dir ); + } + catch( IOException ioe ) + { + String msg = "Unable to delete " + dir.getAbsolutePath(); + throw new BuildException( msg, location ); + } + } + } + + private void removeDir( File dir ) + throws IOException + { + + // check to make sure that the given dir isn't a symlink + // the comparison of absolute path and canonical path + // catches this + + // if (dir.getCanonicalPath().equals(dir.getAbsolutePath())) { + // (costin) It will not work if /home/costin is symlink to /da0/home/costin ( taz + // for example ) + String[] list = dir.list(); + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( dir, s ); + if( f.isDirectory() ) + { + removeDir( f ); + } + else + { + if( !f.delete() ) + { + throw new BuildException( "Unable to delete file " + f.getAbsolutePath() ); + } + } + } + if( !dir.delete() ) + { + throw new BuildException( "Unable to delete directory " + dir.getAbsolutePath() ); + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/DependSet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/DependSet.java new file mode 100644 index 000000000..0768f88e9 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/DependSet.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Date; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.FileList; +import org.apache.tools.ant.types.FileSet; + +/** + * A Task to record explicit dependencies. If any of the target files are out of + * date with respect to any of the source files, all target files are removed. + * This is useful where dependencies cannot be computed (for example, + * dynamically interpreted parameters or files that need to stay in synch but + * are not directly linked) or where the ant task in question could compute them + * but does not (for example, the linked DTD for an XML file using the style + * task). nested arguments: + *
    + *
  • srcfileset (fileset describing the source files to examine) + *
  • srcfilelist (filelist describing the source files to examine) + *
  • targetfileset (fileset describing the target files to examine) + *
  • targetfilelist (filelist describing the target files to examine) + *
+ * At least one instance of either a fileset or filelist for both source and + * target are required.

+ * + * This task will examine each of the source files against each of the target + * files. If any target files are out of date with respect to any of the source + * files, all targets are removed. If any files named in a (src or target) + * filelist do not exist, all targets are removed. Hint: If missing files should + * be ignored, specify them as include patterns in filesets, rather than using + * filelists.

+ * + * This task attempts to optimize speed of dependency checking. It will stop + * after the first out of date file is found and remove all targets, rather than + * exhaustively checking every source vs target combination unnecessarily.

+ *

+ * + * Example uses: + *

    + *
  • Record the fact that an XML file must be up to date with respect to + * its XSD (Schema file), even though the XML file itself includes no + * reference to its XSD.
  • + *
  • Record the fact that an XSL stylesheet includes other sub-stylesheets + *
  • + *
  • Record the fact that java files must be recompiled if the ant build + * file changes
  • + *
+ * + * + * @author Craeg Strong + * @version $Revision$ $Date$ + */ +public class DependSet extends MatchingTask +{ + + private Vector sourceFileSets = new Vector(); + private Vector sourceFileLists = new Vector(); + private Vector targetFileSets = new Vector(); + private Vector targetFileLists = new Vector(); + + /** + * Creates a new DependSet Task. + */ + public DependSet() { } + + /** + * Nested <srcfilelist> element. + * + * @param fl The feature to be added to the Srcfilelist attribute + */ + public void addSrcfilelist( FileList fl ) + { + sourceFileLists.addElement( fl ); + }//-- DependSet + + /** + * Nested <srcfileset> element. + * + * @param fs The feature to be added to the Srcfileset attribute + */ + public void addSrcfileset( FileSet fs ) + { + sourceFileSets.addElement( fs ); + } + + /** + * Nested <targetfilelist> element. + * + * @param fl The feature to be added to the Targetfilelist attribute + */ + public void addTargetfilelist( FileList fl ) + { + targetFileLists.addElement( fl ); + } + + /** + * Nested <targetfileset> element. + * + * @param fs The feature to be added to the Targetfileset attribute + */ + public void addTargetfileset( FileSet fs ) + { + targetFileSets.addElement( fs ); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + + public void execute() + throws BuildException + { + + if( ( sourceFileSets.size() == 0 ) && ( sourceFileLists.size() == 0 ) ) + { + throw new BuildException( "At least one or element must be set" ); + } + + if( ( targetFileSets.size() == 0 ) && ( targetFileLists.size() == 0 ) ) + { + throw new BuildException( "At least one or element must be set" ); + } + + long now = ( new Date() ).getTime(); + /* + * If we're on Windows, we have to munge the time up to 2 secs to + * be able to check file modification times. + * (Windows has a max resolution of two secs for modification times) + */ + if( Os.isFamily( "windows" ) ) + { + now += 2000; + } + + // + // Grab all the target files specified via filesets + // + Vector allTargets = new Vector(); + Enumeration enumTargetSets = targetFileSets.elements(); + while( enumTargetSets.hasMoreElements() ) + { + + FileSet targetFS = ( FileSet )enumTargetSets.nextElement(); + DirectoryScanner targetDS = targetFS.getDirectoryScanner( project ); + String[] targetFiles = targetDS.getIncludedFiles(); + + for( int i = 0; i < targetFiles.length; i++ ) + { + + File dest = new File( targetFS.getDir( project ), targetFiles[i] ); + allTargets.addElement( dest ); + + if( dest.lastModified() > now ) + { + log( "Warning: " + targetFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + } + } + + // + // Grab all the target files specified via filelists + // + boolean upToDate = true; + Enumeration enumTargetLists = targetFileLists.elements(); + while( enumTargetLists.hasMoreElements() ) + { + + FileList targetFL = ( FileList )enumTargetLists.nextElement(); + String[] targetFiles = targetFL.getFiles( project ); + + for( int i = 0; i < targetFiles.length; i++ ) + { + + File dest = new File( targetFL.getDir( project ), targetFiles[i] ); + if( !dest.exists() ) + { + log( targetFiles[i] + " does not exist.", Project.MSG_VERBOSE ); + upToDate = false; + continue; + } + else + { + allTargets.addElement( dest ); + } + if( dest.lastModified() > now ) + { + log( "Warning: " + targetFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + } + } + + // + // Check targets vs source files specified via filesets + // + if( upToDate ) + { + Enumeration enumSourceSets = sourceFileSets.elements(); + while( upToDate && enumSourceSets.hasMoreElements() ) + { + + FileSet sourceFS = ( FileSet )enumSourceSets.nextElement(); + DirectoryScanner sourceDS = sourceFS.getDirectoryScanner( project ); + String[] sourceFiles = sourceDS.getIncludedFiles(); + + for( int i = 0; upToDate && i < sourceFiles.length; i++ ) + { + File src = new File( sourceFS.getDir( project ), sourceFiles[i] ); + + if( src.lastModified() > now ) + { + log( "Warning: " + sourceFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + + Enumeration enumTargets = allTargets.elements(); + while( upToDate && enumTargets.hasMoreElements() ) + { + + File dest = ( File )enumTargets.nextElement(); + if( src.lastModified() > dest.lastModified() ) + { + log( dest.getPath() + " is out of date with respect to " + + sourceFiles[i], Project.MSG_VERBOSE ); + upToDate = false; + + } + } + } + } + } + + // + // Check targets vs source files specified via filelists + // + if( upToDate ) + { + Enumeration enumSourceLists = sourceFileLists.elements(); + while( upToDate && enumSourceLists.hasMoreElements() ) + { + + FileList sourceFL = ( FileList )enumSourceLists.nextElement(); + String[] sourceFiles = sourceFL.getFiles( project ); + + int i = 0; + do + { + File src = new File( sourceFL.getDir( project ), sourceFiles[i] ); + + if( src.lastModified() > now ) + { + log( "Warning: " + sourceFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + + if( !src.exists() ) + { + log( sourceFiles[i] + " does not exist.", Project.MSG_VERBOSE ); + upToDate = false; + break; + } + + Enumeration enumTargets = allTargets.elements(); + while( upToDate && enumTargets.hasMoreElements() ) + { + + File dest = ( File )enumTargets.nextElement(); + + if( src.lastModified() > dest.lastModified() ) + { + log( dest.getPath() + " is out of date with respect to " + + sourceFiles[i], Project.MSG_VERBOSE ); + upToDate = false; + + } + } + }while ( upToDate && ( ++i < sourceFiles.length ) ); + } + } + + if( !upToDate ) + { + log( "Deleting all target files. ", Project.MSG_VERBOSE ); + for( Enumeration e = allTargets.elements(); e.hasMoreElements(); ) + { + File fileToRemove = ( File )e.nextElement(); + log( "Deleting file " + fileToRemove.getAbsolutePath(), Project.MSG_VERBOSE ); + fileToRemove.delete(); + } + } + + }//-- execute + +}//-- DependSet.java diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ear.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ear.java new file mode 100644 index 000000000..955cbb6ca --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ear.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + + +/** + * Creates a EAR archive. Based on WAR task + * + * @author Stefan Bodewig + * @author Les Hughes + */ +public class Ear extends Jar +{ + + private File deploymentDescriptor; + private boolean descriptorAdded; + + public Ear() + { + super(); + archiveType = "ear"; + emptyBehavior = "create"; + } + + public void setAppxml( File descr ) + { + deploymentDescriptor = descr; + if( !deploymentDescriptor.exists() ) + throw new BuildException( "Deployment descriptor: " + deploymentDescriptor + " does not exist." ); + + // Create a ZipFileSet for this file, and pass it up. + ZipFileSet fs = new ZipFileSet(); + fs.setDir( new File( deploymentDescriptor.getParent() ) ); + fs.setIncludes( deploymentDescriptor.getName() ); + fs.setFullpath( "META-INF/application.xml" ); + super.addFileset( fs ); + } + + public void setEarfile( File earFile ) + { + log( "DEPRECATED - The earfile attribute is deprecated. Use file attribute instead." ); + setFile( earFile ); + } + + + public void addArchives( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + // Do we need to do this? LH + log( "addArchives called", Project.MSG_DEBUG ); + fs.setPrefix( "/" ); + super.addFileset( fs ); + } + + /** + * Make sure we don't think we already have a web.xml next time this task + * gets executed. + */ + protected void cleanUp() + { + descriptorAdded = false; + super.cleanUp(); + } + + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + // If no webxml file is specified, it's an error. + if( deploymentDescriptor == null && !isInUpdateMode() ) + { + throw new BuildException( "appxml attribute is required", location ); + } + + super.initZipOutputStream( zOut ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is WEB-INF/web.xml, we warn if it's not the + // one specified in the "webxml" attribute - or if it's being added twice, + // meaning the same file is specified by the "webxml" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "META-INF/aplication.xml" ) ) + { + if( deploymentDescriptor == null || !deploymentDescriptor.equals( file ) || descriptorAdded ) + { + log( "Warning: selected " + archiveType + " files include a META-INF/application.xml which will be ignored " + + "(please use appxml attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + descriptorAdded = true; + } + } + else + { + super.zipFile( file, zOut, vPath ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Echo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Echo.java new file mode 100644 index 000000000..1e57b259a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Echo.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Echo + * + * @author costin@dnt.ro + */ +public class Echo extends Task +{ + protected String message = "";// required + protected File file = null; + protected boolean append = false; + + // by default, messages are always displayed + protected int logLevel = Project.MSG_WARN; + + /** + * Shall we append to an existing file? + * + * @param append The new Append value + */ + public void setAppend( boolean append ) + { + this.append = append; + } + + /** + * Sets the file attribute. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Set the logging level to one of + *
    + *
  • error
  • + *
  • warning
  • + *
  • info
  • + *
  • verbose
  • + *
  • debug
  • + *

      + * + * The default is "warning" to ensure that messages are + * displayed by default when using the -quiet command line option.

      + * + * @param echoLevel The new Level value + */ + public void setLevel( EchoLevel echoLevel ) + { + String option = echoLevel.getValue(); + if( option.equals( "error" ) ) + { + logLevel = Project.MSG_ERR; + } + else if( option.equals( "warning" ) ) + { + logLevel = Project.MSG_WARN; + } + else if( option.equals( "info" ) ) + { + logLevel = Project.MSG_INFO; + } + else if( option.equals( "verbose" ) ) + { + logLevel = Project.MSG_VERBOSE; + } + else + { + // must be "debug" + logLevel = Project.MSG_DEBUG; + } + } + + /** + * Sets the message variable. + * + * @param msg Sets the value for the message variable. + */ + public void setMessage( String msg ) + { + this.message = msg; + } + + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + /** + * Does the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( file == null ) + { + log( message, logLevel ); + } + else + { + FileWriter out = null; + try + { + out = new FileWriter( file.getAbsolutePath(), append ); + out.write( message, 0, message.length() ); + } + catch( IOException ioe ) + { + throw new BuildException( ioe); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + } + } + } + + public static class EchoLevel extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"error", "warning", "info", "verbose", "debug"}; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exec.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exec.java new file mode 100644 index 000000000..076de6fde --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exec.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Executes a given command if the os platform is appropriate. + * + * @author duncan@x180.com + * @author rubys@us.ibm.com + * @deprecated Instead of using this class, please extend ExecTask or delegate + * to Execute. + */ +public class Exec extends Task +{ + + private final static int BUFFER_SIZE = 512; + protected PrintWriter fos = null; + private boolean failOnError = false; + private String command; + private File dir; + private String os; + private String out; + + public void setCommand( String command ) + { + this.command = command; + } + + public void setDir( String d ) + { + this.dir = project.resolveFile( d ); + } + + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + public void setOs( String os ) + { + this.os = os; + } + + public void setOutput( String out ) + { + this.out = out; + } + + public void execute() + throws BuildException + { + run( command ); + } + + protected void logFlush() + { + if( fos != null ) + fos.close(); + } + + protected void outputLog( String line, int messageLevel ) + { + if( fos == null ) + { + log( line, messageLevel ); + } + else + { + fos.println( line ); + } + } + + protected int run( String command ) + throws BuildException + { + + int err = -1;// assume the worst + + // test if os match + String myos = System.getProperty( "os.name" ); + log( "Myos = " + myos, Project.MSG_VERBOSE ); + if( ( os != null ) && ( os.indexOf( myos ) < 0 ) ) + { + // this command will be executed only on the specified OS + log( "Not found in " + os, Project.MSG_VERBOSE ); + return 0; + } + + // default directory to the project's base directory + if( dir == null ) + dir = project.getBaseDir(); + + if( myos.toLowerCase().indexOf( "windows" ) >= 0 ) + { + if( !dir.equals( project.resolveFile( "." ) ) ) + { + if( myos.toLowerCase().indexOf( "nt" ) >= 0 ) + { + command = "cmd /c cd " + dir + " && " + command; + } + else + { + String ant = project.getProperty( "ant.home" ); + if( ant == null ) + { + throw new BuildException( "Property 'ant.home' not found", location ); + } + + String antRun = project.resolveFile( ant + "/bin/antRun.bat" ).toString(); + command = antRun + " " + dir + " " + command; + } + } + } + else + { + String ant = project.getProperty( "ant.home" ); + if( ant == null ) + throw new BuildException( "Property 'ant.home' not found", location ); + String antRun = project.resolveFile( ant + "/bin/antRun" ).toString(); + + command = antRun + " " + dir + " " + command; + } + + try + { + // show the command + log( command, Project.MSG_VERBOSE ); + + // exec command on system runtime + Process proc = Runtime.getRuntime().exec( command ); + + if( out != null ) + { + fos = new PrintWriter( new FileWriter( out ) ); + log( "Output redirected to " + out, Project.MSG_VERBOSE ); + } + + // copy input and error to the output stream + StreamPumper inputPumper = + new StreamPumper( proc.getInputStream(), Project.MSG_INFO, this ); + StreamPumper errorPumper = + new StreamPumper( proc.getErrorStream(), Project.MSG_WARN, this ); + + // starts pumping away the generated output/error + inputPumper.start(); + errorPumper.start(); + + // Wait for everything to finish + proc.waitFor(); + inputPumper.join(); + errorPumper.join(); + proc.destroy(); + + // close the output file if required + logFlush(); + + // check its exit value + err = proc.exitValue(); + if( err != 0 ) + { + if( failOnError ) + { + throw new BuildException( "Exec returned: " + err, location ); + } + else + { + log( "Result: " + err, Project.MSG_ERR ); + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Error exec: " + command, ioe, location ); + } + catch( InterruptedException ex ) + {} + + return err; + } + + // Inner class for continually pumping the input stream during + // Process's runtime. + class StreamPumper extends Thread + { + private boolean endOfStream = false; + private int SLEEP_TIME = 5; + private BufferedReader din; + private int messageLevel; + private Exec parent; + + public StreamPumper( InputStream is, int messageLevel, Exec parent ) + { + this.din = new BufferedReader( new InputStreamReader( is ) ); + this.messageLevel = messageLevel; + this.parent = parent; + } + + public void pumpStream() + throws IOException + { + byte[] buf = new byte[BUFFER_SIZE]; + if( !endOfStream ) + { + String line = din.readLine(); + + if( line != null ) + { + outputLog( line, messageLevel ); + } + else + { + endOfStream = true; + } + } + } + + public void run() + { + try + { + try + { + while( !endOfStream ) + { + pumpStream(); + sleep( SLEEP_TIME ); + } + } + catch( InterruptedException ie ) + {} + din.close(); + } + catch( IOException ioe ) + {} + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecTask.java new file mode 100644 index 000000000..f236d8eae --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecTask.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringReader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Environment; + +/** + * Executes a given command if the os platform is appropriate. + * + * @author duncan@x180.com + * @author rubys@us.ibm.com + * @author thomas.haas@softwired-inc.com + * @author Stefan Bodewig + * @author Mariusz Nowostawski + */ +public class ExecTask extends Task +{ + + private static String lSep = System.getProperty( "line.separator" ); + protected boolean failOnError = false; + protected boolean newEnvironment = false; + private Integer timeout = null; + private Environment env = new Environment(); + protected Commandline cmdl = new Commandline(); + private FileOutputStream fos = null; + private ByteArrayOutputStream baos = null; + private boolean failIfExecFails = true; + + /** + * Controls whether the VM (1.3 and above) is used to execute the command + */ + private boolean vmLauncher = true; + private File dir; + + private String os; + private File out; + private String outputprop; + private String resultProperty; + + /** + * The full commandline to execute, executable + arguments. + * + * @param cmdl The new Command value + */ + public void setCommand( Commandline cmdl ) + { + log( "The command attribute is deprecated. " + + "Please use the executable attribute and nested arg elements.", + Project.MSG_WARN ); + this.cmdl = cmdl; + } + + /** + * The working directory of the process + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * The command to execute. + * + * @param value The new Executable value + */ + public void setExecutable( String value ) + { + cmdl.setExecutable( value ); + } + + /** + * ant attribute + * + * @param flag The new FailIfExecutionFails value + */ + public void setFailIfExecutionFails( boolean flag ) + { + failIfExecFails = flag; + } + + /** + * Throw a BuildException if process returns non 0. + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Use a completely new environment + * + * @param newenv The new Newenvironment value + */ + public void setNewenvironment( boolean newenv ) + { + newEnvironment = newenv; + } + + /** + * Only execute the process if os.name is included in this + * string. + * + * @param os The new Os value + */ + public void setOs( String os ) + { + this.os = os; + } + + /** + * File the output of the process is redirected to. + * + * @param out The new Output value + */ + public void setOutput( File out ) + { + this.out = out; + } + + /** + * Property name whose value should be set to the output of the process + * + * @param outputprop The new Outputproperty value + */ + public void setOutputproperty( String outputprop ) + { + this.outputprop = outputprop; + } + + /** + * fill a property in with a result. when no property is defined: failure to + * execute + * + * @param resultProperty The new ResultProperty value + * @since 1.5 + */ + public void setResultProperty( String resultProperty ) + { + this.resultProperty = resultProperty; + } + + /** + * Timeout in milliseconds after which the process will be killed. + * + * @param value The new Timeout value + */ + public void setTimeout( Integer value ) + { + timeout = value; + } + + /** + * Control whether the VM is used to launch the new process or whether the + * OS's shell is used. + * + * @param vmLauncher The new VMLauncher value + */ + public void setVMLauncher( boolean vmLauncher ) + { + this.vmLauncher = vmLauncher; + } + + /** + * Add a nested env element - an environment variable. + * + * @param var The feature to be added to the Env attribute + */ + public void addEnv( Environment.Variable var ) + { + env.addVariable( var ); + } + + /** + * Add a nested arg element - a command line argument. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createArg() + { + return cmdl.createArgument(); + } + + /** + * Do the work. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + checkConfiguration(); + if( isValidOs() ) + { + runExec( prepareExec() ); + } + } + + /** + * Is this the OS the user wanted? + * + * @return The ValidOs value + */ + protected boolean isValidOs() + { + // test if os match + String myos = System.getProperty( "os.name" ); + log( "Current OS is " + myos, Project.MSG_VERBOSE ); + if( ( os != null ) && ( os.indexOf( myos ) < 0 ) ) + { + // this command will be executed only on the specified OS + log( "This OS, " + myos + " was not found in the specified list of valid OSes: " + os, Project.MSG_VERBOSE ); + return false; + } + return true; + } + + /** + * A Utility method for this classes and subclasses to run an Execute + * instance (an external command). + * + * @param exe Description of Parameter + * @exception IOException Description of Exception + */ + protected final void runExecute( Execute exe ) + throws IOException + { + int err = -1;// assume the worst + + err = exe.execute(); + //test for and handle a forced process death + if( exe.killedProcess() ) + { + log( "Timeout: killed the sub-process", Project.MSG_WARN ); + } + maybeSetResultPropertyValue( err ); + if( err != 0 ) + { + if( failOnError ) + { + throw new BuildException( taskType + " returned: " + err, location ); + } + else + { + log( "Result: " + err, Project.MSG_ERR ); + } + } + if( baos != null ) + { + BufferedReader in = + new BufferedReader( new StringReader( baos.toString() ) ); + String line = null; + StringBuffer val = new StringBuffer(); + while( ( line = in.readLine() ) != null ) + { + if( val.length() != 0 ) + { + val.append( lSep ); + } + val.append( line ); + } + project.setNewProperty( outputprop, val.toString() ); + } + } + + /** + * Has the user set all necessary attributes? + * + * @exception BuildException Description of Exception + */ + protected void checkConfiguration() + throws BuildException + { + if( cmdl.getExecutable() == null ) + { + throw new BuildException( "no executable specified", location ); + } + if( dir != null && !dir.exists() ) + { + throw new BuildException( "The directory you specified does not exist" ); + } + if( dir != null && !dir.isDirectory() ) + { + throw new BuildException( "The directory you specified is not a directory" ); + } + } + + /** + * Create the StreamHandler to use with our Execute instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecuteStreamHandler createHandler() + throws BuildException + { + if( out != null ) + { + try + { + fos = new FileOutputStream( out ); + log( "Output redirected to " + out, Project.MSG_VERBOSE ); + return new PumpStreamHandler( fos ); + } + catch( FileNotFoundException fne ) + { + throw new BuildException( "Cannot write to " + out, fne, location ); + } + catch( IOException ioe ) + { + throw new BuildException( "Cannot write to " + out, ioe, location ); + } + } + else if( outputprop != null ) + { + baos = new ByteArrayOutputStream(); + log( "Output redirected to ByteArray", Project.MSG_VERBOSE ); + return new PumpStreamHandler( baos ); + } + else + { + return new LogStreamHandler( this, + Project.MSG_INFO, Project.MSG_WARN ); + } + } + + /** + * Create the Watchdog to kill a runaway process. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + if( timeout == null ) + return null; + return new ExecuteWatchdog( timeout.intValue() ); + } + + /** + * Flush the output stream - if there is one. + */ + protected void logFlush() + { + try + { + if( fos != null ) + fos.close(); + if( baos != null ) + baos.close(); + } + catch( IOException io ) + {} + } + + /** + * helper method to set result property to the passed in value if + * appropriate + * + * @param result Description of Parameter + */ + protected void maybeSetResultPropertyValue( int result ) + { + String res = Integer.toString( result ); + if( resultProperty != null ) + { + project.setNewProperty( resultProperty, res ); + } + } + + /** + * Create an Execute instance with the correct working directory set. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected Execute prepareExec() + throws BuildException + { + // default directory to the project's base directory + if( dir == null ) + dir = project.getBaseDir(); + // show the command + log( cmdl.toString(), Project.MSG_VERBOSE ); + + Execute exe = new Execute( createHandler(), createWatchdog() ); + exe.setAntRun( project ); + exe.setWorkingDirectory( dir ); + exe.setVMLauncher( vmLauncher ); + String[] environment = env.getVariables(); + if( environment != null ) + { + for( int i = 0; i < environment.length; i++ ) + { + log( "Setting environment variable: " + environment[i], + Project.MSG_VERBOSE ); + } + } + exe.setNewenvironment( newEnvironment ); + exe.setEnvironment( environment ); + return exe; + } + + /** + * Run the command using the given Execute instance. This may be overidden + * by subclasses + * + * @param exe Description of Parameter + * @exception BuildException Description of Exception + */ + protected void runExec( Execute exe ) + throws BuildException + { + exe.setCommandline( cmdl.getCommandline() ); + try + { + runExecute( exe ); + } + catch( IOException e ) + { + if( failIfExecFails ) + { + throw new BuildException( "Execute failed: " + e.toString(), e, location ); + } + else + { + log( "Execute failed: " + e.toString(), Project.MSG_ERR ); + } + } + finally + { + // close the output file if required + logFlush(); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Execute.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Execute.java new file mode 100644 index 000000000..c1fdd0c72 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Execute.java @@ -0,0 +1,947 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Locale; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; + +/** + * Runs an external program. + * + * @author thomas.haas@softwired-inc.com + */ +public class Execute +{ + /** + * Invalid exit code. + */ + public final static int INVALID = Integer.MAX_VALUE; + + private static String antWorkingDirectory = System.getProperty( "user.dir" ); + private static CommandLauncher vmLauncher; + private static CommandLauncher shellLauncher; + private static Vector procEnvironment; + + /** + * Used to destroy processes when the VM exits. + */ + private static ProcessDestroyer processDestroyer = new ProcessDestroyer(); + + private String[] cmdl = null; + private String[] env = null; + private int exitValue = INVALID; + private File workingDirectory = null; + private Project project = null; + private boolean newEnvironment = false; + + /** + * Controls whether the VM is used to launch commands, where possible + */ + private boolean useVMLauncher = true; + private ExecuteStreamHandler streamHandler; + private ExecuteWatchdog watchdog; + + /** + * Builds a command launcher for the OS and JVM we are running under + */ + static + { + // Try using a JDK 1.3 launcher + try + { + vmLauncher = new Java13CommandLauncher(); + } + catch( NoSuchMethodException exc ) + { + // Ignore and keep try + } + + if( Os.isFamily( "mac" ) ) + { + // Mac + shellLauncher = new MacCommandLauncher( new CommandLauncher() ); + } + else if( Os.isFamily( "os/2" ) ) + { + // OS/2 - use same mechanism as Windows 2000 + shellLauncher = new WinNTCommandLauncher( new CommandLauncher() ); + } + else if( Os.isFamily( "windows" ) ) + { + // Windows. Need to determine which JDK we're running in + + CommandLauncher baseLauncher; + if( System.getProperty( "java.version" ).startsWith( "1.1" ) ) + { + // JDK 1.1 + baseLauncher = new Java11CommandLauncher(); + } + else + { + // JDK 1.2 + baseLauncher = new CommandLauncher(); + } + + // Determine if we're running under 2000/NT or 98/95 + String osname = + System.getProperty( "os.name" ).toLowerCase( Locale.US ); + + if( osname.indexOf( "nt" ) >= 0 || osname.indexOf( "2000" ) >= 0 ) + { + // Windows 2000/NT + shellLauncher = new WinNTCommandLauncher( baseLauncher ); + } + else + { + // Windows 98/95 - need to use an auxiliary script + shellLauncher = new ScriptCommandLauncher( "bin/antRun.bat", baseLauncher ); + } + } + else if( ( new Os( "netware" ) ).eval() ) + { + // NetWare. Need to determine which JDK we're running in + CommandLauncher baseLauncher; + if( System.getProperty( "java.version" ).startsWith( "1.1" ) ) + { + // JDK 1.1 + baseLauncher = new Java11CommandLauncher(); + } + else + { + // JDK 1.2 + baseLauncher = new CommandLauncher(); + } + + shellLauncher = new PerlScriptCommandLauncher( "bin/antRun.pl", baseLauncher ); + } + else + { + // Generic + shellLauncher = new ScriptCommandLauncher( "bin/antRun", new CommandLauncher() ); + } + } + + /** + * Creates a new execute object using PumpStreamHandler for + * stream handling. + */ + public Execute() + { + this( new PumpStreamHandler(), null ); + } + + /** + * Creates a new execute object. + * + * @param streamHandler the stream handler used to handle the input and + * output streams of the subprocess. + */ + public Execute( ExecuteStreamHandler streamHandler ) + { + this( streamHandler, null ); + } + + /** + * Creates a new execute object. + * + * @param streamHandler the stream handler used to handle the input and + * output streams of the subprocess. + * @param watchdog a watchdog for the subprocess or null to to + * disable a timeout for the subprocess. + */ + public Execute( ExecuteStreamHandler streamHandler, ExecuteWatchdog watchdog ) + { + this.streamHandler = streamHandler; + this.watchdog = watchdog; + } + + /** + * Find the list of environment variables for this process. + * + * @return The ProcEnvironment value + */ + public static synchronized Vector getProcEnvironment() + { + if( procEnvironment != null ) + return procEnvironment; + + procEnvironment = new Vector(); + try + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Execute exe = new Execute( new PumpStreamHandler( out ) ); + exe.setCommandline( getProcEnvCommand() ); + // Make sure we do not recurse forever + exe.setNewenvironment( true ); + int retval = exe.execute(); + if( retval != 0 ) + { + // Just try to use what we got + } + + BufferedReader in = + new BufferedReader( new StringReader( out.toString() ) ); + String var = null; + String line; + String lineSep = System.getProperty( "line.separator" ); + while( ( line = in.readLine() ) != null ) + { + if( line.indexOf( '=' ) == -1 ) + { + // Chunk part of previous env var (UNIX env vars can + // contain embedded new lines). + if( var == null ) + { + var = lineSep + line; + } + else + { + var += lineSep + line; + } + } + else + { + // New env var...append the previous one if we have it. + if( var != null ) + { + procEnvironment.addElement( var ); + } + var = line; + } + } + // Since we "look ahead" before adding, there's one last env var. + procEnvironment.addElement( var ); + } + catch( java.io.IOException exc ) + { + exc.printStackTrace(); + // Just try to see how much we got + } + return procEnvironment; + } + + /** + * A utility method that runs an external command. Writes the output and + * error streams of the command to the project log. + * + * @param task The task that the command is part of. Used for logging + * @param cmdline The command to execute. + * @throws BuildException if the command does not return 0. + */ + public static void runCommand( Task task, String[] cmdline ) + throws BuildException + { + try + { + task.log( Commandline.toString( cmdline ), Project.MSG_VERBOSE ); + Execute exe = new Execute( new LogStreamHandler( task, + Project.MSG_INFO, + Project.MSG_ERR ) ); + exe.setAntRun( task.getProject() ); + exe.setCommandline( cmdline ); + int retval = exe.execute(); + if( retval != 0 ) + { + throw new BuildException( cmdline[ 0 ] + " failed with return code " + retval, task.getLocation() ); + } + } + catch( java.io.IOException exc ) + { + throw new BuildException( "Could not launch " + cmdline[ 0 ] + ": " + exc, task.getLocation() ); + } + } + + private static String[] getProcEnvCommand() + { + if( Os.isFamily( "os/2" ) ) + { + // OS/2 - use same mechanism as Windows 2000 + // Not sure + String[] cmd = {"cmd", "/c", "set"}; + return cmd; + } + else if( Os.isFamily( "windows" ) ) + { + String osname = + System.getProperty( "os.name" ).toLowerCase( Locale.US ); + // Determine if we're running under 2000/NT or 98/95 + if( osname.indexOf( "nt" ) >= 0 || osname.indexOf( "2000" ) >= 0 ) + { + // Windows 2000/NT + String[] cmd = {"cmd", "/c", "set"}; + return cmd; + } + else + { + // Windows 98/95 - need to use an auxiliary script + String[] cmd = {"command.com", "/c", "set"}; + return cmd; + } + } + else if( Os.isFamily( "unix" ) ) + { + // Generic UNIX + // Alternatively one could use: /bin/sh -c env + String[] cmd = {"/usr/bin/env"}; + return cmd; + } + else if( Os.isFamily( "netware" ) ) + { + String[] cmd = {"env"}; + return cmd; + } + else + { + // MAC OS 9 and previous + // TODO: I have no idea how to get it, someone must fix it + String[] cmd = null; + return cmd; + } + } + + /** + * Set the name of the antRun script using the project's value. + * + * @param project the current project. + * @exception BuildException Description of Exception + */ + public void setAntRun( Project project ) + throws BuildException + { + this.project = project; + } + + /** + * Sets the commandline of the subprocess to launch. + * + * @param commandline the commandline of the subprocess to launch + */ + public void setCommandline( String[] commandline ) + { + cmdl = commandline; + } + + /** + * Sets the environment variables for the subprocess to launch. + * + * @param env The new Environment value + */ + public void setEnvironment( String[] env ) + { + this.env = env; + } + + /** + * Set whether to propagate the default environment or not. + * + * @param newenv whether to propagate the process environment. + */ + public void setNewenvironment( boolean newenv ) + { + newEnvironment = newenv; + } + + /** + * Launch this execution through the VM, where possible, rather than through + * the OS's shell. In some cases and operating systems using the shell will + * allow the shell to perform additional processing such as associating an + * executable with a script, etc + * + * @param useVMLauncher The new VMLauncher value + */ + public void setVMLauncher( boolean useVMLauncher ) + { + this.useVMLauncher = useVMLauncher; + } + + /** + * Sets the working directory of the process to execute.

      + * + * This is emulated using the antRun scripts unless the OS is Windows NT in + * which case a cmd.exe is spawned, or MRJ and setting user.dir works, or + * JDK 1.3 and there is official support in java.lang.Runtime. + * + * @param wd the working directory of the process. + */ + public void setWorkingDirectory( File wd ) + { + if( wd == null || wd.getAbsolutePath().equals( antWorkingDirectory ) ) + workingDirectory = null; + else + workingDirectory = wd; + } + + /** + * Returns the commandline used to create a subprocess. + * + * @return the commandline used to create a subprocess + */ + public String[] getCommandline() + { + return cmdl; + } + + /** + * Returns the environment used to create a subprocess. + * + * @return the environment used to create a subprocess + */ + public String[] getEnvironment() + { + if( env == null || newEnvironment ) + return env; + return patchEnvironment(); + } + + /** + * query the exit value of the process. + * + * @return the exit value, 1 if the process was killed, or Project.INVALID + * if no exit value has been received + */ + public int getExitValue() + { + return exitValue; + } + + /** + * Runs a process defined by the command line and returns its exit status. + * + * @return the exit status of the subprocess or INVALID + * @exception IOException Description of Exception + */ + public int execute() + throws IOException + { + CommandLauncher launcher = vmLauncher != null ? vmLauncher : shellLauncher; + if( !useVMLauncher ) + { + launcher = shellLauncher; + } + + final Process process = launcher.exec( project, getCommandline(), getEnvironment(), workingDirectory ); + try + { + streamHandler.setProcessInputStream( process.getOutputStream() ); + streamHandler.setProcessOutputStream( process.getInputStream() ); + streamHandler.setProcessErrorStream( process.getErrorStream() ); + } + catch( IOException e ) + { + process.destroy(); + throw e; + } + streamHandler.start(); + + // add the process to the list of those to destroy if the VM exits + // + processDestroyer.add( process ); + + if( watchdog != null ) + watchdog.start( process ); + waitFor( process ); + + // remove the process to the list of those to destroy if the VM exits + // + processDestroyer.remove( process ); + + if( watchdog != null ) + watchdog.stop(); + streamHandler.stop(); + if( watchdog != null ) + watchdog.checkException(); + return getExitValue(); + } + + /** + * test for an untimely death of the process + * + * @return true iff a watchdog had to kill the process + * @since 1.5 + */ + public boolean killedProcess() + { + return watchdog != null && watchdog.killedProcess(); + } + + protected void setExitValue( int value ) + { + exitValue = value; + } + + protected void waitFor( Process process ) + { + try + { + process.waitFor(); + setExitValue( process.exitValue() ); + } + catch( InterruptedException e ) + { + } + } + + /** + * Patch the current environment with the new values from the user. + * + * @return the patched environment + */ + private String[] patchEnvironment() + { + Vector osEnv = (Vector)getProcEnvironment().clone(); + for( int i = 0; i < env.length; i++ ) + { + int pos = env[ i ].indexOf( '=' ); + // Get key including "=" + String key = env[ i ].substring( 0, pos + 1 ); + int size = osEnv.size(); + for( int j = 0; j < size; j++ ) + { + if( ( (String)osEnv.elementAt( j ) ).startsWith( key ) ) + { + osEnv.removeElementAt( j ); + break; + } + } + osEnv.addElement( env[ i ] ); + } + String[] result = new String[ osEnv.size() ]; + osEnv.copyInto( result ); + return result; + } + + /** + * A command launcher for a particular JVM/OS platform. This class is a + * general purpose command launcher which can only launch commands in the + * current working directory. + * + * @author RT + */ + private static class CommandLauncher + { + /** + * Launches the given command in a new process. + * + * @param project The project that the command is part of + * @param cmd The command to execute + * @param env The environment for the new process. If null, the + * environment of the current proccess is used. + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + if( project != null ) + { + project.log( "Execute:CommandLauncher: " + + Commandline.toString( cmd ), Project.MSG_DEBUG ); + } + return Runtime.getRuntime().exec( cmd, env ); + } + + /** + * Launches the given command in a new process, in the given working + * directory. + * + * @param project The project that the command is part of + * @param cmd The command to execute + * @param env The environment for the new process. If null, the + * environment of the current proccess is used. + * @param workingDir The directory to start the command in. If null, the + * current directory is used + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot execute a process in different directory under this JVM" ); + } + } + + /** + * A command launcher that proxies another command launcher. Sub-classes + * override exec(args, env, workdir) + * + * @author RT + */ + private static class CommandLauncherProxy extends CommandLauncher + { + + private CommandLauncher _launcher; + + CommandLauncherProxy( CommandLauncher launcher ) + { + _launcher = launcher; + } + + /** + * Launches the given command in a new process. Delegates this method to + * the proxied launcher + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + return _launcher.exec( project, cmd, env ); + } + } + + /** + * A command launcher for JDK/JRE 1.1 under Windows. Fixes quoting problems + * in Runtime.exec(). Can only launch commands in the current working + * directory + * + * @author RT + */ + private static class Java11CommandLauncher extends CommandLauncher + { + /** + * Launches the given command in a new process. Needs to quote arguments + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + // Need to quote arguments with spaces, and to escape quote characters + String[] newcmd = new String[ cmd.length ]; + for( int i = 0; i < cmd.length; i++ ) + { + newcmd[ i ] = Commandline.quoteArgument( cmd[ i ] ); + } + if( project != null ) + { + project.log( "Execute:Java11CommandLauncher: " + + Commandline.toString( newcmd ), Project.MSG_DEBUG ); + } + return Runtime.getRuntime().exec( newcmd, env ); + } + } + + /** + * A command launcher for JDK/JRE 1.3 (and higher). Uses the built-in + * Runtime.exec() command + * + * @author RT + */ + private static class Java13CommandLauncher extends CommandLauncher + { + + private Method _execWithCWD; + + public Java13CommandLauncher() + throws NoSuchMethodException + { + // Locate method Runtime.exec(String[] cmdarray, String[] envp, File dir) + _execWithCWD = Runtime.class.getMethod( "exec", new Class[]{String[].class, String[].class, File.class} ); + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + try + { + if( project != null ) + { + project.log( "Execute:Java13CommandLauncher: " + + Commandline.toString( cmd ), Project.MSG_DEBUG ); + } + Object[] arguments = {cmd, env, workingDir}; + return (Process)_execWithCWD.invoke( Runtime.getRuntime(), arguments ); + } + catch( InvocationTargetException exc ) + { + Throwable realexc = exc.getTargetException(); + if( realexc instanceof ThreadDeath ) + { + throw (ThreadDeath)realexc; + } + else if( realexc instanceof IOException ) + { + throw (IOException)realexc; + } + else + { + throw new BuildException( "Unable to execute command", realexc ); + } + } + catch( Exception exc ) + { + // IllegalAccess, IllegalArgument, ClassCast + throw new BuildException( "Unable to execute command", exc ); + } + } + } + + /** + * A command launcher for Mac that uses a dodgy mechanism to change working + * directory before launching commands. + * + * @author RT + */ + private static class MacCommandLauncher extends CommandLauncherProxy + { + MacCommandLauncher( CommandLauncher launcher ) + { + super( launcher ); + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + + System.getProperties().put( "user.dir", workingDir.getAbsolutePath() ); + try + { + return exec( project, cmd, env ); + } + finally + { + System.getProperties().put( "user.dir", antWorkingDirectory ); + } + } + } + + /** + * A command launcher that uses an auxiliary perl script to launch commands + * in directories other than the current working directory. + * + * @author RT + */ + private static class PerlScriptCommandLauncher extends CommandLauncherProxy + { + + private String _script; + + PerlScriptCommandLauncher( String script, CommandLauncher launcher ) + { + super( launcher ); + _script = script; + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( project == null ) + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot locate antRun script: No project provided" ); + } + + // Locate the auxiliary script + String antHome = project.getProperty( "ant.home" ); + if( antHome == null ) + { + throw new IOException( "Cannot locate antRun script: Property 'ant.home' not found" ); + } + String antRun = project.resolveFile( antHome + File.separator + _script ).toString(); + + // Build the command + File commandDir = workingDir; + if( workingDir == null && project != null ) + { + commandDir = project.getBaseDir(); + } + + String[] newcmd = new String[ cmd.length + 3 ]; + newcmd[ 0 ] = "perl"; + newcmd[ 1 ] = antRun; + newcmd[ 2 ] = commandDir.getAbsolutePath(); + System.arraycopy( cmd, 0, newcmd, 3, cmd.length ); + + return exec( project, newcmd, env ); + } + } + + /** + * A command launcher that uses an auxiliary script to launch commands in + * directories other than the current working directory. + * + * @author RT + */ + private static class ScriptCommandLauncher extends CommandLauncherProxy + { + + private String _script; + + ScriptCommandLauncher( String script, CommandLauncher launcher ) + { + super( launcher ); + _script = script; + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( project == null ) + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot locate antRun script: No project provided" ); + } + + // Locate the auxiliary script + String antHome = project.getProperty( "ant.home" ); + if( antHome == null ) + { + throw new IOException( "Cannot locate antRun script: Property 'ant.home' not found" ); + } + String antRun = project.resolveFile( antHome + File.separator + _script ).toString(); + + // Build the command + File commandDir = workingDir; + if( workingDir == null && project != null ) + { + commandDir = project.getBaseDir(); + } + + String[] newcmd = new String[ cmd.length + 2 ]; + newcmd[ 0 ] = antRun; + newcmd[ 1 ] = commandDir.getAbsolutePath(); + System.arraycopy( cmd, 0, newcmd, 2, cmd.length ); + + return exec( project, newcmd, env ); + } + } + + /** + * A command launcher for Windows 2000/NT that uses 'cmd.exe' when launching + * commands in directories other than the current working directory. + * + * @author RT + */ + private static class WinNTCommandLauncher extends CommandLauncherProxy + { + WinNTCommandLauncher( CommandLauncher launcher ) + { + super( launcher ); + } + + /** + * Launches the given command in a new process, in the given working + * directory. + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + File commandDir = workingDir; + if( workingDir == null ) + { + if( project != null ) + { + commandDir = project.getBaseDir(); + } + else + { + return exec( project, cmd, env ); + } + } + + // Use cmd.exe to change to the specified directory before running + // the command + final int preCmdLength = 6; + String[] newcmd = new String[ cmd.length + preCmdLength ]; + newcmd[ 0 ] = "cmd"; + newcmd[ 1 ] = "/c"; + newcmd[ 2 ] = "cd"; + newcmd[ 3 ] = "/d"; + newcmd[ 4 ] = commandDir.getAbsolutePath(); + newcmd[ 5 ] = "&&"; + System.arraycopy( cmd, 0, newcmd, preCmdLength, cmd.length ); + + return exec( project, newcmd, env ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteJava.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteJava.java new file mode 100644 index 000000000..6fc077991 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteJava.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/* + * @author thomas.haas@softwired-inc.com + * @author Stefan Bodewig + */ +public class ExecuteJava +{ + + private Commandline javaCommand = null; + private Path classpath = null; + private CommandlineJava.SysProperties sysProperties = null; + + public void setClasspath( Path p ) + { + classpath = p; + } + + public void setJavaCommand( Commandline javaCommand ) + { + this.javaCommand = javaCommand; + } + + /** + * All output (System.out as well as System.err) will be written to this + * Stream. + * + * @param out The new Output value + * @deprecated manage output at the task level + */ + public void setOutput( PrintStream out ) { } + + public void setSystemProperties( CommandlineJava.SysProperties s ) + { + sysProperties = s; + } + + public void execute( Project project ) + throws BuildException + { + final String classname = javaCommand.getExecutable(); + final Object[] argument = {javaCommand.getArguments()}; + + AntClassLoader loader = null; + try + { + if( sysProperties != null ) + { + sysProperties.setSystem(); + } + + final Class[] param = {Class.forName( "[Ljava.lang.String;" )}; + Class target = null; + if( classpath == null ) + { + target = Class.forName( classname ); + } + else + { + loader = new AntClassLoader( project.getCoreLoader(), project, classpath, false ); + loader.setIsolated( true ); + loader.setThreadContextLoader(); + target = loader.forceLoadClass( classname ); + AntClassLoader.initializeClass( target ); + } + final Method main = target.getMethod( "main", param ); + main.invoke( null, argument ); + } + catch( NullPointerException e ) + { + throw new BuildException( "Could not find main() method in " + classname ); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( "Could not find " + classname + ". Make sure you have it in your classpath" ); + } + catch( InvocationTargetException e ) + { + Throwable t = e.getTargetException(); + if( !( t instanceof SecurityException ) ) + { + throw new BuildException( t ); + } + else + { + throw ( SecurityException )t; + } + } + catch( Exception e ) + { + throw new BuildException( e ); + } + finally + { + if( loader != null ) + { + loader.resetThreadContextLoader(); + loader.cleanup(); + } + if( sysProperties != null ) + { + sysProperties.restoreSystem(); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteOn.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteOn.java new file mode 100644 index 000000000..0a8bd9d53 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteOn.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Executes a given command, supplying a set of files as arguments. + * + * @author Stefan Bodewig + * @author Mariusz Nowostawski + */ +public class ExecuteOn extends ExecTask +{ + + protected Vector filesets = new Vector(); + private boolean relative = false; + private boolean parallel = false; + protected String type = "file"; + protected Commandline.Marker srcFilePos = null; + private boolean skipEmpty = false; + protected Commandline.Marker targetFilePos = null; + protected Mapper mapperElement = null; + protected FileNameMapper mapper = null; + protected File destDir = null; + + /** + * Has <srcfile> been specified before <targetfile> + */ + protected boolean srcIsFirst = true; + + /** + * Set the destination directory. + * + * @param destDir The new Dest value + */ + public void setDest( File destDir ) + { + this.destDir = destDir; + } + + + /** + * Shall the command work on all specified files in parallel? + * + * @param parallel The new Parallel value + */ + public void setParallel( boolean parallel ) + { + this.parallel = parallel; + } + + /** + * Should filenames be returned as relative path names? + * + * @param relative The new Relative value + */ + public void setRelative( boolean relative ) + { + this.relative = relative; + } + + /** + * Should empty filesets be ignored? + * + * @param skip The new SkipEmptyFilesets value + */ + public void setSkipEmptyFilesets( boolean skip ) + { + skipEmpty = skip; + } + + /** + * Shall the command work only on files, directories or both? + * + * @param type The new Type value + */ + public void setType( FileDirBoth type ) + { + this.type = type.getValue(); + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Marker that indicates where the name of the source file should be put on + * the command line. + * + * @return Description of the Returned Value + */ + public Commandline.Marker createSrcfile() + { + if( srcFilePos != null ) + { + throw new BuildException( taskType + " doesn\'t support multiple srcfile elements.", + location ); + } + srcFilePos = cmdl.createMarker(); + return srcFilePos; + } + + /** + * Marker that indicates where the name of the target file should be put on + * the command line. + * + * @return Description of the Returned Value + */ + public Commandline.Marker createTargetfile() + { + if( targetFilePos != null ) + { + throw new BuildException( taskType + " doesn\'t support multiple targetfile elements.", + location ); + } + targetFilePos = cmdl.createMarker(); + srcIsFirst = ( srcFilePos != null ); + return targetFilePos; + } + + /** + * Construct the command line for parallel execution. + * + * @param srcFiles The filenames to add to the commandline + * @param baseDirs Description of Parameter + * @return The Commandline value + */ + protected String[] getCommandline( String[] srcFiles, File[] baseDirs ) + { + Vector targets = new Vector(); + if( targetFilePos != null ) + { + Hashtable addedFiles = new Hashtable(); + for( int i = 0; i < srcFiles.length; i++ ) + { + String[] subTargets = mapper.mapFileName( srcFiles[i] ); + if( subTargets != null ) + { + for( int j = 0; j < subTargets.length; j++ ) + { + String name = null; + if( !relative ) + { + name = + ( new File( destDir, subTargets[j] ) ).getAbsolutePath(); + } + else + { + name = subTargets[j]; + } + if( !addedFiles.contains( name ) ) + { + targets.addElement( name ); + addedFiles.put( name, name ); + } + } + } + } + } + String[] targetFiles = new String[targets.size()]; + targets.copyInto( targetFiles ); + + String[] orig = cmdl.getCommandline(); + String[] result = new String[orig.length + srcFiles.length + targetFiles.length]; + + int srcIndex = orig.length; + if( srcFilePos != null ) + { + srcIndex = srcFilePos.getPosition(); + } + + if( targetFilePos != null ) + { + int targetIndex = targetFilePos.getPosition(); + + if( srcIndex < targetIndex + || ( srcIndex == targetIndex && srcIsFirst ) ) + { + + // 0 --> srcIndex + System.arraycopy( orig, 0, result, 0, srcIndex ); + + // srcIndex --> targetIndex + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length, + targetIndex - srcIndex ); + + // targets are already absolute file names + System.arraycopy( targetFiles, 0, result, + targetIndex + srcFiles.length, + targetFiles.length ); + + // targetIndex --> end + System.arraycopy( orig, targetIndex, result, + targetIndex + srcFiles.length + targetFiles.length, + orig.length - targetIndex ); + } + else + { + // 0 --> targetIndex + System.arraycopy( orig, 0, result, 0, targetIndex ); + + // targets are already absolute file names + System.arraycopy( targetFiles, 0, result, + targetIndex, + targetFiles.length ); + + // targetIndex --> srcIndex + System.arraycopy( orig, targetIndex, result, + targetIndex + targetFiles.length, + srcIndex - targetIndex ); + + // srcIndex --> end + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length + targetFiles.length, + orig.length - srcIndex ); + srcIndex += targetFiles.length; + } + + } + else + {// no targetFilePos + + // 0 --> srcIndex + System.arraycopy( orig, 0, result, 0, srcIndex ); + // srcIndex --> end + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length, + orig.length - srcIndex ); + + } + + // fill in source file names + for( int i = 0; i < srcFiles.length; i++ ) + { + if( !relative ) + { + result[srcIndex + i] = + ( new File( baseDirs[i], srcFiles[i] ) ).getAbsolutePath(); + } + else + { + result[srcIndex + i] = srcFiles[i]; + } + } + return result; + } + + /** + * Construct the command line for serial execution. + * + * @param srcFile The filename to add to the commandline + * @param baseDir filename is relative to this dir + * @return The Commandline value + */ + protected String[] getCommandline( String srcFile, File baseDir ) + { + return getCommandline( new String[]{srcFile}, new File[]{baseDir} ); + } + + /** + * Return the list of Directories from this DirectoryScanner that should be + * included on the command line. + * + * @param baseDir Description of Parameter + * @param ds Description of Parameter + * @return The Dirs value + */ + protected String[] getDirs( File baseDir, DirectoryScanner ds ) + { + if( mapper != null ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + return sfs.restrict( ds.getIncludedDirectories(), baseDir, destDir, + mapper ); + } + else + { + return ds.getIncludedDirectories(); + } + } + + /** + * Return the list of files from this DirectoryScanner that should be + * included on the command line. + * + * @param baseDir Description of Parameter + * @param ds Description of Parameter + * @return The Files value + */ + protected String[] getFiles( File baseDir, DirectoryScanner ds ) + { + if( mapper != null ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + return sfs.restrict( ds.getIncludedFiles(), baseDir, destDir, + mapper ); + } + else + { + return ds.getIncludedFiles(); + } + } + + protected void checkConfiguration() + { + if( "execon".equals( taskName ) ) + { + log( "!! execon is deprecated. Use apply instead. !!" ); + } + + super.checkConfiguration(); + if( filesets.size() == 0 ) + { + throw new BuildException( "no filesets specified", location ); + } + + if( targetFilePos != null || mapperElement != null + || destDir != null ) + { + + if( mapperElement == null ) + { + throw new BuildException( "no mapper specified", location ); + } + if( mapperElement == null ) + { + throw new BuildException( "no dest attribute specified", + location ); + } + mapper = mapperElement.getImplementation(); + } + } + + protected void runExec( Execute exe ) + throws BuildException + { + try + { + + Vector fileNames = new Vector(); + Vector baseDirs = new Vector(); + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + File base = fs.getDir( project ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + + if( !"dir".equals( type ) ) + { + String[] s = getFiles( base, ds ); + for( int j = 0; j < s.length; j++ ) + { + fileNames.addElement( s[j] ); + baseDirs.addElement( base ); + } + } + + if( !"file".equals( type ) ) + { + String[] s = getDirs( base, ds ); + ; + for( int j = 0; j < s.length; j++ ) + { + fileNames.addElement( s[j] ); + baseDirs.addElement( base ); + } + } + + if( fileNames.size() == 0 && skipEmpty ) + { + log( "Skipping fileset for directory " + + base + ". It is empty.", Project.MSG_INFO ); + continue; + } + + if( !parallel ) + { + String[] s = new String[fileNames.size()]; + fileNames.copyInto( s ); + for( int j = 0; j < s.length; j++ ) + { + String[] command = getCommandline( s[j], base ); + log( "Executing " + Commandline.toString( command ), + Project.MSG_VERBOSE ); + exe.setCommandline( command ); + runExecute( exe ); + } + fileNames.removeAllElements(); + baseDirs.removeAllElements(); + } + } + + if( parallel && ( fileNames.size() > 0 || !skipEmpty ) ) + { + String[] s = new String[fileNames.size()]; + fileNames.copyInto( s ); + File[] b = new File[baseDirs.size()]; + baseDirs.copyInto( b ); + String[] command = getCommandline( s, b ); + log( "Executing " + Commandline.toString( command ), + Project.MSG_VERBOSE ); + exe.setCommandline( command ); + runExecute( exe ); + } + + } + catch( IOException e ) + { + throw new BuildException( "Execute failed: " + e, e, location ); + } + finally + { + // close the output file if required + logFlush(); + } + } + + /** + * Enumerated attribute with the values "file", "dir" and "both" for the + * type attribute. + * + * @author RT + */ + public static class FileDirBoth extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"file", "dir", "both"}; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java new file mode 100644 index 000000000..f9f64cca6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Used by Execute to handle input and output stream of + * subprocesses. + * + * @author thomas.haas@softwired-inc.com + */ +public interface ExecuteStreamHandler +{ + + /** + * Install a handler for the input stream of the subprocess. + * + * @param os output stream to write to the standard input stream of the + * subprocess + * @exception IOException Description of Exception + */ + void setProcessInputStream( OutputStream os ) + throws IOException; + + /** + * Install a handler for the error stream of the subprocess. + * + * @param is input stream to read from the error stream from the subprocess + * @exception IOException Description of Exception + */ + void setProcessErrorStream( InputStream is ) + throws IOException; + + /** + * Install a handler for the output stream of the subprocess. + * + * @param is input stream to read from the error stream from the subprocess + * @exception IOException Description of Exception + */ + void setProcessOutputStream( InputStream is ) + throws IOException; + + /** + * Start handling of the streams. + * + * @exception IOException Description of Exception + */ + void start() + throws IOException; + + /** + * Stop handling of the streams - will not be restarted. + */ + void stop(); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java new file mode 100644 index 000000000..318e622aa --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Destroys a process running for too long. For example:

      + * ExecuteWatchdog watchdog = new ExecuteWatchdog(30000);
      + * Execute exec = new Execute(myloghandler, watchdog);
      + * exec.setCommandLine(mycmdline);
      + * int exitvalue = exec.execute();
      + * if (exitvalue != SUCCESS && watchdog.killedProcess()){
      + *              // it was killed on purpose by the watchdog
      + * }
      + * 
      + * + * @author thomas.haas@softwired-inc.com + * @author Stephane Bailliez + * @see Execute + */ +public class ExecuteWatchdog implements Runnable +{ + + /** + * say whether or not the watchog is currently monitoring a process + */ + private boolean watch = false; + + /** + * exception that might be thrown during the process execution + */ + private Exception caught = null; + + /** + * say whether or not the process was killed due to running overtime + */ + private boolean killedProcess = false; + + /** + * the process to execute and watch for duration + */ + private Process process; + + /** + * timeout duration. Once the process running time exceeds this it should be + * killed + */ + private int timeout; + + /** + * Creates a new watchdog with a given timeout. + * + * @param timeout the timeout for the process in milliseconds. It must be + * greather than 0. + */ + public ExecuteWatchdog( int timeout ) + { + if( timeout < 1 ) + { + throw new IllegalArgumentException( "timeout lesser than 1." ); + } + this.timeout = timeout; + } + + /** + * Indicates whether or not the watchdog is still monitoring the process. + * + * @return true if the process is still running, otherwise + * false . + */ + public boolean isWatching() + { + return watch; + } + + /** + * This method will rethrow the exception that was possibly caught during + * the run of the process. It will only remains valid once the process has + * been terminated either by 'error', timeout or manual intervention. + * Information will be discarded once a new process is ran. + * + * @throws BuildException a wrapped exception over the one that was silently + * swallowed and stored during the process run. + */ + public void checkException() + throws BuildException + { + if( caught != null ) + { + throw new BuildException( "Exception in ExecuteWatchdog.run: " + + caught.getMessage(), caught ); + } + } + + /** + * Indicates whether the last process run was killed on timeout or not. + * + * @return true if the process was killed otherwise false + * . + */ + public boolean killedProcess() + { + return killedProcess; + } + + + /** + * Watches the process and terminates it, if it runs for to long. + */ + public synchronized void run() + { + try + { + // This isn't a Task, don't have a Project object to log. + // project.log("ExecuteWatchdog: timeout = "+timeout+" msec", Project.MSG_VERBOSE); + final long until = System.currentTimeMillis() + timeout; + long now; + while( watch && until > ( now = System.currentTimeMillis() ) ) + { + try + { + wait( until - now ); + } + catch( InterruptedException e ) + {} + } + + // if we are here, either someone stopped the watchdog, + // we are on timeout and the process must be killed, or + // we are on timeout and the process has already stopped. + try + { + // We must check if the process was not stopped + // before being here + process.exitValue(); + } + catch( IllegalThreadStateException e ) + { + // the process is not terminated, if this is really + // a timeout and not a manual stop then kill it. + if( watch ) + { + killedProcess = true; + process.destroy(); + } + } + } + catch( Exception e ) + { + caught = e; + } + finally + { + cleanUp(); + } + } + + /** + * Watches the given process and terminates it, if it runs for too long. All + * information from the previous run are reset. + * + * @param process the process to monitor. It cannot be null + * @throws IllegalStateException thrown if a process is still being + * monitored. + */ + public synchronized void start( Process process ) + { + if( process == null ) + { + throw new NullPointerException( "process is null." ); + } + if( this.process != null ) + { + throw new IllegalStateException( "Already running." ); + } + this.caught = null; + this.killedProcess = false; + this.watch = true; + this.process = process; + final Thread thread = new Thread( this, "WATCHDOG" ); + thread.setDaemon( true ); + thread.start(); + } + + /** + * Stops the watcher. It will notify all threads possibly waiting on this + * object. + */ + public synchronized void stop() + { + watch = false; + notifyAll(); + } + + /** + * reset the monitor flag and the process. + */ + protected void cleanUp() + { + watch = false; + process = null; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exit.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exit.java new file mode 100644 index 000000000..60ae24009 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exit.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; + +/** + * Just exit the active build, giving an additional message if available. + * + * @author Nico Seessle + */ +public class Exit extends Task +{ + private String ifCondition, unlessCondition; + private String message; + + public void setIf( String c ) + { + ifCondition = c; + } + + public void setMessage( String value ) + { + this.message = value; + } + + public void setUnless( String c ) + { + unlessCondition = c; + } + + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + public void execute() + throws BuildException + { + if( testIfCondition() && testUnlessCondition() ) + { + if( message != null && message.length() > 0 ) + { + throw new BuildException( message ); + } + else + { + throw new BuildException( "No message" ); + } + } + } + + private boolean testIfCondition() + { + if( ifCondition == null || "".equals( ifCondition ) ) + { + return true; + } + + return project.getProperty( ifCondition ) != null; + } + + private boolean testUnlessCondition() + { + if( unlessCondition == null || "".equals( unlessCondition ) ) + { + return true; + } + return project.getProperty( unlessCondition ) == null; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Expand.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Expand.java new file mode 100644 index 000000000..38ebc9404 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Expand.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; +import org.apache.tools.ant.util.FileUtils; + +/** + * Unzip a file. + * + * @author costin@dnt.ro + * @author Stefan Bodewig + * @author Magesh Umasankar + */ +public class Expand extends MatchingTask +{// req + private boolean overwrite = true; + private Vector patternsets = new Vector(); + private Vector filesets = new Vector(); + private File dest;//req + private File source; + + /** + * Set the destination directory. File will be unzipped into the destination + * directory. + * + * @param d Path to the directory. + */ + public void setDest( File d ) + { + this.dest = d; + } + + /** + * Should we overwrite files in dest, even if they are newer than the + * corresponding entries in the archive? + * + * @param b The new Overwrite value + */ + public void setOverwrite( boolean b ) + { + overwrite = b; + } + + /** + * Set the path to zip-file. + * + * @param s Path to zip-file. + */ + public void setSrc( File s ) + { + this.source = s; + } + + /** + * Add a fileset + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Add a patternset + * + * @param set The feature to be added to the Patternset attribute + */ + public void addPatternset( PatternSet set ) + { + patternsets.addElement( set ); + } + + /** + * Do the work. + * + * @exception BuildException Thrown in unrecoverable error. + */ + public void execute() + throws BuildException + { + if( "expand".equals( taskType ) ) + { + log( "!! expand is deprecated. Use unzip instead. !!" ); + } + + if( source == null && filesets.size() == 0 ) + { + throw new BuildException( "src attribute and/or filesets must be specified" ); + } + + if( dest == null ) + { + throw new BuildException( + "Dest attribute must be specified" ); + } + + if( dest.exists() && !dest.isDirectory() ) + { + throw new BuildException( "Dest must be a directory.", location ); + } + + FileUtils fileUtils = FileUtils.newFileUtils(); + + if( source != null ) + { + if( source.isDirectory() ) + { + throw new BuildException( "Src must not be a directory." + + " Use nested filesets instead.", location ); + } + else + { + expandFile( fileUtils, source, dest ); + } + } + if( filesets.size() > 0 ) + { + for( int j = 0; j < filesets.size(); j++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( j ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] files = ds.getIncludedFiles(); + for( int i = 0; i < files.length; ++i ) + { + File file = new File( fromDir, files[i] ); + expandFile( fileUtils, file, dest ); + } + } + } + } + + /* + * This method is to be overridden by extending unarchival tasks. + */ + protected void expandFile( FileUtils fileUtils, File srcF, File dir ) + { + ZipInputStream zis = null; + try + { + // code from WarExpand + zis = new ZipInputStream( new FileInputStream( srcF ) ); + ZipEntry ze = null; + + while( ( ze = zis.getNextEntry() ) != null ) + { + extractFile( fileUtils, srcF, dir, zis, + ze.getName(), + new Date( ze.getTime() ), + ze.isDirectory() ); + } + + log( "expand complete", Project.MSG_VERBOSE ); + } + catch( IOException ioe ) + { + throw new BuildException( "Error while expanding " + srcF.getPath(), ioe ); + } + finally + { + if( zis != null ) + { + try + { + zis.close(); + } + catch( IOException e ) + {} + } + } + } + + protected void extractFile( FileUtils fileUtils, File srcF, File dir, + InputStream compressedInputStream, + String entryName, + Date entryDate, boolean isDirectory ) + throws IOException + { + + if( patternsets != null && patternsets.size() > 0 ) + { + String name = entryName; + boolean included = false; + for( int v = 0; v < patternsets.size(); v++ ) + { + PatternSet p = ( PatternSet )patternsets.elementAt( v ); + String[] incls = p.getIncludePatterns( project ); + if( incls != null ) + { + for( int w = 0; w < incls.length; w++ ) + { + boolean isIncl = DirectoryScanner.match( incls[w], name ); + if( isIncl ) + { + included = true; + break; + } + } + } + String[] excls = p.getExcludePatterns( project ); + if( excls != null ) + { + for( int w = 0; w < excls.length; w++ ) + { + boolean isExcl = DirectoryScanner.match( excls[w], name ); + if( isExcl ) + { + included = false; + break; + } + } + } + } + if( !included ) + { + //Do not process this file + return; + } + } + + File f = fileUtils.resolveFile( dir, entryName ); + try + { + if( !overwrite && f.exists() + && f.lastModified() >= entryDate.getTime() ) + { + log( "Skipping " + f + " as it is up-to-date", + Project.MSG_DEBUG ); + return; + } + + log( "expanding " + entryName + " to " + f, + Project.MSG_VERBOSE ); + // create intermediary directories - sometimes zip don't add them + File dirF = fileUtils.getParentFile( f ); + dirF.mkdirs(); + + if( isDirectory ) + { + f.mkdirs(); + } + else + { + byte[] buffer = new byte[1024]; + int length = 0; + FileOutputStream fos = null; + try + { + fos = new FileOutputStream( f ); + + while( ( length = + compressedInputStream.read( buffer ) ) >= 0 ) + { + fos.write( buffer, 0, length ); + } + + fos.close(); + fos = null; + } + finally + { + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException e ) + {} + } + } + } + + fileUtils.setFileLastModified( f, entryDate.getTime() ); + } + catch( FileNotFoundException ex ) + { + log( "Unable to expand to file " + f.getPath(), Project.MSG_WARN ); + } + + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Filter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Filter.java new file mode 100644 index 000000000..6970d0ab4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Filter.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * This task sets a token filter that is used by the file copy methods of the + * project to do token substitution, or sets mutiple tokens by reading these + * from a file. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Gero Vermaas gero@xs4all.nl + * @author Michael McCallum + */ +public class Filter extends Task +{ + private File filtersFile; + + private String token; + private String value; + + public void setFiltersfile( File filtersFile ) + { + this.filtersFile = filtersFile; + } + + public void setToken( String token ) + { + this.token = token; + } + + public void setValue( String value ) + { + this.value = value; + } + + public void execute() + throws BuildException + { + boolean isFiltersFromFile = filtersFile != null && token == null && value == null; + boolean isSingleFilter = filtersFile == null && token != null && value != null; + + if( !isFiltersFromFile && !isSingleFilter ) + { + throw new BuildException( "both token and value parameters, or only a filtersFile parameter is required", location ); + } + + if( isSingleFilter ) + { + project.getGlobalFilterSet().addFilter( token, value ); + } + + if( isFiltersFromFile ) + { + readFilters(); + } + } + + protected void readFilters() + throws BuildException + { + log( "Reading filters from " + filtersFile, Project.MSG_VERBOSE ); + project.getGlobalFilterSet().readFiltersFromFile( filtersFile ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/FixCRLF.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/FixCRLF.java new file mode 100644 index 000000000..2cd21df0a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/FixCRLF.java @@ -0,0 +1,1159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Enumeration; +import java.util.NoSuchElementException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.util.FileUtils; + +/** + * Task to convert text source files to local OS formatting conventions, as well + * as repair text files damaged by misconfigured or misguided editors or file + * transfer programs.

      + * + * This task can take the following arguments: + *

        + *
      • srcdir + *
      • destdir + *
      • include + *
      • exclude + *
      • cr + *
      • eol + *
      • tab + *
      • eof + *
      • encoding + *
      + * Of these arguments, only sourcedir is required.

      + * + * When this task executes, it will scan the srcdir based on the include and + * exclude properties.

      + * + * This version generalises the handling of EOL characters, and allows for + * CR-only line endings (which I suspect is the standard on Macs.) Tab handling + * has also been generalised to accommodate any tabwidth from 2 to 80, + * inclusive. Importantly, it will leave untouched any literal TAB characters + * embedded within string or character constants.

      + * + * Warning: do not run on binary files. Caution: run with care + * on carefully formatted files. This may sound obvious, but if you don't + * specify asis, presume that your files are going to be modified. If "tabs" is + * "add" or "remove", whitespace characters may be added or removed as + * necessary. Similarly, for CR's - in fact "eol"="crlf" or cr="add" can result + * in cr characters being removed in one special case accommodated, i.e., CRCRLF + * is regarded as a single EOL to handle cases where other programs have + * converted CRLF into CRCRLF. + * + * @author Sam Ruby rubys@us.ibm.com + * @author Peter B. West + * @version $Revision$ $Name$ + */ + +public class FixCRLF extends MatchingTask +{ + + private final static int UNDEF = -1; + private final static int NOTJAVA = 0; + private final static int LOOKING = 1; + private final static int IN_CHAR_CONST = 2; + private final static int IN_STR_CONST = 3; + private final static int IN_SINGLE_COMMENT = 4; + private final static int IN_MULTI_COMMENT = 5; + + private final static int ASIS = 0; + private final static int CR = 1; + private final static int LF = 2; + private final static int CRLF = 3; + private final static int ADD = 1; + private final static int REMOVE = -1; + private final static int SPACES = -1; + private final static int TABS = 1; + + private final static int INBUFLEN = 8192; + private final static int LINEBUFLEN = 200; + + private final static char CTRLZ = '\u001A'; + + private int tablength = 8; + private String spaces = " "; + private StringBuffer linebuf = new StringBuffer( 1024 ); + private StringBuffer linebuf2 = new StringBuffer( 1024 ); + private boolean javafiles = false; + private File destDir = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + /** + * Encoding to assume for the files + */ + private String encoding = null; + private int ctrlz; + private int eol; + private String eolstr; + + private File srcDir; + private int tabs; + + /** + * Defaults the properties based on the system type. + *

        + *
      • Unix: eol="LF" tab="asis" eof="remove" + *
      • Mac: eol="CR" tab="asis" eof="remove" + *
      • DOS: eol="CRLF" tab="asis" eof="asis" + *
      + * + */ + public FixCRLF() + { + tabs = ASIS; + if( System.getProperty( "path.separator" ).equals( ":" ) ) + { + ctrlz = REMOVE; + if( System.getProperty( "os.name" ).indexOf( "Mac" ) > -1 ) + { + eol = CR; + eolstr = "\r"; + } + else + { + eol = LF; + eolstr = "\n"; + } + } + else + { + ctrlz = ASIS; + eol = CRLF; + eolstr = "\r\n"; + } + } + + /** + * Specify how carriage return (CR) characters are to be handled + * + * @param attr The new Cr value + * @deprecated use {@link #setEol setEol} instead. + */ + public void setCr( AddAsisRemove attr ) + { + log( "DEPRECATED: The cr attribute has been deprecated,", + Project.MSG_WARN ); + log( "Please us the eol attribute instead", Project.MSG_WARN ); + String option = attr.getValue(); + CrLf c = new CrLf(); + if( option.equals( "remove" ) ) + { + c.setValue( "lf" ); + } + else if( option.equals( "asis" ) ) + { + c.setValue( "asis" ); + } + else + { + // must be "add" + c.setValue( "crlf" ); + } + setEol( c ); + } + + /** + * Set the destination where the fixed files should be placed. Default is to + * replace the original file. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Specifies the encoding Ant expects the files to be in - defaults to the + * platforms default encoding. + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Specify how DOS EOF (control-z) charaters are to be handled + * + * @param attr The new Eof value + */ + public void setEof( AddAsisRemove attr ) + { + String option = attr.getValue(); + if( option.equals( "remove" ) ) + { + ctrlz = REMOVE; + } + else if( option.equals( "asis" ) ) + { + ctrlz = ASIS; + } + else + { + // must be "add" + ctrlz = ADD; + } + } + + + /** + * Specify how EndOfLine characters are to be handled + * + * @param attr The new Eol value + */ + public void setEol( CrLf attr ) + { + String option = attr.getValue(); + if( option.equals( "asis" ) ) + { + eol = ASIS; + } + else if( option.equals( "cr" ) ) + { + eol = CR; + eolstr = "\r"; + } + else if( option.equals( "lf" ) ) + { + eol = LF; + eolstr = "\n"; + } + else + { + // Must be "crlf" + eol = CRLF; + eolstr = "\r\n"; + } + } + + /** + * Fixing Java source files? + * + * @param javafiles The new Javafiles value + */ + public void setJavafiles( boolean javafiles ) + { + this.javafiles = javafiles; + } + + /** + * Set the source dir to find the source text files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Specify how tab characters are to be handled + * + * @param attr The new Tab value + */ + public void setTab( AddAsisRemove attr ) + { + String option = attr.getValue(); + if( option.equals( "remove" ) ) + { + tabs = SPACES; + } + else if( option.equals( "asis" ) ) + { + tabs = ASIS; + } + else + { + // must be "add" + tabs = TABS; + } + } + + /** + * Specify tab length in characters + * + * @param tlength specify the length of tab in spaces, + * @exception BuildException Description of Exception + */ + public void setTablength( int tlength ) + throws BuildException + { + if( tlength < 2 || tlength > 80 ) + { + throw new BuildException( "tablength must be between 2 and 80", + location ); + } + tablength = tlength; + StringBuffer sp = new StringBuffer(); + for( int i = 0; i < tablength; i++ ) + { + sp.append( ' ' ); + } + spaces = sp.toString(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir and destdir + + if( srcDir == null ) + { + throw new BuildException( "srcdir attribute must be set!" ); + } + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir does not exist!" ); + } + if( !srcDir.isDirectory() ) + { + throw new BuildException( "srcdir is not a directory!" ); + } + if( destDir != null ) + { + if( !destDir.exists() ) + { + throw new BuildException( "destdir does not exist!" ); + } + if( !destDir.isDirectory() ) + { + throw new BuildException( "destdir is not a directory!" ); + } + } + + // log options used + log( "options:" + + " eol=" + + ( eol == ASIS ? "asis" : eol == CR ? "cr" : eol == LF ? "lf" : "crlf" ) + + " tab=" + ( tabs == TABS ? "add" : tabs == ASIS ? "asis" : "remove" ) + + " eof=" + ( ctrlz == ADD ? "add" : ctrlz == ASIS ? "asis" : "remove" ) + + " tablength=" + tablength + + " encoding=" + ( encoding == null ? "default" : encoding ), + Project.MSG_VERBOSE ); + + DirectoryScanner ds = super.getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + + for( int i = 0; i < files.length; i++ ) + { + processFile( files[i] ); + } + } + + /** + * Creates a Reader reading from a given file an taking the user defined + * encoding into account. + * + * @param f Description of Parameter + * @return The Reader value + * @exception IOException Description of Exception + */ + private Reader getReader( File f ) + throws IOException + { + return ( encoding == null ) ? new FileReader( f ) + : new InputStreamReader( new FileInputStream( f ), encoding ); + } + + + /** + * Scan a BufferLine forward from the 'next' pointer for the end of a + * character constant. Set 'lookahead' pointer to the character following + * the terminating quote. + * + * @param bufline Description of Parameter + * @param terminator Description of Parameter + * @exception BuildException Description of Exception + */ + private void endOfCharConst( OneLiner.BufferLine bufline, char terminator ) + throws BuildException + { + int ptr = bufline.getNext(); + int eol = bufline.length(); + char c; + ptr++;// skip past initial quote + while( ptr < eol ) + { + if( ( c = bufline.getChar( ptr++ ) ) == '\\' ) + { + ptr++; + } + else + { + if( c == terminator ) + { + bufline.setLookahead( ptr ); + return; + } + } + }// end of while (ptr < eol) + // Must have fallen through to the end of the line + throw new BuildException( "endOfCharConst: unterminated char constant" ); + } + + /** + * Scan a BufferLine for the next state changing token: the beginning of a + * single or multi-line comment, a character or a string constant. As a + * side-effect, sets the buffer state to the next state, and sets field + * lookahead to the first character of the state-changing token, or to the + * next eol character. + * + * @param bufline Description of Parameter + * @exception BuildException Description of Exception + */ + private void nextStateChange( OneLiner.BufferLine bufline ) + throws BuildException + { + int eol = bufline.length(); + int ptr = bufline.getNext(); + + // Look for next single or double quote, double slash or slash star + while( ptr < eol ) + { + switch ( bufline.getChar( ptr++ ) ) + { + case '\'': + bufline.setState( IN_CHAR_CONST ); + bufline.setLookahead( --ptr ); + return; + case '\"': + bufline.setState( IN_STR_CONST ); + bufline.setLookahead( --ptr ); + return; + case '/': + if( ptr < eol ) + { + if( bufline.getChar( ptr ) == '*' ) + { + bufline.setState( IN_MULTI_COMMENT ); + bufline.setLookahead( --ptr ); + return; + } + else if( bufline.getChar( ptr ) == '/' ) + { + bufline.setState( IN_SINGLE_COMMENT ); + bufline.setLookahead( --ptr ); + return; + } + } + break; + }// end of switch (bufline.getChar(ptr++)) + + }// end of while (ptr < eol) + // Eol is the next token + bufline.setLookahead( ptr ); + } + + + /** + * Process a BufferLine string which is not part of of a string constant. + * The start position of the string is given by the 'next' field. Sets the + * 'next' and 'column' fields in the BufferLine. + * + * @param bufline Description of Parameter + * @param end Description of Parameter + * @param outWriter Description of Parameter + */ + private void notInConstant( OneLiner.BufferLine bufline, int end, + BufferedWriter outWriter ) + { + // N.B. both column and string index are zero-based + // Process a string not part of a constant; + // i.e. convert tabs<->spaces as required + // This is NOT called for ASIS tab handling + int nextTab; + int nextStop; + int tabspaces; + String line = bufline.substring( bufline.getNext(), end ); + int place = 0;// Zero-based + int col = bufline.getColumn();// Zero-based + + // process sequences of white space + // first convert all tabs to spaces + linebuf.setLength( 0 ); + while( ( nextTab = line.indexOf( ( int )'\t', place ) ) >= 0 ) + { + linebuf.append( line.substring( place, nextTab ) );// copy to the TAB + col += nextTab - place; + tabspaces = tablength - ( col % tablength ); + linebuf.append( spaces.substring( 0, tabspaces ) ); + col += tabspaces; + place = nextTab + 1; + }// end of while + linebuf.append( line.substring( place, line.length() ) ); + // if converting to spaces, all finished + String linestring = new String( linebuf.toString() ); + if( tabs == REMOVE ) + { + try + { + outWriter.write( linestring ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + } + else + {// tabs == ADD + int tabCol; + linebuf2.setLength( 0 ); + place = 0; + col = bufline.getColumn(); + int placediff = col - 0; + // for the length of the string, cycle through the tab stop + // positions, checking for a space preceded by at least one + // other space at the tab stop. if so replace the longest possible + // preceding sequence of spaces with a tab. + nextStop = col + ( tablength - col % tablength ); + if( nextStop - col < 2 ) + { + linebuf2.append( linestring.substring( + place, nextStop - placediff ) ); + place = nextStop - placediff; + nextStop += tablength; + } + + for( ; nextStop - placediff <= linestring.length() + ; nextStop += tablength ) + { + for( tabCol = nextStop; + --tabCol - placediff >= place + && linestring.charAt( tabCol - placediff ) == ' ' + ; ) + { + ;// Loop for the side-effects + } + // tabCol is column index of the last non-space character + // before the next tab stop + if( nextStop - tabCol > 2 ) + { + linebuf2.append( linestring.substring( + place, ++tabCol - placediff ) ); + linebuf2.append( '\t' ); + } + else + { + linebuf2.append( linestring.substring( + place, nextStop - placediff ) ); + }// end of else + + place = nextStop - placediff; + }// end of for (nextStop ... ) + + // pick up that last bit, if any + linebuf2.append( linestring.substring( place, linestring.length() ) ); + + try + { + outWriter.write( linebuf2.toString() ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + }// end of else tabs == ADD + + // Set column position as modified by this method + bufline.setColumn( bufline.getColumn() + linestring.length() ); + bufline.setNext( end ); + + } + + + private void processFile( String file ) + throws BuildException + { + File srcFile = new File( srcDir, file ); + File destD = destDir == null ? srcDir : destDir; + File tmpFile = null; + BufferedWriter outWriter; + OneLiner.BufferLine line; + + // read the contents of the file + OneLiner lines = new OneLiner( srcFile ); + + try + { + // Set up the output Writer + try + { + tmpFile = fileUtils.createTempFile( "fixcrlf", "", destD ); + Writer writer = ( encoding == null ) ? new FileWriter( tmpFile ) + : new OutputStreamWriter( new FileOutputStream( tmpFile ), encoding ); + outWriter = new BufferedWriter( writer ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + while( lines.hasMoreElements() ) + { + // In-line states + int endComment; + + try + { + line = ( OneLiner.BufferLine )lines.nextElement(); + } + catch( NoSuchElementException e ) + { + throw new BuildException( e ); + } + + String lineString = line.getLineString(); + int linelen = line.length(); + + // Note - all of the following processing NOT done for + // tabs ASIS + + if( tabs == ASIS ) + { + // Just copy the body of the line across + try + { + outWriter.write( lineString ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + } + else + {// (tabs != ASIS) + int ptr; + + while( ( ptr = line.getNext() ) < linelen ) + { + + switch ( lines.getState() ) + { + + case NOTJAVA: + notInConstant( line, line.length(), outWriter ); + break; + case IN_MULTI_COMMENT: + if( ( endComment = + lineString.indexOf( "*/", line.getNext() ) + ) >= 0 ) + { + // End of multiLineComment on this line + endComment += 2;// Include the end token + lines.setState( LOOKING ); + } + else + { + endComment = linelen; + } + + notInConstant( line, endComment, outWriter ); + break; + case IN_SINGLE_COMMENT: + notInConstant( line, line.length(), outWriter ); + lines.setState( LOOKING ); + break; + case IN_CHAR_CONST: + case IN_STR_CONST: + // Got here from LOOKING by finding an opening "\'" + // next points to that quote character. + // Find the end of the constant. Watch out for + // backslashes. Literal tabs are left unchanged, and + // the column is adjusted accordingly. + + int begin = line.getNext(); + char terminator = ( lines.getState() == IN_STR_CONST + ? '\"' + : '\'' ); + endOfCharConst( line, terminator ); + while( line.getNext() < line.getLookahead() ) + { + if( line.getNextCharInc() == '\t' ) + { + line.setColumn( + line.getColumn() + + tablength - + line.getColumn() % tablength ); + } + else + { + line.incColumn(); + } + } + + // Now output the substring + try + { + outWriter.write( line.substring( begin, line.getNext() ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + lines.setState( LOOKING ); + + break; + + case LOOKING: + nextStateChange( line ); + notInConstant( line, line.getLookahead(), outWriter ); + break; + }// end of switch (state) + + }// end of while (line.getNext() < linelen) + + }// end of else (tabs != ASIS) + + try + { + outWriter.write( eolstr ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + }// end of while (lines.hasNext()) + + try + { + // Handle CTRLZ + if( ctrlz == ASIS ) + { + outWriter.write( lines.getEofStr() ); + } + else if( ctrlz == ADD ) + { + outWriter.write( CTRLZ ); + } + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + try + { + outWriter.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + File destFile = new File( destD, file ); + + try + { + lines.close(); + lines = null; + } + catch( IOException e ) + { + throw new BuildException( "Unable to close source file " + srcFile ); + } + + if( destFile.exists() ) + { + // Compare the destination with the temp file + log( "destFile exists", Project.MSG_DEBUG ); + if( !fileUtils.contentEquals( destFile, tmpFile ) ) + { + log( destFile + " is being written", Project.MSG_DEBUG ); + if( !destFile.delete() ) + { + throw new BuildException( "Unable to delete " + + destFile ); + } + if( !tmpFile.renameTo( destFile ) ) + { + throw new BuildException( + "Failed to transform " + srcFile + + " to " + destFile + + ". Couldn't rename temporary file: " + + tmpFile ); + } + + } + else + {// destination is equal to temp file + log( destFile + + " is not written, as the contents are identical", + Project.MSG_DEBUG ); + if( !tmpFile.delete() ) + { + throw new BuildException( "Unable to delete " + + tmpFile ); + } + } + } + else + {// destFile does not exist - write the temp file + log( "destFile does not exist", Project.MSG_DEBUG ); + if( !tmpFile.renameTo( destFile ) ) + { + throw new BuildException( + "Failed to transform " + srcFile + + " to " + destFile + + ". Couldn't rename temporary file: " + + tmpFile ); + } + } + + tmpFile = null; + + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + try + { + if( lines != null ) + { + lines.close(); + } + } + catch( IOException io ) + { + log( "Error closing " + srcFile, Project.MSG_ERR ); + }// end of catch + + if( tmpFile != null ) + { + tmpFile.delete(); + } + }// end of finally + } + + /** + * Enumerated attribute with the values "asis", "add" and "remove". + * + * @author RT + */ + public static class AddAsisRemove extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"add", "asis", "remove"}; + } + } + + /** + * Enumerated attribute with the values "asis", "cr", "lf" and "crlf". + * + * @author RT + */ + public static class CrLf extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"asis", "cr", "lf", "crlf"}; + } + } + + + class OneLiner implements Enumeration + { + + private int state = javafiles ? LOOKING : NOTJAVA; + + private StringBuffer eolStr = new StringBuffer( LINEBUFLEN ); + private StringBuffer eofStr = new StringBuffer(); + private StringBuffer line = new StringBuffer(); + private boolean reachedEof = false; + + private BufferedReader reader; + + public OneLiner( File srcFile ) + throws BuildException + { + try + { + reader = new BufferedReader + ( getReader( srcFile ), INBUFLEN ); + nextLine(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + public void setState( int state ) + { + this.state = state; + } + + public String getEofStr() + { + return eofStr.toString(); + } + + public int getState() + { + return state; + } + + public void close() + throws IOException + { + if( reader != null ) + { + reader.close(); + } + } + + public boolean hasMoreElements() + { + return !reachedEof; + } + + public Object nextElement() + throws NoSuchElementException + { + if( !hasMoreElements() ) + { + throw new NoSuchElementException( "OneLiner" ); + } + BufferLine tmpLine = + new BufferLine( line.toString(), eolStr.toString() ); + nextLine(); + return tmpLine; + } + + protected void nextLine() + throws BuildException + { + int ch = -1; + int eolcount = 0; + + eolStr.setLength( 0 ); + line.setLength( 0 ); + + try + { + ch = reader.read(); + while( ch != -1 && ch != '\r' && ch != '\n' ) + { + line.append( ( char )ch ); + ch = reader.read(); + } + + if( ch == -1 && line.length() == 0 ) + { + // Eof has been reached + reachedEof = true; + return; + } + + switch ( ( char )ch ) + { + case '\r': + // Check for \r, \r\n and \r\r\n + // Regard \r\r not followed by \n as two lines + ++eolcount; + eolStr.append( '\r' ); + switch ( ( char )( ch = reader.read() ) ) + { + case '\r': + if( ( char )( ch = reader.read() ) == '\n' ) + { + eolcount += 2; + eolStr.append( "\r\n" ); + } + break; + case '\n': + ++eolcount; + eolStr.append( '\n' ); + break; + }// end of switch ((char)(ch = reader.read())) + break; + case '\n': + ++eolcount; + eolStr.append( '\n' ); + break; + }// end of switch ((char) ch) + + // if at eolcount == 0 and trailing characters of string + // are CTRL-Zs, set eofStr + if( eolcount == 0 ) + { + int i = line.length(); + while( --i >= 0 && line.charAt( i ) == CTRLZ ) + { + // keep searching for the first ^Z + } + if( i < line.length() - 1 ) + { + // Trailing characters are ^Zs + // Construct new line and eofStr + eofStr.append( line.toString().substring( i + 1 ) ); + if( i < 0 ) + { + line.setLength( 0 ); + reachedEof = true; + } + else + { + line.setLength( i + 1 ); + } + } + + }// end of if (eolcount == 0) + + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + class BufferLine + { + private int next = 0; + private int column = 0; + private int lookahead = UNDEF; + private String eolStr; + private String line; + + public BufferLine( String line, String eolStr ) + throws BuildException + { + next = 0; + column = 0; + this.line = line; + this.eolStr = eolStr; + } + + public void setColumn( int col ) + { + column = col; + } + + public void setLookahead( int lookahead ) + { + this.lookahead = lookahead; + } + + public void setNext( int next ) + { + this.next = next; + } + + public void setState( int state ) + { + OneLiner.this.setState( state ); + } + + public char getChar( int i ) + { + return line.charAt( i ); + } + + public int getColumn() + { + return column; + } + + public String getEol() + { + return eolStr; + } + + public int getEolLength() + { + return eolStr.length(); + } + + public String getLineString() + { + return line; + } + + public int getLookahead() + { + return lookahead; + } + + public int getNext() + { + return next; + } + + public char getNextChar() + { + return getChar( next ); + } + + public char getNextCharInc() + { + return getChar( next++ ); + } + + public int getState() + { + return OneLiner.this.getState(); + } + + public int incColumn() + { + return column++; + } + + public int length() + { + return line.length(); + } + + public String substring( int begin ) + { + return line.substring( begin ); + } + + public String substring( int begin, int end ) + { + return line.substring( begin, end ); + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GUnzip.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GUnzip.java new file mode 100644 index 000000000..a4001fba5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GUnzip.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; +import org.apache.tools.ant.BuildException; + +/** + * Expands a file that has been compressed with the GZIP algorithm. Normally + * used to compress non-compressed archives such as TAR files. + * + * @author Stefan Bodewig + * @author Magesh Umasankar + */ + +public class GUnzip extends Unpack +{ + + private final static String DEFAULT_EXTENSION = ".gz"; + + protected String getDefaultExtension() + { + return DEFAULT_EXTENSION; + } + + protected void extract() + { + if( source.lastModified() > dest.lastModified() ) + { + log( "Expanding " + source.getAbsolutePath() + " to " + + dest.getAbsolutePath() ); + + FileOutputStream out = null; + GZIPInputStream zIn = null; + FileInputStream fis = null; + try + { + out = new FileOutputStream( dest ); + fis = new FileInputStream( source ); + zIn = new GZIPInputStream( fis ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = zIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + catch( IOException ioe ) + { + String msg = "Problem expanding gzip " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException ioex ) + {} + } + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + if( zIn != null ) + { + try + { + zIn.close(); + } + catch( IOException ioex ) + {} + } + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GZip.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GZip.java new file mode 100644 index 000000000..df35b0daf --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GZip.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.GZIPOutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Pack; + +/** + * Compresses a file with the GZIP algorithm. Normally used to compress + * non-compressed archives such as TAR files. + * + * @author James Davidson duncan@x180.com + * @author Jon S. Stevens jon@clearink.com + * @author Magesh Umasankar + */ + +public class GZip extends Pack +{ + protected void pack() + { + GZIPOutputStream zOut = null; + try + { + zOut = new GZIPOutputStream( new FileOutputStream( zipFile ) ); + zipFile( source, zOut ); + } + catch( IOException ioe ) + { + String msg = "Problem creating gzip " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( zOut != null ) + { + try + { + // close up + zOut.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GenerateKey.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GenerateKey.java new file mode 100644 index 000000000..06a0425ea --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GenerateKey.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; + +/** + * Generates a key. + * + * @author Peter Donald + */ +public class GenerateKey extends Task +{ + + /** + * The alias of signer. + */ + protected String alias; + protected String dname; + protected DistinguishedName expandedDname; + protected String keyalg; + protected String keypass; + protected int keysize; + + /** + * The name of keystore file. + */ + protected String keystore; + + protected String sigalg; + protected String storepass; + protected String storetype; + protected int validity; + protected boolean verbose; + + public void setAlias( final String alias ) + { + this.alias = alias; + } + + public void setDname( final String dname ) + { + if( null != expandedDname ) + { + throw new BuildException( "It is not possible to specify dname both " + + "as attribute and element." ); + } + this.dname = dname; + } + + public void setKeyalg( final String keyalg ) + { + this.keyalg = keyalg; + } + + public void setKeypass( final String keypass ) + { + this.keypass = keypass; + } + + public void setKeysize( final String keysize ) + throws BuildException + { + try + { + this.keysize = Integer.parseInt( keysize ); + } + catch( final NumberFormatException nfe ) + { + throw new BuildException( "KeySize attribute should be a integer" ); + } + } + + public void setKeystore( final String keystore ) + { + this.keystore = keystore; + } + + public void setSigalg( final String sigalg ) + { + this.sigalg = sigalg; + } + + public void setStorepass( final String storepass ) + { + this.storepass = storepass; + } + + public void setStoretype( final String storetype ) + { + this.storetype = storetype; + } + + public void setValidity( final String validity ) + throws BuildException + { + try + { + this.validity = Integer.parseInt( validity ); + } + catch( final NumberFormatException nfe ) + { + throw new BuildException( "Validity attribute should be a integer" ); + } + } + + public void setVerbose( final boolean verbose ) + { + this.verbose = verbose; + } + + public DistinguishedName createDname() + throws BuildException + { + if( null != expandedDname ) + { + throw new BuildException( "DName sub-element can only be specified once." ); + } + if( null != dname ) + { + throw new BuildException( "It is not possible to specify dname both " + + "as attribute and element." ); + } + expandedDname = new DistinguishedName(); + return expandedDname; + } + + public void execute() + throws BuildException + { + if( project.getJavaVersion().equals( Project.JAVA_1_1 ) ) + { + throw new BuildException( "The genkey task is only available on JDK" + + " versions 1.2 or greater" ); + } + + if( null == alias ) + { + throw new BuildException( "alias attribute must be set" ); + } + + if( null == storepass ) + { + throw new BuildException( "storepass attribute must be set" ); + } + + if( null == dname && null == expandedDname ) + { + throw new BuildException( "dname must be set" ); + } + + final StringBuffer sb = new StringBuffer(); + + sb.append( "keytool -genkey " ); + + if( verbose ) + { + sb.append( "-v " ); + } + + sb.append( "-alias \"" ); + sb.append( alias ); + sb.append( "\" " ); + + if( null != dname ) + { + sb.append( "-dname \"" ); + sb.append( dname ); + sb.append( "\" " ); + } + + if( null != expandedDname ) + { + sb.append( "-dname \"" ); + sb.append( expandedDname ); + sb.append( "\" " ); + } + + if( null != keystore ) + { + sb.append( "-keystore \"" ); + sb.append( keystore ); + sb.append( "\" " ); + } + + if( null != storepass ) + { + sb.append( "-storepass \"" ); + sb.append( storepass ); + sb.append( "\" " ); + } + + if( null != storetype ) + { + sb.append( "-storetype \"" ); + sb.append( storetype ); + sb.append( "\" " ); + } + + sb.append( "-keypass \"" ); + if( null != keypass ) + { + sb.append( keypass ); + } + else + { + sb.append( storepass ); + } + sb.append( "\" " ); + + if( null != sigalg ) + { + sb.append( "-sigalg \"" ); + sb.append( sigalg ); + sb.append( "\" " ); + } + + if( null != keyalg ) + { + sb.append( "-keyalg \"" ); + sb.append( keyalg ); + sb.append( "\" " ); + } + + if( 0 < keysize ) + { + sb.append( "-keysize \"" ); + sb.append( keysize ); + sb.append( "\" " ); + } + + if( 0 < validity ) + { + sb.append( "-validity \"" ); + sb.append( validity ); + sb.append( "\" " ); + } + + log( "Generating Key for " + alias ); + final ExecTask cmd = ( ExecTask )project.createTask( "exec" ); + cmd.setCommand( new Commandline( sb.toString() ) ); + cmd.setFailonerror( true ); + cmd.setTaskName( getTaskName() ); + cmd.execute(); + } + + public static class DistinguishedName + { + + private Vector params = new Vector(); + private String name; + private String path; + + public Enumeration getParams() + { + return params.elements(); + } + + public Object createParam() + { + DnameParam param = new DnameParam(); + params.addElement( param ); + + return param; + } + + public String encode( final String string ) + { + int end = string.indexOf( ',' ); + + if( -1 == end ) + return string; + + final StringBuffer sb = new StringBuffer(); + + int start = 0; + + while( -1 != end ) + { + sb.append( string.substring( start, end ) ); + sb.append( "\\," ); + start = end + 1; + end = string.indexOf( ',', start ); + } + + sb.append( string.substring( start ) ); + + return sb.toString(); + } + + public String toString() + { + final int size = params.size(); + final StringBuffer sb = new StringBuffer(); + boolean firstPass = true; + + for( int i = 0; i < size; i++ ) + { + if( !firstPass ) + { + sb.append( " ," ); + } + firstPass = false; + + final DnameParam param = ( DnameParam )params.elementAt( i ); + sb.append( encode( param.getName() ) ); + sb.append( '=' ); + sb.append( encode( param.getValue() ) ); + } + + return sb.toString(); + } + } + + public static class DnameParam + { + private String name; + private String value; + + public void setName( String name ) + { + this.name = name; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Get.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Get.java new file mode 100644 index 000000000..6c1eb4451 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Get.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.Date; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Get a particular file from a URL source. Options include verbose reporting, + * timestamp based fetches and controlling actions on failures. NB: access + * through a firewall only works if the whole Java runtime is correctly + * configured. + * + * @author costin@dnt.ro + * @author gg@grtmail.com (Added Java 1.1 style HTTP basic auth) + */ +public class Get extends Task +{// required + private boolean verbose = false; + private boolean useTimestamp = false;//off by default + private boolean ignoreErrors = false; + private String uname = null; + private String pword = null;// required + private File dest; + private URL source; + + /** + * Where to copy the source file. + * + * @param dest Path to file. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Don't stop if get fails if set to "true". + * + * @param v if "true" then don't report download errors up to ant + */ + public void setIgnoreErrors( boolean v ) + { + ignoreErrors = v; + } + + /** + * password for the basic auth. + * + * @param p password for authentication + */ + public void setPassword( String p ) + { + this.pword = p; + } + + /** + * Set the URL. + * + * @param u URL for the file. + */ + public void setSrc( URL u ) + { + this.source = u; + } + + /** + * Use timestamps, if set to "true".

      + * + * In this situation, the if-modified-since header is set so that the file + * is only fetched if it is newer than the local file (or there is no local + * file) This flag is only valid on HTTP connections, it is ignored in other + * cases. When the flag is set, the local copy of the downloaded file will + * also have its timestamp set to the remote file time.
      + * Note that remote files of date 1/1/1970 (GMT) are treated as 'no + * timestamp', and web servers often serve files with a timestamp in the + * future by replacing their timestamp with that of the current time. Also, + * inter-computer clock differences can cause no end of grief. + * + * @param v "true" to enable file time fetching + */ + public void setUseTimestamp( boolean v ) + { + if( project.getJavaVersion() != Project.JAVA_1_1 ) + { + useTimestamp = v; + } + } + + + /** + * Username for basic auth. + * + * @param u username for authentication + */ + public void setUsername( String u ) + { + this.uname = u; + } + + /** + * Be verbose, if set to "true". + * + * @param v if "true" then be verbose + */ + public void setVerbose( boolean v ) + { + verbose = v; + } + + + /** + * Does the work. + * + * @exception BuildException Thrown in unrecoverable error. + */ + public void execute() + throws BuildException + { + if( source == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( dest == null ) + { + throw new BuildException( "dest attribute is required", location ); + } + + if( dest.exists() && dest.isDirectory() ) + { + throw new BuildException( "The specified destination is a directory", + location ); + } + + if( dest.exists() && !dest.canWrite() ) + { + throw new BuildException( "Can't write to " + dest.getAbsolutePath(), + location ); + } + + try + { + + log( "Getting: " + source ); + + //set the timestamp to the file date. + long timestamp = 0; + + boolean hasTimestamp = false; + if( useTimestamp && dest.exists() ) + { + timestamp = dest.lastModified(); + if( verbose ) + { + Date t = new Date( timestamp ); + log( "local file date : " + t.toString() ); + } + + hasTimestamp = true; + } + + //set up the URL connection + URLConnection connection = source.openConnection(); + //modify the headers + //NB: things like user authentication could go in here too. + if( useTimestamp && hasTimestamp ) + { + connection.setIfModifiedSince( timestamp ); + } + // prepare Java 1.1 style credentials + if( uname != null || pword != null ) + { + String up = uname + ":" + pword; + String encoding; + // check to see if sun's Base64 encoder is available. + try + { + sun.misc.BASE64Encoder encoder = + ( sun.misc.BASE64Encoder )Class.forName( "sun.misc.BASE64Encoder" ).newInstance(); + encoding = encoder.encode( up.getBytes() ); + + } + catch( Exception ex ) + {// sun's base64 encoder isn't available + Base64Converter encoder = new Base64Converter(); + encoding = encoder.encode( up.getBytes() ); + } + connection.setRequestProperty( "Authorization", "Basic " + encoding ); + } + + //connect to the remote site (may take some time) + connection.connect(); + //next test for a 304 result (HTTP only) + if( connection instanceof HttpURLConnection ) + { + HttpURLConnection httpConnection = ( HttpURLConnection )connection; + if( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED ) + { + //not modified so no file download. just return instead + //and trace out something so the user doesn't think that the + //download happened when it didnt + log( "Not modified - so not downloaded" ); + return; + } + // test for 401 result (HTTP only) + if( httpConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED ) + { + log( "Not authorized - check " + dest + " for details" ); + return; + } + + } + + //REVISIT: at this point even non HTTP connections may support the if-modified-since + //behaviour -we just check the date of the content and skip the write if it is not + //newer. Some protocols (FTP) dont include dates, of course. + + FileOutputStream fos = new FileOutputStream( dest ); + + InputStream is = null; + for( int i = 0; i < 3; i++ ) + { + try + { + is = connection.getInputStream(); + break; + } + catch( IOException ex ) + { + log( "Error opening connection " + ex ); + } + } + if( is == null ) + { + log( "Can't get " + source + " to " + dest ); + if( ignoreErrors ) + return; + throw new BuildException( "Can't get " + source + " to " + dest, + location ); + } + + byte[] buffer = new byte[100 * 1024]; + int length; + + while( ( length = is.read( buffer ) ) >= 0 ) + { + fos.write( buffer, 0, length ); + if( verbose ) + System.out.print( "." ); + } + if( verbose ) + System.out.println(); + fos.close(); + is.close(); + + //if (and only if) the use file time option is set, then the + //saved file now has its timestamp set to that of the downloaded file + if( useTimestamp ) + { + long remoteTimestamp = connection.getLastModified(); + if( verbose ) + { + Date t = new Date( remoteTimestamp ); + log( "last modified = " + t.toString() + + ( ( remoteTimestamp == 0 ) ? " - using current time instead" : "" ) ); + } + if( remoteTimestamp != 0 ) + touchFile( dest, remoteTimestamp ); + } + } + catch( IOException ioe ) + { + log( "Error getting " + source + " to " + dest ); + if( ignoreErrors ) + return; + throw new BuildException( ioe); + } + } + + /** + * set the timestamp of a named file to a specified time. + * + * @param file Description of Parameter + * @param timemillis Description of Parameter + * @return true if it succeeded. False means that this is a java1.1 system + * and that file times can not be set + * @exception BuildException Thrown in unrecoverable error. Likely this + * comes from file access failures. + */ + protected boolean touchFile( File file, long timemillis ) + throws BuildException + { + + if( project.getJavaVersion() != Project.JAVA_1_1 ) + { + Touch touch = ( Touch )project.createTask( "touch" ); + touch.setOwningTarget( target ); + touch.setTaskName( getTaskName() ); + touch.setLocation( getLocation() ); + touch.setFile( file ); + touch.setMillis( timemillis ); + touch.touch(); + return true; + } + else + { + return false; + } + } + + /** + * BASE 64 encoding of a String or an array of bytes. Based on RFC 1421. + * + * @author Unknown + * @author Gautam Guliani + */ + + class Base64Converter + { + + public final char[] alphabet = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0 to 7 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 8 to 15 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 16 to 23 + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 24 to 31 + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 32 to 39 + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 40 to 47 + 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 48 to 55 + '4', '5', '6', '7', '8', '9', '+', '/'};// 56 to 63 + + + public String encode( String s ) + { + return encode( s.getBytes() ); + } + + public String encode( byte[] octetString ) + { + int bits24; + int bits6; + + char[] out + = new char[( ( octetString.length - 1 ) / 3 + 1 ) * 4]; + + int outIndex = 0; + int i = 0; + + while( ( i + 3 ) <= octetString.length ) + { + // store the octets + bits24 = ( octetString[i++] & 0xFF ) << 16; + bits24 |= ( octetString[i++] & 0xFF ) << 8; + + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x00000FC0 ) >> 6; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0000003F ); + out[outIndex++] = alphabet[bits6]; + } + + if( octetString.length - i == 2 ) + { + // store the octets + bits24 = ( octetString[i] & 0xFF ) << 16; + bits24 |= ( octetString[i + 1] & 0xFF ) << 8; + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x00000FC0 ) >> 6; + out[outIndex++] = alphabet[bits6]; + + // padding + out[outIndex++] = '='; + } + else if( octetString.length - i == 1 ) + { + // store the octets + bits24 = ( octetString[i] & 0xFF ) << 16; + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + + // padding + out[outIndex++] = '='; + out[outIndex++] = '='; + } + + return new String( out ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Input.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Input.java new file mode 100644 index 000000000..caf080870 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Input.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.*; + + +/** + * Ant task to read input line from console. + * + * @author Ulrich Schmidt + */ +public class Input extends Task +{ + private String validargs = null; + private String message = ""; + private String addproperty = null; + private String input = null; + + /** + * No arg constructor. + */ + public Input() { } + + /** + * Defines the name of a property to be created from input. Behaviour is + * according to property task which means that existing properties cannot be + * overriden. + * + * @param addproperty Name for the property to be created from input + */ + public void setAddproperty( String addproperty ) + { + this.addproperty = addproperty; + } + + /** + * Sets the Message which gets displayed to the user during the build run. + * + * @param message The message to be displayed. + */ + public void setMessage( String message ) + { + this.message = message; + } + + /** + * Sets surrogate input to allow automated testing. + * + * @param testinput The new Testinput value + */ + public void setTestinput( String testinput ) + { + this.input = testinput; + } + + /** + * Defines valid input parameters as comma separated String. If set, input + * task will reject any input not defined as accepted and requires the user + * to reenter it. Validargs are case sensitive. If you want 'a' and 'A' to + * be accepted you need to define both values as accepted arguments. + * + * @param validargs A comma separated String defining valid input args. + */ + public void setValidargs( String validargs ) + { + this.validargs = validargs; + } + + // copied n' pasted from org.apache.tools.ant.taskdefs.Exit + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + /** + * Actual test method executed by jakarta-ant. + * + * @exception BuildException + */ + public void execute() + throws BuildException + { + Vector accept = null; + if( validargs != null ) + { + accept = new Vector(); + StringTokenizer stok = new StringTokenizer( validargs, ",", false ); + while( stok.hasMoreTokens() ) + { + accept.addElement( stok.nextToken() ); + } + } + log( message, Project.MSG_WARN ); + if( input == null ) + { + try + { + BufferedReader in = new BufferedReader( new InputStreamReader( System.in ) ); + input = in.readLine(); + if( accept != null ) + { + while( !accept.contains( input ) ) + { + log( message, Project.MSG_WARN ); + input = in.readLine(); + } + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to read input from Console.", e ); + } + } + // not quite the original intention of this task but for the sake + // of testing ;-) + else + { + if( accept != null && ( !accept.contains( input ) ) ) + { + throw new BuildException( "Invalid input please reenter." ); + } + } + // adopted from org.apache.tools.ant.taskdefs.Property + if( addproperty != null ) + { + if( project.getProperty( addproperty ) == null ) + { + project.setProperty( addproperty, input ); + } + else + { + log( "Override ignored for " + addproperty, Project.MSG_VERBOSE ); + } + } + } +} + + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jar.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jar.java new file mode 100644 index 000000000..1b2d8a976 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jar.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.*; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.FileScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + +/** + * Creates a JAR archive. + * + * @author James Davidson duncan@x180.com + */ +public class Jar extends Zip +{ + /** + * The index file name. + */ + private final static String INDEX_NAME = "META-INF/INDEX.LIST"; + + /** + * true if a manifest has been specified in the task + */ + private boolean buildFileManifest = false; + + /** + * jar index is JDK 1.3+ only + */ + private boolean index = false; + private Manifest execManifest; + private Manifest manifest; + + private File manifestFile; + + /** + * constructor + */ + public Jar() + { + super(); + archiveType = "jar"; + emptyBehavior = "create"; + setEncoding( "UTF8" ); + } + + /** + * Set whether or not to create an index list for classes to speed up + * classloading. + * + * @param flag The new Index value + */ + public void setIndex( boolean flag ) + { + index = flag; + } + + /** + * @param jarFile The new Jarfile value + * @deprecated use setFile(File) instead. + */ + public void setJarfile( File jarFile ) + { + log( "DEPRECATED - The jarfile attribute is deprecated. Use file attribute instead." ); + setFile( jarFile ); + } + + public void setManifest( File manifestFile ) + { + if( !manifestFile.exists() ) + { + throw new BuildException( "Manifest file: " + manifestFile + " does not exist.", + getLocation() ); + } + + this.manifestFile = manifestFile; + + Reader r = null; + try + { + r = new FileReader( manifestFile ); + Manifest newManifest = new Manifest( r ); + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + manifest.merge( newManifest ); + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest: " + manifestFile, e, getLocation() ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest file: " + manifestFile, e ); + } + finally + { + if( r != null ) + { + try + { + r.close(); + } + catch( IOException e ) + { + // do nothing + } + } + } + } + + public void setWhenempty( WhenEmpty we ) + { + log( "JARs are never empty, they contain at least a manifest file", + Project.MSG_WARN ); + } + + public void addConfiguredManifest( Manifest newManifest ) + throws ManifestException + { + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + manifest.merge( newManifest ); + buildFileManifest = true; + } + + public void addMetainf( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "META-INF/" ); + super.addFileset( fs ); + } + + /** + * Check whether the archive is up-to-date; + * + * @param scanners list of prepared scanners containing files to archive + * @param zipFile intended archive file (may or may not exist) + * @return true if nothing need be done (may have done something already); + * false if archive creation should proceed + * @exception BuildException if it likes + */ + protected boolean isUpToDate( FileScanner[] scanners, File zipFile ) + throws BuildException + { + // need to handle manifest as a special check + if( buildFileManifest || manifestFile == null ) + { + java.util.zip.ZipFile theZipFile = null; + try + { + theZipFile = new java.util.zip.ZipFile( zipFile ); + java.util.zip.ZipEntry entry = theZipFile.getEntry( "META-INF/MANIFEST.MF" ); + if( entry == null ) + { + log( "Updating jar since the current jar has no manifest", Project.MSG_VERBOSE ); + return false; + } + Manifest currentManifest = new Manifest( new InputStreamReader( theZipFile.getInputStream( entry ) ) ); + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + if( !currentManifest.equals( manifest ) ) + { + log( "Updating jar since jar manifest has changed", Project.MSG_VERBOSE ); + return false; + } + } + catch( Exception e ) + { + // any problems and we will rebuild + log( "Updating jar since cannot read current jar manifest: " + e.getClass().getName() + e.getMessage(), + Project.MSG_VERBOSE ); + return false; + } + finally + { + if( theZipFile != null ) + { + try + { + theZipFile.close(); + } + catch( IOException e ) + { + //ignore + } + } + } + } + else if( manifestFile.lastModified() > zipFile.lastModified() ) + { + return false; + } + return super.isUpToDate( scanners, zipFile ); + } + + /** + * Make sure we don't think we already have a MANIFEST next time this task + * gets executed. + */ + protected void cleanUp() + { + super.cleanUp(); + } + + protected boolean createEmptyZip( File zipFile ) + { + // Jar files always contain a manifest and can never be empty + return false; + } + + protected void finalizeZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + if( index ) + { + createIndexList( zOut ); + } + } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + try + { + execManifest = Manifest.getDefaultManifest(); + + if( manifest != null ) + { + execManifest.merge( manifest ); + } + for( Enumeration e = execManifest.getWarnings(); e.hasMoreElements(); ) + { + log( "Manifest warning: " + ( String )e.nextElement(), Project.MSG_WARN ); + } + + zipDir( null, zOut, "META-INF/" ); + // time to write the manifest + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter( baos ); + execManifest.write( writer ); + writer.flush(); + + ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() ); + super.zipFile( bais, zOut, "META-INF/MANIFEST.MF", System.currentTimeMillis() ); + super.initZipOutputStream( zOut ); + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest", e, getLocation() ); + } + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is META-INF/MANIFEST.MF, we warn if it's not the + // one specified in the "manifest" attribute - or if it's being added twice, + // meaning the same file is specified by the "manifeset" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "META-INF/MANIFEST.MF" ) ) + { + log( "Warning: selected " + archiveType + " files include a META-INF/MANIFEST.MF which will be ignored " + + "(please use manifest attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + } + + } + + protected void zipFile( InputStream is, ZipOutputStream zOut, String vPath, long lastModified ) + throws IOException + { + // If the file being added is META-INF/MANIFEST.MF, we merge it with the + // current manifest + if( vPath.equalsIgnoreCase( "META-INF/MANIFEST.MF" ) ) + { + try + { + zipManifestEntry( is ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest file: ", e ); + } + } + else + { + super.zipFile( is, zOut, vPath, lastModified ); + } + } + + /** + * Create the index list to speed up classloading. This is a JDK 1.3+ + * specific feature and is enabled by default. {@link + * http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#JAR%20Index} + * + * @param zOut the zip stream representing the jar being built. + * @throws IOException thrown if there is an error while creating the index + * and adding it to the zip stream. + */ + private void createIndexList( ZipOutputStream zOut ) + throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // encoding must be UTF8 as specified in the specs. + PrintWriter writer = new PrintWriter( new OutputStreamWriter( baos, "UTF8" ) ); + + // version-info blankline + writer.println( "JarIndex-Version: 1.0" ); + writer.println(); + + // header newline + writer.println( zipFile.getName() ); + + // JarIndex is sorting the directories by ascending order. + // it's painful to do in JDK 1.1 and it has no value but cosmetic + // since it will be read into a hashtable by the classloader. + Enumeration enum = addedDirs.keys(); + while( enum.hasMoreElements() ) + { + String dir = ( String )enum.nextElement(); + + // try to be smart, not to be fooled by a weird directory name + // @fixme do we need to check for directories starting by ./ ? + dir = dir.replace( '\\', '/' ); + int pos = dir.lastIndexOf( '/' ); + if( pos != -1 ) + { + dir = dir.substring( 0, pos ); + } + + // looks like nothing from META-INF should be added + // and the check is not case insensitive. + // see sun.misc.JarIndex + if( dir.startsWith( "META-INF" ) ) + { + continue; + } + // name newline + writer.println( dir ); + } + + writer.flush(); + ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() ); + super.zipFile( bais, zOut, INDEX_NAME, System.currentTimeMillis() ); + } + + + + /** + * Handle situation when we encounter a manifest file If we haven't been + * given one, we use this one. If we have, we merge the manifest in, + * provided it is a new file and not the old one from the JAR we are + * updating + * + * @param is Description of Parameter + * @exception IOException Description of Exception + */ + private void zipManifestEntry( InputStream is ) + throws IOException + { + try + { + if( execManifest == null ) + { + execManifest = new Manifest( new InputStreamReader( is ) ); + } + else if( isAddingNewFiles() ) + { + execManifest.merge( new Manifest( new InputStreamReader( is ) ) ); + } + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest", e, getLocation() ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Java.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Java.java new file mode 100644 index 000000000..060a331c6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Java.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ExitException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * This task acts as a loader for java applications but allows to use the same + * JVM for the called application thus resulting in much faster operation. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Stefan Bodewig + */ +public class Java extends Task +{ + + private CommandlineJava cmdl = new CommandlineJava(); + private boolean fork = false; + private File dir = null; + private PrintStream outStream = null; + private boolean failOnError = false; + private File out; + + /** + * Set the command line arguments for the class. + * + * @param s The new Args value + */ + public void setArgs( String s ) + { + log( "The args attribute is deprecated. " + + "Please use nested arg elements.", + Project.MSG_WARN ); + cmdl.createArgument().setLine( s ); + } + + /** + * Set the class name. + * + * @param s The new Classname value + * @exception BuildException Description of Exception + */ + public void setClassname( String s ) + throws BuildException + { + if( cmdl.getJar() != null ) + { + throw new BuildException( "Cannot use 'jar' and 'classname' attributes in same command" ); + } + cmdl.setClassname( s ); + } + + /** + * Set the classpath to be used for this compilation. + * + * @param s The new Classpath value + */ + public void setClasspath( Path s ) + { + createClasspath().append( s ); + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * The working directory of the process + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * Throw a BuildException if process returns non 0. + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Set the forking flag. + * + * @param s The new Fork value + */ + public void setFork( boolean s ) + { + this.fork = s; + } + + public void setJVMVersion( String value ) + { + cmdl.setVmversion( value ); + } + + /** + * set the jar name... + * + * @param jarfile The new Jar value + * @exception BuildException Description of Exception + */ + public void setJar( File jarfile ) + throws BuildException + { + if( cmdl.getClassname() != null ) + { + throw new BuildException( "Cannot use 'jar' and 'classname' attributes in same command." ); + } + cmdl.setJar( jarfile.getAbsolutePath() ); + } + + /** + * Set the command used to start the VM (only if fork==false). + * + * @param s The new Jvm value + */ + public void setJvm( String s ) + { + cmdl.setVm( s ); + } + + /** + * Set the command line arguments for the JVM. + * + * @param s The new Jvmargs value + */ + public void setJvmargs( String s ) + { + log( "The jvmargs attribute is deprecated. " + + "Please use nested jvmarg elements.", + Project.MSG_WARN ); + cmdl.createVmArgument().setLine( s ); + } + + /** + * -mx or -Xmx depending on VM version + * + * @param max The new Maxmemory value + */ + public void setMaxmemory( String max ) + { + cmdl.setMaxmemory( max ); + } + + /** + * File the output of the process is redirected to. + * + * @param out The new Output value + */ + public void setOutput( File out ) + { + this.out = out; + } + + /** + * Add a nested sysproperty element. + * + * @param sysp The feature to be added to the Sysproperty attribute + */ + public void addSysproperty( Environment.Variable sysp ) + { + cmdl.addSysproperty( sysp ); + } + + /** + * Clear out the arguments to this java task. + */ + public void clearArgs() + { + cmdl.clearJavaArgs(); + } + + /** + * Creates a nested arg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createArg() + { + return cmdl.createArgument(); + } + + /** + * Creates a nested classpath element + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return cmdl.createClasspath( project ).createPath(); + } + + /** + * Creates a nested jvmarg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createJvmarg() + { + return cmdl.createVmArgument(); + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + int err = -1; + if( ( err = executeJava() ) != 0 ) + { + if( failOnError ) + { + throw new BuildException( "Java returned: " + err, location ); + } + else + { + log( "Java Result: " + err, Project.MSG_ERR ); + } + } + } + + /** + * Do the execution and return a return code. + * + * @return the return code from the execute java class if it was executed in + * a separate VM (fork = "yes"). + * @exception BuildException Description of Exception + */ + public int executeJava() + throws BuildException + { + String classname = cmdl.getClassname(); + if( classname == null && cmdl.getJar() == null ) + { + throw new BuildException( "Classname must not be null." ); + } + if( !fork && cmdl.getJar() != null ) + { + throw new BuildException( "Cannot execute a jar in non-forked mode. Please set fork='true'. " ); + } + + if( fork ) + { + log( "Forking " + cmdl.toString(), Project.MSG_VERBOSE ); + + return run( cmdl.getCommandline() ); + } + else + { + if( cmdl.getVmCommand().size() > 1 ) + { + log( "JVM args ignored when same JVM is used.", Project.MSG_WARN ); + } + if( dir != null ) + { + log( "Working directory ignored when same JVM is used.", Project.MSG_WARN ); + } + + log( "Running in same VM " + cmdl.getJavaCommand().toString(), + Project.MSG_VERBOSE ); + try + { + run( cmdl ); + return 0; + } + catch( ExitException ex ) + { + return ex.getStatus(); + } + } + } + + protected void handleErrorOutput( String line ) + { + if( outStream != null ) + { + outStream.println( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( outStream != null ) + { + outStream.println( line ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Executes the given classname with the given arguments as it was a command + * line application. + * + * @param classname Description of Parameter + * @param args Description of Parameter + * @exception BuildException Description of Exception + */ + protected void run( String classname, Vector args ) + throws BuildException + { + CommandlineJava cmdj = new CommandlineJava(); + cmdj.setClassname( classname ); + for( int i = 0; i < args.size(); i++ ) + { + cmdj.createArgument().setValue( ( String )args.elementAt( i ) ); + } + run( cmdj ); + } + + /** + * Executes the given classname with the given arguments as it was a command + * line application. + * + * @param command Description of Parameter + * @exception BuildException Description of Exception + */ + private void run( CommandlineJava command ) + throws BuildException + { + ExecuteJava exe = new ExecuteJava(); + exe.setJavaCommand( command.getJavaCommand() ); + exe.setClasspath( command.getClasspath() ); + exe.setSystemProperties( command.getSystemProperties() ); + if( out != null ) + { + try + { + outStream = new PrintStream( new FileOutputStream( out ) ); + exe.execute( project ); + } + catch( IOException io ) + { + throw new BuildException( io ); + } + finally + { + if( outStream != null ) + { + outStream.close(); + } + } + } + else + { + exe.execute( project ); + } + } + + /** + * Executes the given classname with the given arguments in a separate VM. + * + * @param command Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int run( String[] command ) + throws BuildException + { + FileOutputStream fos = null; + try + { + Execute exe = null; + if( out == null ) + { + exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), + null ); + } + else + { + fos = new FileOutputStream( out ); + exe = new Execute( new PumpStreamHandler( fos ), null ); + } + + exe.setAntRun( project ); + + if( dir == null ) + { + dir = project.getBaseDir(); + } + else if( !dir.exists() || !dir.isDirectory() ) + { + throw new BuildException( dir.getAbsolutePath() + " is not a valid directory", + location ); + } + + exe.setWorkingDirectory( dir ); + + exe.setCommandline( command ); + try + { + return exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + catch( IOException io ) + { + throw new BuildException( io ); + } + finally + { + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException io ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javac.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javac.java new file mode 100644 index 000000000..7793bc4f2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javac.java @@ -0,0 +1,941 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.compilers.CompilerAdapter; +import org.apache.tools.ant.taskdefs.compilers.CompilerAdapterFactory; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.GlobPatternMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Task to compile Java source files. This task can take the following + * arguments: + *

        + *
      • sourcedir + *
      • destdir + *
      • deprecation + *
      • classpath + *
      • bootclasspath + *
      • extdirs + *
      • optimize + *
      • debug + *
      • encoding + *
      • target + *
      • depend + *
      • vebose + *
      • failonerror + *
      • includeantruntime + *
      • includejavaruntime + *
      • source + *
      + * Of these arguments, the sourcedir and destdir are required.

      + * + * When this task executes, it will recursively scan the sourcedir and destdir + * looking for Java source files to compile. This task makes its compile + * decision based on timestamp. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ + +public class Javac extends MatchingTask +{ + + private final static String FAIL_MSG + = "Compile failed, messages should have been provided."; + private boolean debug = false; + private boolean optimize = false; + private boolean deprecation = false; + private boolean depend = false; + private boolean verbose = false; + private boolean includeAntRuntime = true; + private boolean includeJavaRuntime = false; + private String fork = "false"; + private String forkedExecutable = null; + private boolean nowarn = false; + private Vector implementationSpecificArgs = new Vector(); + + protected boolean failOnError = true; + protected File[] compileList = new File[0]; + private Path bootclasspath; + private Path compileClasspath; + private String debugLevel; + private File destDir; + private String encoding; + private Path extdirs; + private String memoryInitialSize; + private String memoryMaximumSize; + + private String source; + + private Path src; + private String target; + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + /** + * Sets the bootclasspath that will be used to compile the classes against. + * + * @param bootclasspath The new Bootclasspath value + */ + public void setBootclasspath( Path bootclasspath ) + { + if( this.bootclasspath == null ) + { + this.bootclasspath = bootclasspath; + } + else + { + this.bootclasspath.append( bootclasspath ); + } + } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the debug flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Set the value of debugLevel. + * + * @param v Value to assign to debugLevel. + */ + public void setDebugLevel( String v ) + { + this.debugLevel = v; + } + + /** + * Set the depend flag. + * + * @param depend The new Depend value + */ + public void setDepend( boolean depend ) + { + this.depend = depend; + } + + /** + * Set the deprecation flag. + * + * @param deprecation The new Deprecation value + */ + public void setDeprecation( boolean deprecation ) + { + this.deprecation = deprecation; + } + + /** + * Set the destination directory into which the Java source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the Java source file encoding name. + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Sets the extension directories that will be used during the compilation. + * + * @param extdirs The new Extdirs value + */ + public void setExtdirs( Path extdirs ) + { + if( this.extdirs == null ) + { + this.extdirs = extdirs; + } + else + { + this.extdirs.append( extdirs ); + } + } + + /** + * Throw a BuildException if compilation fails + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Sets whether to fork the javac compiler. + * + * @param f "true|false|on|off|yes|no" or the name of the javac executable. + */ + public void setFork( String f ) + { + if( f.equalsIgnoreCase( "on" ) + || f.equalsIgnoreCase( "true" ) + || f.equalsIgnoreCase( "yes" ) ) + { + fork = "true"; + forkedExecutable = getSystemJavac(); + } + else if( f.equalsIgnoreCase( "off" ) + || f.equalsIgnoreCase( "false" ) + || f.equalsIgnoreCase( "no" ) ) + { + fork = "false"; + forkedExecutable = null; + } + else + { + fork = "true"; + forkedExecutable = f; + } + } + + /** + * Include ant's own classpath in this task's classpath? + * + * @param include The new Includeantruntime value + */ + public void setIncludeantruntime( boolean include ) + { + includeAntRuntime = include; + } + + /** + * Sets whether or not to include the java runtime libraries to this task's + * classpath. + * + * @param include The new Includejavaruntime value + */ + public void setIncludejavaruntime( boolean include ) + { + includeJavaRuntime = include; + } + + /** + * Set the memoryInitialSize flag. + * + * @param memoryInitialSize The new MemoryInitialSize value + */ + public void setMemoryInitialSize( String memoryInitialSize ) + { + this.memoryInitialSize = memoryInitialSize; + } + + /** + * Set the memoryMaximumSize flag. + * + * @param memoryMaximumSize The new MemoryMaximumSize value + */ + public void setMemoryMaximumSize( String memoryMaximumSize ) + { + this.memoryMaximumSize = memoryMaximumSize; + } + + /** + * Sets whether the -nowarn option should be used. + * + * @param flag The new Nowarn value + */ + public void setNowarn( boolean flag ) + { + this.nowarn = flag; + } + + /** + * Set the optimize flag. + * + * @param optimize The new Optimize value + */ + public void setOptimize( boolean optimize ) + { + this.optimize = optimize; + } + + /** + * Proceed if compilation fails + * + * @param proceed The new Proceed value + */ + public void setProceed( boolean proceed ) + { + failOnError = !proceed; + } + + /** + * Set the value of source. + * + * @param v Value to assign to source. + */ + public void setSource( String v ) + { + this.source = v; + } + + /** + * Set the source dirs to find the source Java files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( Path srcDir ) + { + if( src == null ) + { + src = srcDir; + } + else + { + src.append( srcDir ); + } + } + + /** + * Sets the target VM that the classes will be compiled for. Valid strings + * are "1.1", "1.2", and "1.3". + * + * @param target The new Target value + */ + public void setTarget( String target ) + { + this.target = target; + } + + /** + * Set the verbose flag. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + this.verbose = verbose; + } + + /** + * Gets the bootclasspath that will be used to compile the classes against. + * + * @return The Bootclasspath value + */ + public Path getBootclasspath() + { + return bootclasspath; + } + + /** + * Gets the classpath to be used for this compilation. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return compileClasspath; + } + + /** + * Get the additional implementation specific command line arguments. + * + * @return array of command line arguments, guaranteed to be non-null. + */ + public String[] getCurrentCompilerArgs() + { + Vector args = new Vector(); + for( Enumeration enum = implementationSpecificArgs.elements(); + enum.hasMoreElements(); + ) + { + String[] curr = + ( ( ImplementationSpecificArgument )enum.nextElement() ).getParts(); + for( int i = 0; i < curr.length; i++ ) + { + args.addElement( curr[i] ); + } + } + String[] res = new String[args.size()]; + args.copyInto( res ); + return res; + } + + /** + * Gets the debug flag. + * + * @return The Debug value + */ + public boolean getDebug() + { + return debug; + } + + /** + * Get the value of debugLevel. + * + * @return value of debugLevel. + */ + public String getDebugLevel() + { + return debugLevel; + } + + /** + * Gets the depend flag. + * + * @return The Depend value + */ + public boolean getDepend() + { + return depend; + } + + /** + * Gets the deprecation flag. + * + * @return The Deprecation value + */ + public boolean getDeprecation() + { + return deprecation; + } + + /** + * Gets the destination directory into which the java source files should be + * compiled. + * + * @return The Destdir value + */ + public File getDestdir() + { + return destDir; + } + + /** + * Gets the java source file encoding name. + * + * @return The Encoding value + */ + public String getEncoding() + { + return encoding; + } + + /** + * Gets the extension directories that will be used during the compilation. + * + * @return The Extdirs value + */ + public Path getExtdirs() + { + return extdirs; + } + + /** + * Gets the failonerror flag. + * + * @return The Failonerror value + */ + public boolean getFailonerror() + { + return failOnError; + } + + /** + * Gets the list of files to be compiled. + * + * @return The FileList value + */ + public File[] getFileList() + { + return compileList; + } + + /** + * Gets whether or not the ant classpath is to be included in the task's + * classpath. + * + * @return The Includeantruntime value + */ + public boolean getIncludeantruntime() + { + return includeAntRuntime; + } + + /** + * Gets whether or not the java runtime should be included in this task's + * classpath. + * + * @return The Includejavaruntime value + */ + public boolean getIncludejavaruntime() + { + return includeJavaRuntime; + } + + /** + * The name of the javac executable to use in fork-mode. + * + * @return The JavacExecutable value + */ + public String getJavacExecutable() + { + if( forkedExecutable == null && isForkedJavac() ) + { + forkedExecutable = getSystemJavac(); + } + else if( forkedExecutable != null && !isForkedJavac() ) + { + forkedExecutable = null; + } + return forkedExecutable; + } + + /** + * Gets the memoryInitialSize flag. + * + * @return The MemoryInitialSize value + */ + public String getMemoryInitialSize() + { + return memoryInitialSize; + } + + /** + * Gets the memoryMaximumSize flag. + * + * @return The MemoryMaximumSize value + */ + public String getMemoryMaximumSize() + { + return memoryMaximumSize; + } + + /** + * Should the -nowarn option be used. + * + * @return The Nowarn value + */ + public boolean getNowarn() + { + return nowarn; + } + + /** + * Gets the optimize flag. + * + * @return The Optimize value + */ + public boolean getOptimize() + { + return optimize; + } + + /** + * Get the value of source. + * + * @return value of source. + */ + public String getSource() + { + return source; + } + + /** + * Gets the source dirs to find the source java files. + * + * @return The Srcdir value + */ + public Path getSrcdir() + { + return src; + } + + /** + * Gets the target VM that the classes will be compiled for. + * + * @return The Target value + */ + public String getTarget() + { + return target; + } + + /** + * Gets the verbose flag. + * + * @return The Verbose value + */ + public boolean getVerbose() + { + return verbose; + } + + /** + * Is this a forked invocation of JDK's javac? + * + * @return The ForkedJavac value + */ + public boolean isForkedJavac() + { + return !"false".equals( fork ) || + "extJavac".equals( project.getProperty( "build.compiler" ) ); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath.createPath(); + } + + /** + * Adds an implementation specific command line argument. + * + * @return Description of the Returned Value + */ + public ImplementationSpecificArgument createCompilerArg() + { + ImplementationSpecificArgument arg = + new ImplementationSpecificArgument(); + implementationSpecificArgs.addElement( arg ); + return arg; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createExtdirs() + { + if( extdirs == null ) + { + extdirs = new Path( project ); + } + return extdirs.createPath(); + } + + /** + * Create a nested src element for multiple source path support. + * + * @return a nested src element. + */ + public Path createSrc() + { + if( src == null ) + { + src = new Path( project ); + } + return src.createPath(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + + if( src == null ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + String[] list = src.list(); + if( list.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + + if( destDir != null && !destDir.isDirectory() ) + { + throw new BuildException( "destination directory \"" + destDir + "\" does not exist or is not a directory", location ); + } + + // scan source directories and dest directory to build up + // compile lists + resetFileLists(); + for( int i = 0; i < list.length; i++ ) + { + File srcDir = ( File )project.resolveFile( list[i] ); + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir \"" + srcDir.getPath() + "\" does not exist!", location ); + } + + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + + scanDir( srcDir, destDir != null ? destDir : srcDir, files ); + } + + // compile the source files + + String compiler = determineCompiler(); + + if( compileList.length > 0 ) + { + + CompilerAdapter adapter = CompilerAdapterFactory.getCompiler( + compiler, this ); + log( "Compiling " + compileList.length + + " source file" + + ( compileList.length == 1 ? "" : "s" ) + + ( destDir != null ? " to " + destDir : "" ) ); + + // now we need to populate the compiler adapter + adapter.setJavac( this ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + if( failOnError ) + { + throw new BuildException( FAIL_MSG, location ); + } + else + { + log( FAIL_MSG, Project.MSG_ERR ); + } + } + } + } + + protected String getSystemJavac() + { + // This is the most common extension case - exe for windows and OS/2, + // nothing for *nix. + String extension = Os.isFamily( "dos" ) ? ".exe" : ""; + + // Look for java in the java.home/../bin directory. Unfortunately + // on Windows java.home doesn't always refer to the correct location, + // so we need to fall back to assuming java is somewhere on the + // PATH. + java.io.File jExecutable = + new java.io.File( System.getProperty( "java.home" ) + + "/../bin/javac" + extension ); + + if( jExecutable.exists() && !Os.isFamily( "netware" ) ) + { + return jExecutable.getAbsolutePath(); + } + else + { + return "javac"; + } + } + + protected boolean isJdkCompiler( String compiler ) + { + return "modern".equals( compiler ) || + "classic".equals( compiler ) || + "javac1.1".equals( compiler ) || + "javac1.2".equals( compiler ) || + "javac1.3".equals( compiler ) || + "javac1.4".equals( compiler ); + } + + /** + * Recreate src + * + * @return a nested src element. + */ + protected Path recreateSrc() + { + src = null; + return createSrc(); + } + + /** + * Clear the list of files to be compiled and copied.. + */ + protected void resetFileLists() + { + compileList = new File[0]; + } + + /** + * Scans the directory looking for source files to be compiled. The results + * are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, File destDir, String files[] ) + { + GlobPatternMapper m = new GlobPatternMapper(); + m.setFrom( "*.java" ); + m.setTo( "*.class" ); + SourceFileScanner sfs = new SourceFileScanner( this ); + File[] newFiles = sfs.restrictAsFiles( files, srcDir, destDir, m ); + + if( newFiles.length > 0 ) + { + File[] newCompileList = new File[compileList.length + + newFiles.length]; + System.arraycopy( compileList, 0, newCompileList, 0, + compileList.length ); + System.arraycopy( newFiles, 0, newCompileList, + compileList.length, newFiles.length ); + compileList = newCompileList; + } + } + + private String determineCompiler() + { + String compiler = project.getProperty( "build.compiler" ); + + if( !"false".equals( fork ) ) + { + if( compiler != null ) + { + if( isJdkCompiler( compiler ) ) + { + log( "Since fork is true, ignoring build.compiler setting.", + Project.MSG_WARN ); + compiler = "extJavac"; + } + else + { + log( "Since build.compiler setting isn't classic or modern, ignoring fork setting.", Project.MSG_WARN ); + } + } + else + { + compiler = "extJavac"; + } + } + + if( compiler == null ) + { + if( Project.getJavaVersion() != Project.JAVA_1_1 && + Project.getJavaVersion() != Project.JAVA_1_2 ) + { + compiler = "modern"; + } + else + { + compiler = "classic"; + } + } + return compiler; + } + + /** + * Adds an "implementation" attribute to Commandline$Attribute used to + * filter command line attributes based on the current implementation. + * + * @author RT + */ + public class ImplementationSpecificArgument + extends Commandline.Argument + { + + private String impl; + + public void setImplementation( String impl ) + { + this.impl = impl; + } + + public String[] getParts() + { + if( impl == null || impl.equals( determineCompiler() ) ) + { + return super.getParts(); + } + else + { + return new String[0]; + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JavacOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JavacOutputStream.java new file mode 100644 index 000000000..4d959fbf8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JavacOutputStream.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Task; + +/** + * Serves as an output stream to Javac. This let's us print messages out to the + * log and detect whether or not Javac had an error while compiling. + * + * @author James Duncan Davidson (duncan@x180.com) + * @deprecated use returnvalue of compile to detect compilation failure. + */ + +class JavacOutputStream extends OutputStream +{ + private boolean errorFlag = false; + private StringBuffer line; + + private Task task; + + /** + * Constructs a new JavacOutputStream with the given task as the output + * source for messages. + * + * @param task Description of Parameter + */ + + JavacOutputStream( Task task ) + { + this.task = task; + line = new StringBuffer(); + } + + /** + * Write a character to the output stream. This method looks to make sure + * that there isn't an error being reported and will flush each line of + * input out to the project's log stream. + * + * @param c Description of Parameter + * @exception IOException Description of Exception + */ + + public void write( int c ) + throws IOException + { + char cc = ( char )c; + if( cc == '\r' || cc == '\n' ) + { + // line feed + if( line.length() > 0 ) + { + processLine(); + } + } + else + { + line.append( cc ); + } + } + + /** + * Returns the error status of the compile. If no errors occured, this + * method will return false, else this method will return true. + * + * @return The ErrorFlag value + */ + + boolean getErrorFlag() + { + return errorFlag; + } + + /** + * Processes a line of input and determines if an error occured. + */ + + private void processLine() + { + String s = line.toString(); + if( s.indexOf( "error" ) > -1 ) + { + errorFlag = true; + } + task.log( s ); + line = new StringBuffer(); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javadoc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javadoc.java new file mode 100644 index 000000000..e95245de7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javadoc.java @@ -0,0 +1,1473 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + +/** + * This task makes it easy to generate Javadoc documentation for a collection of + * source code.

      + * + * Current known limitations are:

      + * + * + *

        + *
      • patterns must be of the form "xxx.*", every other pattern doesn't + * work. + *
      • the java comment-stripper reader is horribly slow + *
      • there is no control on arguments sanity since they are left to the + * javadoc implementation. + *
      • argument J in javadoc1 is not supported (what is that for anyway?) + * + *
      + *

      + * + * If no doclet is set, then the version and author + * are by default "yes".

      + * + * Note: This task is run on another VM because the Javadoc code calls System.exit() + * which would break Ant functionality. + * + * @author Jon S. Stevens jon@clearink.com + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Patrick Chanezon + * chanezon@netscape.com + * @author Ernst de Haan ernst@jollem.com + * @author Stefan Bodewig + */ + +public class Javadoc extends Task +{ + private static boolean javadoc1 = + ( Project.getJavaVersion() == Project.JAVA_1_1 ); + + private Commandline cmd = new Commandline(); + + private boolean foundJavaFile = false; + private boolean failOnError = false; + private Path sourcePath = null; + private File destDir = null; + private Vector sourceFiles = new Vector(); + private Vector packageNames = new Vector( 5 ); + private Vector excludePackageNames = new Vector( 1 ); + private boolean author = true; + private boolean version = true; + private DocletInfo doclet = null; + private Path classpath = null; + private Path bootclasspath = null; + private String group = null; + private Vector compileList = new Vector( 10 ); + private String packageList = null; + private Vector links = new Vector( 2 ); + private Vector groups = new Vector( 2 ); + private boolean useDefaultExcludes = true; + private Html doctitle = null; + private Html header = null; + private Html footer = null; + private Html bottom = null; + private boolean useExternalFile = false; + private File tmpList = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + public void setAccess( AccessType at ) + { + cmd.createArgument().setValue( "-" + at.getValue() ); + } + + public void setAdditionalparam( String add ) + { + cmd.createArgument().setLine( add ); + } + + public void setAuthor( boolean src ) + { + author = src; + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + public void setBootclasspath( Path src ) + { + if( bootclasspath == null ) + { + bootclasspath = src; + } + else + { + bootclasspath.append( src ); + } + } + + public void setBottom( String src ) + { + Html h = new Html(); + h.addText( src ); + addBottom( h ); + } + + public void setCharset( String src ) + { + this.add12ArgIfNotEmpty( "-charset", src ); + } + + public void setClasspath( Path src ) + { + if( classpath == null ) + { + classpath = src; + } + else + { + classpath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + public void setDestdir( File dir ) + { + destDir = dir; + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + public void setDocencoding( String enc ) + { + cmd.createArgument().setValue( "-docencoding" ); + cmd.createArgument().setValue( enc ); + } + + public void setDoclet( String src ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.setName( src ); + } + + public void setDocletPath( Path src ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.setPath( src ); + } + + public void setDocletPathRef( Reference r ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.createPath().setRefid( r ); + } + + public void setDoctitle( String src ) + { + Html h = new Html(); + h.addText( src ); + addDoctitle( h ); + } + + public void setEncoding( String enc ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( enc ); + } + + public void setExcludePackageNames( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addExcludePackage( pn ); + } + } + + public void setExtdirs( String src ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-extdirs" ); + cmd.createArgument().setValue( src ); + } + } + + /** + * Should the build process fail if javadoc fails (as indicated by a non + * zero return code)?

      + * + * Default is false.

      + * + * @param b The new Failonerror value + */ + public void setFailonerror( boolean b ) + { + failOnError = b; + } + + public void setFooter( String src ) + { + Html h = new Html(); + h.addText( src ); + addFooter( h ); + } + + public void setGroup( String src ) + { + group = src; + } + + public void setHeader( String src ) + { + Html h = new Html(); + h.addText( src ); + addHeader( h ); + } + + public void setHelpfile( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-helpfile" ); + cmd.createArgument().setFile( f ); + } + } + + public void setLink( String src ) + { + if( !javadoc1 ) + { + createLink().setHref( src ); + } + } + + public void setLinkoffline( String src ) + { + if( !javadoc1 ) + { + LinkArgument le = createLink(); + le.setOffline( true ); + String linkOfflineError = "The linkoffline attribute must include a URL and " + + "a package-list file location separated by a space"; + if( src.trim().length() == 0 ) + { + throw new BuildException( linkOfflineError ); + } + StringTokenizer tok = new StringTokenizer( src, " ", false ); + le.setHref( tok.nextToken() ); + + if( !tok.hasMoreTokens() ) + { + throw new BuildException( linkOfflineError ); + } + le.setPackagelistLoc( project.resolveFile( tok.nextToken() ) ); + } + } + + public void setLocale( String src ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-locale" ); + cmd.createArgument().setValue( src ); + } + } + + public void setMaxmemory( String max ) + { + if( javadoc1 ) + { + cmd.createArgument().setValue( "-J-mx" + max ); + } + else + { + cmd.createArgument().setValue( "-J-Xmx" + max ); + } + } + + public void setNodeprecated( boolean b ) + { + addArgIf( b, "-nodeprecated" ); + } + + public void setNodeprecatedlist( boolean b ) + { + add12ArgIf( b, "-nodeprecatedlist" ); + } + + public void setNohelp( boolean b ) + { + add12ArgIf( b, "-nohelp" ); + } + + public void setNoindex( boolean b ) + { + addArgIf( b, "-noindex" ); + } + + public void setNonavbar( boolean b ) + { + add12ArgIf( b, "-nonavbar" ); + } + + public void setNotree( boolean b ) + { + addArgIf( b, "-notree" ); + } + + public void setOld( boolean b ) + { + add12ArgIf( b, "-1.1" ); + } + + public void setOverview( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-overview" ); + cmd.createArgument().setFile( f ); + } + } + + public void setPackage( boolean b ) + { + addArgIf( b, "-package" ); + } + + public void setPackageList( String src ) + { + packageList = src; + } + + public void setPackagenames( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addPackage( pn ); + } + } + + public void setPrivate( boolean b ) + { + addArgIf( b, "-private" ); + } + + public void setProtected( boolean b ) + { + addArgIf( b, "-protected" ); + } + + public void setPublic( boolean b ) + { + addArgIf( b, "-public" ); + } + + public void setSerialwarn( boolean b ) + { + add12ArgIf( b, "-serialwarn" ); + } + + public void setSourcefiles( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String f = tok.nextToken(); + SourceFile sf = new SourceFile(); + sf.setFile( project.resolveFile( f ) ); + addSource( sf ); + } + } + + public void setSourcepath( Path src ) + { + if( sourcePath == null ) + { + sourcePath = src; + } + else + { + sourcePath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new SourcepathRef value + */ + public void setSourcepathRef( Reference r ) + { + createSourcepath().setRefid( r ); + } + + public void setSplitindex( boolean b ) + { + add12ArgIf( b, "-splitindex" ); + } + + public void setStylesheetfile( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-stylesheetfile" ); + cmd.createArgument().setFile( f ); + } + } + + public void setUse( boolean b ) + { + add12ArgIf( b, "-use" ); + } + + /** + * Work around command line length limit by using an external file for the + * sourcefiles. + * + * @param b The new UseExternalFile value + */ + public void setUseExternalFile( boolean b ) + { + if( !javadoc1 ) + { + useExternalFile = b; + } + } + + public void setVerbose( boolean b ) + { + add12ArgIf( b, "-verbose" ); + } + + public void setVersion( boolean src ) + { + version = src; + } + + public void setWindowtitle( String src ) + { + add12ArgIfNotEmpty( "-windowtitle", src ); + } + + public void addBottom( Html text ) + { + if( !javadoc1 ) + { + bottom = text; + } + } + + public void addDoctitle( Html text ) + { + if( !javadoc1 ) + { + doctitle = text; + } + } + + public void addExcludePackage( PackageName pn ) + { + excludePackageNames.addElement( pn ); + } + + public void addFooter( Html text ) + { + if( !javadoc1 ) + { + footer = text; + } + } + + public void addHeader( Html text ) + { + if( !javadoc1 ) + { + header = text; + } + } + + public void addPackage( PackageName pn ) + { + packageNames.addElement( pn ); + } + + public void addSource( SourceFile sf ) + { + sourceFiles.addElement( sf ); + } + + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + public DocletInfo createDoclet() + { + doclet = new DocletInfo(); + return doclet; + } + + public GroupArgument createGroup() + { + GroupArgument ga = new GroupArgument(); + groups.addElement( ga ); + return ga; + } + + public LinkArgument createLink() + { + LinkArgument la = new LinkArgument(); + links.addElement( la ); + return la; + } + + public Path createSourcepath() + { + if( sourcePath == null ) + { + sourcePath = new Path( project ); + } + return sourcePath.createPath(); + } + + public void execute() + throws BuildException + { + if( "javadoc2".equals( taskType ) ) + { + log( "!! javadoc2 is deprecated. Use javadoc instead. !!" ); + } + + if( sourcePath == null ) + { + String msg = "sourcePath attribute must be set!"; + throw new BuildException( msg ); + } + + log( "Generating Javadoc", Project.MSG_INFO ); + + if( doctitle != null ) + { + cmd.createArgument().setValue( "-doctitle" ); + cmd.createArgument().setValue( expand( doctitle.getText() ) ); + } + if( header != null ) + { + cmd.createArgument().setValue( "-header" ); + cmd.createArgument().setValue( expand( header.getText() ) ); + } + if( footer != null ) + { + cmd.createArgument().setValue( "-footer" ); + cmd.createArgument().setValue( expand( footer.getText() ) ); + } + if( bottom != null ) + { + cmd.createArgument().setValue( "-bottom" ); + cmd.createArgument().setValue( expand( bottom.getText() ) ); + } + + Commandline toExecute = ( Commandline )cmd.clone(); + toExecute.setExecutable( getJavadocExecutableName() ); + +// ------------------------------------------------ general javadoc arguments + if( classpath == null ) + classpath = Path.systemClasspath; + else + classpath = classpath.concatSystemClasspath( "ignore" ); + + if( !javadoc1 ) + { + toExecute.createArgument().setValue( "-classpath" ); + toExecute.createArgument().setPath( classpath ); + toExecute.createArgument().setValue( "-sourcepath" ); + toExecute.createArgument().setPath( sourcePath ); + } + else + { + toExecute.createArgument().setValue( "-classpath" ); + toExecute.createArgument().setValue( sourcePath.toString() + + System.getProperty( "path.separator" ) + classpath.toString() ); + } + + if( version && doclet == null ) + toExecute.createArgument().setValue( "-version" ); + if( author && doclet == null ) + toExecute.createArgument().setValue( "-author" ); + + if( javadoc1 || doclet == null ) + { + if( destDir == null ) + { + String msg = "destDir attribute must be set!"; + throw new BuildException( msg ); + } + } + +// --------------------------------- javadoc2 arguments for default doclet + +// XXX: how do we handle a custom doclet? + + if( !javadoc1 ) + { + if( doclet != null ) + { + if( doclet.getName() == null ) + { + throw new BuildException( "The doclet name must be specified.", location ); + } + else + { + toExecute.createArgument().setValue( "-doclet" ); + toExecute.createArgument().setValue( doclet.getName() ); + if( doclet.getPath() != null ) + { + toExecute.createArgument().setValue( "-docletpath" ); + toExecute.createArgument().setPath( doclet.getPath() ); + } + for( Enumeration e = doclet.getParams(); e.hasMoreElements(); ) + { + DocletParam param = ( DocletParam )e.nextElement(); + if( param.getName() == null ) + { + throw new BuildException( "Doclet parameters must have a name" ); + } + + toExecute.createArgument().setValue( param.getName() ); + if( param.getValue() != null ) + { + toExecute.createArgument().setValue( param.getValue() ); + } + } + } + } + if( bootclasspath != null ) + { + toExecute.createArgument().setValue( "-bootclasspath" ); + toExecute.createArgument().setPath( bootclasspath ); + } + + // add the links arguments + if( links.size() != 0 ) + { + for( Enumeration e = links.elements(); e.hasMoreElements(); ) + { + LinkArgument la = ( LinkArgument )e.nextElement(); + + if( la.getHref() == null ) + { + throw new BuildException( "Links must provide the URL to the external class documentation." ); + } + + if( la.isLinkOffline() ) + { + File packageListLocation = la.getPackagelistLoc(); + if( packageListLocation == null ) + { + throw new BuildException( "The package list location for link " + la.getHref() + + " must be provided because the link is offline" ); + } + File packageList = new File( packageListLocation, "package-list" ); + if( packageList.exists() ) + { + toExecute.createArgument().setValue( "-linkoffline" ); + toExecute.createArgument().setValue( la.getHref() ); + toExecute.createArgument().setValue( packageListLocation.getAbsolutePath() ); + } + else + { + log( "Warning: No package list was found at " + packageListLocation, + Project.MSG_VERBOSE ); + } + } + else + { + toExecute.createArgument().setValue( "-link" ); + toExecute.createArgument().setValue( la.getHref() ); + } + } + } + + // add the single group arguments + // Javadoc 1.2 rules: + // Multiple -group args allowed. + // Each arg includes 3 strings: -group [name] [packagelist]. + // Elements in [packagelist] are colon-delimited. + // An element in [packagelist] may end with the * wildcard. + + // Ant javadoc task rules for group attribute: + // Args are comma-delimited. + // Each arg is 2 space-delimited strings. + // E.g., group="XSLT_Packages org.apache.xalan.xslt*,XPath_Packages org.apache.xalan.xpath*" + if( group != null ) + { + StringTokenizer tok = new StringTokenizer( group, ",", false ); + while( tok.hasMoreTokens() ) + { + String grp = tok.nextToken().trim(); + int space = grp.indexOf( " " ); + if( space > 0 ) + { + String name = grp.substring( 0, space ); + String pkgList = grp.substring( space + 1 ); + toExecute.createArgument().setValue( "-group" ); + toExecute.createArgument().setValue( name ); + toExecute.createArgument().setValue( pkgList ); + } + } + } + + // add the group arguments + if( groups.size() != 0 ) + { + for( Enumeration e = groups.elements(); e.hasMoreElements(); ) + { + GroupArgument ga = ( GroupArgument )e.nextElement(); + String title = ga.getTitle(); + String packages = ga.getPackages(); + if( title == null || packages == null ) + { + throw new BuildException( "The title and packages must be specified for group elements." ); + } + toExecute.createArgument().setValue( "-group" ); + toExecute.createArgument().setValue( expand( title ) ); + toExecute.createArgument().setValue( packages ); + } + } + + } + + tmpList = null; + if( packageNames.size() > 0 ) + { + Vector packages = new Vector(); + Enumeration enum = packageNames.elements(); + while( enum.hasMoreElements() ) + { + PackageName pn = ( PackageName )enum.nextElement(); + String name = pn.getName().trim(); + if( name.endsWith( ".*" ) ) + { + packages.addElement( name ); + } + else + { + toExecute.createArgument().setValue( name ); + } + } + + Vector excludePackages = new Vector(); + if( excludePackageNames.size() > 0 ) + { + enum = excludePackageNames.elements(); + while( enum.hasMoreElements() ) + { + PackageName pn = ( PackageName )enum.nextElement(); + excludePackages.addElement( pn.getName().trim() ); + } + } + if( packages.size() > 0 ) + { + evaluatePackages( toExecute, sourcePath, packages, excludePackages ); + } + } + + if( sourceFiles.size() > 0 ) + { + PrintWriter srcListWriter = null; + try + { + + /** + * Write sourcefiles to a temporary file if requested. + */ + if( useExternalFile ) + { + if( tmpList == null ) + { + tmpList = fileUtils.createTempFile( "javadoc", "", null ); + toExecute.createArgument().setValue( "@" + tmpList.getAbsolutePath() ); + } + srcListWriter = new PrintWriter( new FileWriter( tmpList.getAbsolutePath(), + true ) ); + } + + Enumeration enum = sourceFiles.elements(); + while( enum.hasMoreElements() ) + { + SourceFile sf = ( SourceFile )enum.nextElement(); + String sourceFileName = sf.getFile().getAbsolutePath(); + if( useExternalFile ) + { + srcListWriter.println( sourceFileName ); + } + else + { + toExecute.createArgument().setValue( sourceFileName ); + } + } + + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", + e, location ); + } + finally + { + if( srcListWriter != null ) + { + srcListWriter.close(); + } + } + } + + if( packageList != null ) + { + toExecute.createArgument().setValue( "@" + packageList ); + } + log( "Javadoc args: " + toExecute, Project.MSG_VERBOSE ); + + log( "Javadoc execution", Project.MSG_INFO ); + + JavadocOutputStream out = new JavadocOutputStream( Project.MSG_INFO ); + JavadocOutputStream err = new JavadocOutputStream( Project.MSG_WARN ); + Execute exe = new Execute( new PumpStreamHandler( out, err ) ); + exe.setAntRun( project ); + + /* + * No reason to change the working directory as all filenames and + * path components have been resolved already. + * + * Avoid problems with command line length in some environments. + */ + exe.setWorkingDirectory( null ); + try + { + exe.setCommandline( toExecute.getCommandline() ); + int ret = exe.execute(); + if( ret != 0 && failOnError ) + { + throw new BuildException( "Javadoc returned " + ret, location ); + } + } + catch( IOException e ) + { + throw new BuildException( "Javadoc failed: " + e, e, location ); + } + finally + { + + if( tmpList != null ) + { + tmpList.delete(); + tmpList = null; + } + + out.logFlush(); + err.logFlush(); + try + { + out.close(); + err.close(); + } + catch( IOException e ) + {} + } + } + + /** + * Convenience method to expand properties. + * + * @param content Description of Parameter + * @return Description of the Returned Value + */ + protected String expand( String content ) + { + return project.replaceProperties( content ); + } + + private String getJavadocExecutableName() + { + // This is the most common extension case - exe for windows and OS/2, + // nothing for *nix. + String extension = Os.isFamily( "dos" ) ? ".exe" : ""; + + // Look for javadoc in the java.home/../bin directory. Unfortunately + // on Windows java.home doesn't always refer to the correct location, + // so we need to fall back to assuming javadoc is somewhere on the + // PATH. + File jdocExecutable = new File( System.getProperty( "java.home" ) + + "/../bin/javadoc" + extension ); + + if( jdocExecutable.exists() && !Os.isFamily( "netware" ) ) + { + return jdocExecutable.getAbsolutePath(); + } + else + { + if( !Os.isFamily( "netware" ) ) + { + log( "Unable to locate " + jdocExecutable.getAbsolutePath() + + ". Using \"javadoc\" instead.", Project.MSG_VERBOSE ); + } + return "javadoc"; + } + } + + private void add11ArgIf( boolean b, String arg ) + { + if( javadoc1 && b ) + { + cmd.createArgument().setValue( arg ); + } + } + + private void add12ArgIf( boolean b, String arg ) + { + if( !javadoc1 && b ) + { + cmd.createArgument().setValue( arg ); + } + } + + private void add12ArgIfNotEmpty( String key, String value ) + { + if( !javadoc1 ) + { + if( value != null && value.length() != 0 ) + { + cmd.createArgument().setValue( key ); + cmd.createArgument().setValue( value ); + } + else + { + project.log( this, + "Warning: Leaving out empty argument '" + key + "'", + Project.MSG_WARN ); + } + } + } + + + private void addArgIf( boolean b, String arg ) + { + if( b ) + { + cmd.createArgument().setValue( arg ); + } + } + + /** + * Given a source path, a list of package patterns, fill the given list with + * the packages found in that path subdirs matching one of the given + * patterns. + * + * @param toExecute Description of Parameter + * @param sourcePath Description of Parameter + * @param packages Description of Parameter + * @param excludePackages Description of Parameter + */ + private void evaluatePackages( Commandline toExecute, Path sourcePath, + Vector packages, Vector excludePackages ) + { + log( "Source path = " + sourcePath.toString(), Project.MSG_VERBOSE ); + StringBuffer msg = new StringBuffer( "Packages = " ); + for( int i = 0; i < packages.size(); i++ ) + { + if( i > 0 ) + { + msg.append( "," ); + } + msg.append( packages.elementAt( i ) ); + } + log( msg.toString(), Project.MSG_VERBOSE ); + + msg.setLength( 0 ); + msg.append( "Exclude Packages = " ); + for( int i = 0; i < excludePackages.size(); i++ ) + { + if( i > 0 ) + { + msg.append( "," ); + } + msg.append( excludePackages.elementAt( i ) ); + } + log( msg.toString(), Project.MSG_VERBOSE ); + + Vector addedPackages = new Vector(); + + String[] list = sourcePath.list(); + if( list == null ) + list = new String[0]; + + FileSet fs = new FileSet(); + fs.setDefaultexcludes( useDefaultExcludes ); + + Enumeration e = packages.elements(); + while( e.hasMoreElements() ) + { + String pkg = ( String )e.nextElement(); + pkg = pkg.replace( '.', '/' ); + if( pkg.endsWith( "*" ) ) + { + pkg += "*"; + } + + fs.createInclude().setName( pkg ); + }// while + + e = excludePackages.elements(); + while( e.hasMoreElements() ) + { + String pkg = ( String )e.nextElement(); + pkg = pkg.replace( '.', '/' ); + if( pkg.endsWith( "*" ) ) + { + pkg += "*"; + } + + fs.createExclude().setName( pkg ); + } + + PrintWriter packageListWriter = null; + try + { + if( useExternalFile ) + { + tmpList = fileUtils.createTempFile( "javadoc", "", null ); + toExecute.createArgument().setValue( "@" + tmpList.getAbsolutePath() ); + packageListWriter = new PrintWriter( new FileWriter( tmpList ) ); + } + + for( int j = 0; j < list.length; j++ ) + { + File source = project.resolveFile( list[j] ); + fs.setDir( source ); + + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] packageDirs = ds.getIncludedDirectories(); + + for( int i = 0; i < packageDirs.length; i++ ) + { + File pd = new File( source, packageDirs[i] ); + String[] files = pd.list( + new FilenameFilter() + { + public boolean accept( File dir1, String name ) + { + if( name.endsWith( ".java" ) ) + { + return true; + } + return false;// ignore dirs + } + } ); + + if( files.length > 0 ) + { + String pkgDir = packageDirs[i].replace( '/', '.' ).replace( '\\', '.' ); + if( !addedPackages.contains( pkgDir ) ) + { + if( useExternalFile ) + { + packageListWriter.println( pkgDir ); + } + else + { + toExecute.createArgument().setValue( pkgDir ); + } + addedPackages.addElement( pkgDir ); + } + } + } + } + } + catch( IOException ioex ) + { + throw new BuildException( "Error creating temporary file", + ioex, location ); + } + finally + { + if( packageListWriter != null ) + { + packageListWriter.close(); + } + } + } + + public static class AccessType extends EnumeratedAttribute + { + public String[] getValues() + { + // Protected first so if any GUI tool offers a default + // based on enum #0, it will be right. + return new String[]{"protected", "public", "package", "private"}; + } + } + + public static class Html + { + private StringBuffer text = new StringBuffer(); + + public String getText() + { + return text.toString(); + } + + public void addText( String t ) + { + text.append( t ); + } + } + + public static class PackageName + { + private String name; + + public void setName( String name ) + { + this.name = name; + } + + public String getName() + { + return name; + } + + public String toString() + { + return getName(); + } + } + + public static class SourceFile + { + private File file; + + public void setFile( File file ) + { + this.file = file; + } + + public File getFile() + { + return file; + } + } + + public class DocletInfo + { + + private Vector params = new Vector(); + private String name; + private Path path; + + public void setName( String name ) + { + this.name = name; + } + + public void setPath( Path path ) + { + if( this.path == null ) + { + this.path = path; + } + else + { + this.path.append( path ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new PathRef value + */ + public void setPathRef( Reference r ) + { + createPath().setRefid( r ); + } + + public String getName() + { + return name; + } + + public Enumeration getParams() + { + return params.elements(); + } + + public Path getPath() + { + return path; + } + + public DocletParam createParam() + { + DocletParam param = new DocletParam(); + params.addElement( param ); + + return param; + } + + public Path createPath() + { + if( path == null ) + { + path = new Path( getProject() ); + } + return path.createPath(); + } + } + + public class DocletParam + { + private String name; + private String value; + + public void setName( String name ) + { + this.name = name; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + } + + public class GroupArgument + { + private Vector packages = new Vector( 3 ); + private Html title; + + public GroupArgument() { } + + public void setPackages( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addPackage( pn ); + } + } + + public void setTitle( String src ) + { + Html h = new Html(); + h.addText( src ); + addTitle( h ); + } + + public String getPackages() + { + StringBuffer p = new StringBuffer(); + for( int i = 0; i < packages.size(); i++ ) + { + if( i > 0 ) + { + p.append( ":" ); + } + p.append( packages.elementAt( i ).toString() ); + } + return p.toString(); + } + + public String getTitle() + { + return title != null ? title.getText() : null; + } + + public void addPackage( PackageName pn ) + { + packages.addElement( pn ); + } + + public void addTitle( Html text ) + { + title = text; + } + } + + public class LinkArgument + { + private boolean offline = false; + private String href; + private File packagelistLoc; + + public LinkArgument() { } + + public void setHref( String hr ) + { + href = hr; + } + + public void setOffline( boolean offline ) + { + this.offline = offline; + } + + public void setPackagelistLoc( File src ) + { + packagelistLoc = src; + } + + public String getHref() + { + return href; + } + + public File getPackagelistLoc() + { + return packagelistLoc; + } + + public boolean isLinkOffline() + { + return offline; + } + } + + private class JavadocOutputStream extends LogOutputStream + { + + // + // Override the logging of output in order to filter out Generating + // messages. Generating messages are set to a priority of VERBOSE + // unless they appear after what could be an informational message. + // + private String queuedLine = null; + + JavadocOutputStream( int level ) + { + super( Javadoc.this, level ); + } + + + protected void logFlush() + { + if( queuedLine != null ) + { + super.processLine( queuedLine, Project.MSG_VERBOSE ); + queuedLine = null; + } + } + + protected void processLine( String line, int messageLevel ) + { + if( messageLevel == Project.MSG_INFO && line.startsWith( "Generating " ) ) + { + if( queuedLine != null ) + { + super.processLine( queuedLine, Project.MSG_VERBOSE ); + } + queuedLine = line; + } + else + { + if( queuedLine != null ) + { + if( line.startsWith( "Building " ) ) + super.processLine( queuedLine, Project.MSG_VERBOSE ); + else + super.processLine( queuedLine, Project.MSG_INFO ); + queuedLine = null; + } + super.processLine( line, messageLevel ); + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jikes.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jikes.java new file mode 100644 index 000000000..4e8700635 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jikes.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Random; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Encapsulates a Jikes compiler, by directly executing an external process. + * + * @author skanthak@muehlheim.de + * @deprecated merged into the class Javac. + */ +public class Jikes +{ + protected String command; + protected JikesOutputParser jop; + protected Project project; + + /** + * Constructs a new Jikes obect. + * + * @param jop - Parser to send jike's output to + * @param command - name of jikes executeable + * @param project Description of Parameter + */ + protected Jikes( JikesOutputParser jop, String command, Project project ) + { + super(); + this.jop = jop; + this.command = command; + this.project = project; + } + + /** + * Do the compile with the specified arguments. + * + * @param args - arguments to pass to process on command line + */ + protected void compile( String[] args ) + { + String[] commandArray = null; + File tmpFile = null; + + try + { + String myos = System.getProperty( "os.name" ); + + // Windows has a 32k limit on total arg size, so + // create a temporary file to store all the arguments + + // There have been reports that 300 files could be compiled + // so 250 is a conservative approach + if( myos.toLowerCase().indexOf( "windows" ) >= 0 + && args.length > 250 ) + { + PrintWriter out = null; + try + { + tmpFile = new File( "jikes" + ( new Random( System.currentTimeMillis() ) ).nextLong() ); + out = new PrintWriter( new FileWriter( tmpFile ) ); + for( int i = 0; i < args.length; i++ ) + { + out.println( args[i] ); + } + out.flush(); + commandArray = new String[]{command, + "@" + tmpFile.getAbsolutePath()}; + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", e ); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( Throwable t ) + {} + } + } + } + else + { + commandArray = new String[args.length + 1]; + commandArray[0] = command; + System.arraycopy( args, 0, commandArray, 1, args.length ); + } + + // We assume, that everything jikes writes goes to + // standard output, not to standard error. The option + // -Xstdout that is given to Jikes in Javac.doJikesCompile() + // should guarantee this. At least I hope so. :) + try + { + Execute exe = new Execute( jop ); + exe.setAntRun( project ); + exe.setWorkingDirectory( project.getBaseDir() ); + exe.setCommandline( commandArray ); + exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Error running Jikes compiler", e ); + } + } + finally + { + if( tmpFile != null ) + { + tmpFile.delete(); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JikesOutputParser.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JikesOutputParser.java new file mode 100644 index 000000000..3376ff97f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JikesOutputParser.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Parses output from jikes and passes errors and warnings into the right + * logging channels of Project. TODO: Parsing could be much better + * + * @author skanthak@muehlheim.de + * @deprecated use Jikes' exit value to detect compilation failure. + */ +public class JikesOutputParser implements ExecuteStreamHandler +{ + protected boolean errorFlag = false; + protected boolean error = false; + + protected BufferedReader br; + protected boolean emacsMode;// no errors so far + protected int errors, warnings; + protected Task task; + + /** + * Construct a new Parser object + * + * @param task - task in whichs context we are called + * @param emacsMode Description of Parameter + */ + protected JikesOutputParser( Task task, boolean emacsMode ) + { + super(); + this.task = task; + this.emacsMode = emacsMode; + } + + /** + * Ignore. + * + * @param is The new ProcessErrorStream value + */ + public void setProcessErrorStream( InputStream is ) { } + + /** + * Ignore. + * + * @param os The new ProcessInputStream value + */ + public void setProcessInputStream( OutputStream os ) { } + + /** + * Set the inputstream + * + * @param is The new ProcessOutputStream value + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + br = new BufferedReader( new InputStreamReader( is ) ); + } + + /** + * Invokes parseOutput. + * + * @exception IOException Description of Exception + */ + public void start() + throws IOException + { + parseOutput( br ); + } + + /** + * Ignore. + */ + public void stop() { } + + /** + * Indicate if there were errors during the compile + * + * @return if errors ocured + */ + protected boolean getErrorFlag() + { + return errorFlag; + } + + /** + * Parse the output of a jikes compiler + * + * @param reader - Reader used to read jikes's output + * @exception IOException Description of Exception + */ + protected void parseOutput( BufferedReader reader ) + throws IOException + { + if( emacsMode ) + parseEmacsOutput( reader ); + else + parseStandardOutput( reader ); + } + + private void setError( boolean err ) + { + error = err; + if( error ) + errorFlag = true; + } + + private void log( String line ) + { + if( !emacsMode ) + { + task.log( "", ( error ? Project.MSG_ERR : Project.MSG_WARN ) ); + } + task.log( line, ( error ? Project.MSG_ERR : Project.MSG_WARN ) ); + } + + private void parseEmacsOutput( BufferedReader reader ) + throws IOException + { + // This may change, if we add advanced parsing capabilities. + parseStandardOutput( reader ); + } + + private void parseStandardOutput( BufferedReader reader ) + throws IOException + { + String line; + String lower; + // We assume, that every output, jike does, stands for an error/warning + // XXX + // Is this correct? + + // TODO: + // A warning line, that shows code, which contains a variable + // error will cause some trouble. The parser should definitely + // be much better. + + while( ( line = reader.readLine() ) != null ) + { + lower = line.toLowerCase(); + if( line.trim().equals( "" ) ) + continue; + if( lower.indexOf( "error" ) != -1 ) + setError( true ); + else if( lower.indexOf( "warning" ) != -1 ) + setError( false ); + else + { + // If we don't know the type of the line + // and we are in emacs mode, it will be + // an error, because in this mode, jikes won't + // always print "error", but sometimes other + // keywords like "Syntax". We should look for + // all those keywords. + if( emacsMode ) + setError( true ); + } + log( line ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/KeySubst.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/KeySubst.java new file mode 100644 index 000000000..22fbf44f3 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/KeySubst.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Hashtable; +import java.util.StringTokenizer; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Keyword substitution. Input file is written to output file. Do not make input + * file same as output file. Keywords in input files look like this: + * + * @author Jon S. Stevens jon@clearink.com + * @foo@. See the docs for the setKeys method to understand how to do the + * substitutions. + * @deprecated KeySubst is deprecated. Use Filter + CopyDir instead. + */ +public class KeySubst extends Task +{ + private File source = null; + private File dest = null; + private String sep = "*"; + private Hashtable replacements = new Hashtable(); + + + public static void main( String[] args ) + { + try + { + Hashtable hash = new Hashtable(); + hash.put( "VERSION", "1.0.3" ); + hash.put( "b", "ffff" ); + System.out.println( KeySubst.replace( "$f ${VERSION} f ${b} jj $", hash ) ); + } + catch( Exception e ) + { + e.printStackTrace(); + } + } + + /** + * Does replacement on text using the hashtable of keys. + * + * @param origString Description of Parameter + * @param keys Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @returns the string with the replacements in it. + */ + public static String replace( String origString, Hashtable keys ) + throws BuildException + { + StringBuffer finalString = new StringBuffer(); + int index = 0; + int i = 0; + String key = null; + while( ( index = origString.indexOf( "${", i ) ) > -1 ) + { + key = origString.substring( index + 2, origString.indexOf( "}", index + 3 ) ); + finalString.append( origString.substring( i, index ) ); + if( keys.containsKey( key ) ) + { + finalString.append( keys.get( key ) ); + } + else + { + finalString.append( "${" ); + finalString.append( key ); + finalString.append( "}" ); + } + i = index + 3 + key.length(); + } + finalString.append( origString.substring( i ) ); + return finalString.toString(); + } + + /** + * Set the destination file. + * + * @param dest The new Dest value + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Format string is like this:

      + * + * name=value*name2=value

      + * + * Names are case sensitive.

      + * + * Use the setSep() method to change the * to something else if you need to + * use * as a name or value. + * + * @param keys The new Keys value + */ + public void setKeys( String keys ) + { + if( keys != null && keys.length() > 0 ) + { + StringTokenizer tok = + new StringTokenizer( keys, this.sep, false ); + while( tok.hasMoreTokens() ) + { + String token = tok.nextToken().trim(); + StringTokenizer itok = + new StringTokenizer( token, "=", false ); + + String name = itok.nextToken(); + String value = itok.nextToken(); +// log ( "Name: " + name ); +// log ( "Value: " + value ); + replacements.put( name, value ); + } + } + } + + /** + * Sets the seperator between name=value arguments in setKeys(). By default + * it is "*". + * + * @param sep The new Sep value + */ + public void setSep( String sep ) + { + this.sep = sep; + } + + /** + * Set the source file. + * + * @param s The new Src value + */ + public void setSrc( File s ) + { + this.source = s; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + log( "!! KeySubst is deprecated. Use Filter + CopyDir instead. !!" ); + log( "Performing Substitions" ); + if( source == null || dest == null ) + { + log( "Source and destinations must not be null" ); + return; + } + BufferedReader br = null; + BufferedWriter bw = null; + try + { + br = new BufferedReader( new FileReader( source ) ); + dest.delete(); + bw = new BufferedWriter( new FileWriter( dest ) ); + + String line = null; + String newline = null; + int length; + line = br.readLine(); + while( line != null ) + { + if( line.length() == 0 ) + { + bw.newLine(); + } + else + { + newline = KeySubst.replace( line, replacements ); + bw.write( newline ); + bw.newLine(); + } + line = br.readLine(); + } + bw.flush(); + bw.close(); + br.close(); + } + catch( IOException ioe ) + { + ioe.printStackTrace(); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogOutputStream.java new file mode 100644 index 000000000..f715beaaa --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogOutputStream.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + + +/** + * Logs each line written to this stream to the log system of ant. Tries to be + * smart about line separators.
      + * TODO: This class can be split to implement other line based processing of + * data written to the stream. + * + * @author thomas.haas@softwired-inc.com + */ +public class LogOutputStream extends OutputStream +{ + + private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private boolean skip = false; + private int level = Project.MSG_INFO; + + private Task task; + + /** + * Creates a new instance of this class. + * + * @param task the task for whom to log + * @param level loglevel used to log data written to this stream. + */ + public LogOutputStream( Task task, int level ) + { + this.task = task; + this.level = level; + } + + public int getMessageLevel() + { + return level; + } + + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + if( buffer.size() > 0 ) + processBuffer(); + super.close(); + } + + + /** + * Write the data to the buffer and flush the buffer, if a line separator is + * detected. + * + * @param cc data to log (byte). + * @exception IOException Description of Exception + */ + public void write( int cc ) + throws IOException + { + final byte c = ( byte )cc; + if( ( c == '\n' ) || ( c == '\r' ) ) + { + if( !skip ) + processBuffer(); + } + else + buffer.write( cc ); + skip = ( c == '\r' ); + } + + + /** + * Converts the buffer to a string and sends it to processLine + */ + protected void processBuffer() + { + processLine( buffer.toString() ); + buffer.reset(); + } + + /** + * Logs a line to the log system of ant. + * + * @param line the line to log. + */ + protected void processLine( String line ) + { + processLine( line, level ); + } + + /** + * Logs a line to the log system of ant. + * + * @param line the line to log. + * @param level Description of Parameter + */ + protected void processLine( String line, int level ) + { + task.log( line, level ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogStreamHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogStreamHandler.java new file mode 100644 index 000000000..b7e6c849b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogStreamHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Logs standard output and error of a subprocess to the log system of ant. + * + * @author thomas.haas@softwired-inc.com + */ +public class LogStreamHandler extends PumpStreamHandler +{ + + /** + * Creates a new instance of this class. + * + * @param task the task for whom to log + * @param outlevel the loglevel used to log standard output + * @param errlevel the loglevel used to log standard error + */ + public LogStreamHandler( Task task, int outlevel, int errlevel ) + { + super( new LogOutputStream( task, outlevel ), + new LogOutputStream( task, errlevel ) ); + } + + public void stop() + { + super.stop(); + try + { + getErr().close(); + getOut().close(); + } + catch( IOException e ) + { + // plain impossible + throw new BuildException( e ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Manifest.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Manifest.java new file mode 100644 index 000000000..242b0fa5e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Manifest.java @@ -0,0 +1,950 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Class to manage Manifest information + * + * @author Conor MacNeill + * @author Stefan Bodewig + */ +public class Manifest extends Task +{ + /** + * The standard manifest version header + */ + public final static String ATTRIBUTE_MANIFEST_VERSION = "Manifest-Version"; + + /** + * The standard Signature Version header + */ + public final static String ATTRIBUTE_SIGNATURE_VERSION = "Signature-Version"; + + /** + * The Name Attribute is the first in a named section + */ + public final static String ATTRIBUTE_NAME = "Name"; + + /** + * The From Header is disallowed in a Manifest + */ + public final static String ATTRIBUTE_FROM = "From"; + + /** + * The Class-Path Header is special - it can be duplicated + */ + public final static String ATTRIBUTE_CLASSPATH = "class-path"; + + /** + * Default Manifest version if one is not specified + */ + public final static String DEFAULT_MANIFEST_VERSION = "1.0"; + + /** + * The max length of a line in a Manifest + */ + public final static int MAX_LINE_LENGTH = 70; + + /** + * The version of this manifest + */ + private String manifestVersion = DEFAULT_MANIFEST_VERSION; + + /** + * The main section of this manifest + */ + private Section mainSection = new Section(); + + /** + * The named sections of this manifest + */ + private Hashtable sections = new Hashtable(); + + private File manifestFile; + + private Mode mode; + + /** + * Construct an empty manifest + */ + public Manifest() + { + mode = new Mode(); + mode.setValue( "replace" ); + manifestVersion = null; + } + + /** + * Read a manifest file from the given reader + * + * @param r Description of Parameter + * @exception ManifestException Description of Exception + * @exception IOException Description of Exception + * @throws ManifestException if the manifest is not valid according to the + * JAR spec + * @throws IOException if the manifest cannot be read from the reader. + */ + public Manifest( Reader r ) + throws ManifestException, IOException + { + BufferedReader reader = new BufferedReader( r ); + // This should be the manifest version + String nextSectionName = mainSection.read( reader ); + String readManifestVersion = mainSection.getAttributeValue( ATTRIBUTE_MANIFEST_VERSION ); + if( readManifestVersion != null ) + { + manifestVersion = readManifestVersion; + mainSection.removeAttribute( ATTRIBUTE_MANIFEST_VERSION ); + } + + String line = null; + while( ( line = reader.readLine() ) != null ) + { + if( line.length() == 0 ) + { + continue; + } + + Section section = new Section(); + if( nextSectionName == null ) + { + Attribute sectionName = new Attribute( line ); + if( !sectionName.getName().equalsIgnoreCase( ATTRIBUTE_NAME ) ) + { + throw new ManifestException( "Manifest sections should start with a \"" + ATTRIBUTE_NAME + + "\" attribute and not \"" + sectionName.getName() + "\"" ); + } + nextSectionName = sectionName.getValue(); + } + else + { + // we have already started reading this section + // this line is the first attribute. set it and then let the normal + // read handle the rest + Attribute firstAttribute = new Attribute( line ); + section.addAttributeAndCheck( firstAttribute ); + } + + section.setName( nextSectionName ); + nextSectionName = section.read( reader ); + addConfiguredSection( section ); + } + } + + /** + * Construct a manifest from Ant's default manifest file. + * + * @return The DefaultManifest value + * @exception BuildException Description of Exception + */ + public static Manifest getDefaultManifest() + throws BuildException + { + try + { + String s = "/org/apache/tools/ant/defaultManifest.mf"; + InputStream in = Manifest.class.getResourceAsStream( s ); + if( in == null ) + { + throw new BuildException( "Could not find default manifest: " + s ); + } + try + { + return new Manifest( new InputStreamReader( in, "ASCII" ) ); + } + catch( UnsupportedEncodingException e ) + { + return new Manifest( new InputStreamReader( in ) ); + } + } + catch( ManifestException e ) + { + throw new BuildException( "Default manifest is invalid !!" ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read default manifest", e ); + } + } + + /** + * The name of the manifest file to write (if used as a task). + * + * @param f The new File value + */ + public void setFile( File f ) + { + manifestFile = f; + } + + /** + * Shall we update or replace an existing manifest? + * + * @param m The new Mode value + */ + public void setMode( Mode m ) + { + mode = m; + } + + /** + * Get the warnings for this manifest. + * + * @return an enumeration of warning strings + */ + public Enumeration getWarnings() + { + Vector warnings = new Vector(); + + for( Enumeration e2 = mainSection.getWarnings(); e2.hasMoreElements(); ) + { + warnings.addElement( e2.nextElement() ); + } + + // create a vector and add in the warnings for all the sections + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + for( Enumeration e2 = section.getWarnings(); e2.hasMoreElements(); ) + { + warnings.addElement( e2.nextElement() ); + } + } + + return warnings.elements(); + } + + public void addConfiguredAttribute( Attribute attribute ) + throws ManifestException + { + mainSection.addConfiguredAttribute( attribute ); + } + + public void addConfiguredSection( Section section ) + throws ManifestException + { + if( section.getName() == null ) + { + throw new BuildException( "Sections must have a name" ); + } + sections.put( section.getName().toLowerCase(), section ); + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Manifest ) ) + { + return false; + } + + Manifest rhsManifest = ( Manifest )rhs; + if( manifestVersion == null ) + { + if( rhsManifest.manifestVersion != null ) + { + return false; + } + } + else if( !manifestVersion.equals( rhsManifest.manifestVersion ) ) + { + return false; + } + if( sections.size() != rhsManifest.sections.size() ) + { + return false; + } + + if( !mainSection.equals( rhsManifest.mainSection ) ) + { + return false; + } + + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + Section rhsSection = ( Section )rhsManifest.sections.get( section.getName().toLowerCase() ); + if( !section.equals( rhsSection ) ) + { + return false; + } + } + + return true; + } + + /** + * Create or update the Manifest when used as a task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( manifestFile == null ) + { + throw new BuildException( "the file attribute is required" ); + } + + Manifest toWrite = getDefaultManifest(); + + if( mode.getValue().equals( "update" ) && manifestFile.exists() ) + { + FileReader f = null; + try + { + f = new FileReader( manifestFile ); + toWrite.merge( new Manifest( f ) ); + } + catch( ManifestException m ) + { + throw new BuildException( "Existing manifest " + manifestFile + + " is invalid", m, location ); + } + catch( IOException e ) + { + throw new BuildException( "Failed to read " + manifestFile, + e, location ); + } + finally + { + if( f != null ) + { + try + { + f.close(); + } + catch( IOException e ) + {} + } + } + } + + try + { + toWrite.merge( this ); + } + catch( ManifestException m ) + { + throw new BuildException( "Manifest is invalid", m, location ); + } + + PrintWriter w = null; + try + { + w = new PrintWriter( new FileWriter( manifestFile ) ); + toWrite.write( w ); + } + catch( IOException e ) + { + throw new BuildException( "Failed to write " + manifestFile, + e, location ); + } + finally + { + if( w != null ) + { + w.close(); + } + } + } + + /** + * Merge the contents of the given manifest into this manifest + * + * @param other the Manifest to be merged with this one. + * @throws ManifestException if there is a problem merging the manfest + * according to the Manifest spec. + */ + public void merge( Manifest other ) + throws ManifestException + { + if( other.manifestVersion != null ) + { + manifestVersion = other.manifestVersion; + } + mainSection.merge( other.mainSection ); + for( Enumeration e = other.sections.keys(); e.hasMoreElements(); ) + { + String sectionName = ( String )e.nextElement(); + Section ourSection = ( Section )sections.get( sectionName ); + Section otherSection = ( Section )other.sections.get( sectionName ); + if( ourSection == null ) + { + sections.put( sectionName.toLowerCase(), otherSection ); + } + else + { + ourSection.merge( otherSection ); + } + } + + } + + /** + * Convert the manifest to its string representation + * + * @return a multiline string with the Manifest as it appears in a Manifest + * file. + */ + public String toString() + { + StringWriter sw = new StringWriter(); + try + { + write( new PrintWriter( sw ) ); + } + catch( IOException e ) + { + return null; + } + return sw.toString(); + } + + /** + * Write the manifest out to a print writer. + * + * @param writer the Writer to which the manifest is written + * @throws IOException if the manifest cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + writer.println( ATTRIBUTE_MANIFEST_VERSION + ": " + manifestVersion ); + String signatureVersion = mainSection.getAttributeValue( ATTRIBUTE_SIGNATURE_VERSION ); + if( signatureVersion != null ) + { + writer.println( ATTRIBUTE_SIGNATURE_VERSION + ": " + signatureVersion ); + mainSection.removeAttribute( ATTRIBUTE_SIGNATURE_VERSION ); + } + mainSection.write( writer ); + if( signatureVersion != null ) + { + try + { + mainSection.addConfiguredAttribute( new Attribute( ATTRIBUTE_SIGNATURE_VERSION, signatureVersion ) ); + } + catch( ManifestException e ) + { + // shouldn't happen - ignore + } + } + + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + section.write( writer ); + } + } + + /** + * Class to hold manifest attributes + * + * @author RT + */ + public static class Attribute + { + /** + * The attribute's name + */ + private String name = null; + + /** + * The attribute's value + */ + private String value = null; + + /** + * Construct an empty attribute + */ + public Attribute() { } + + /** + * Construct an attribute by parsing a line from the Manifest + * + * @param line the line containing the attribute name and value + * @exception ManifestException Description of Exception + * @throws ManifestException if the line is not valid + */ + public Attribute( String line ) + throws ManifestException + { + parse( line ); + } + + /** + * Construct a manifest by specifying its name and value + * + * @param name the attribute's name + * @param value the Attribute's value + */ + public Attribute( String name, String value ) + { + this.name = name; + this.value = value; + } + + /** + * Set the Attribute's name + * + * @param name the attribute's name + */ + public void setName( String name ) + { + this.name = name; + } + + /** + * Set the Attribute's value + * + * @param value the attribute's value + */ + public void setValue( String value ) + { + this.value = value; + } + + /** + * Get the Attribute's name + * + * @return the attribute's name. + */ + public String getName() + { + return name; + } + + /** + * Get the Attribute's value + * + * @return the attribute's value. + */ + public String getValue() + { + return value; + } + + /** + * Add a continuation line from the Manifest file When lines are too + * long in a manifest, they are continued on the next line by starting + * with a space. This method adds the continuation data to the attribute + * value by skipping the first character. + * + * @param line The feature to be added to the Continuation attribute + */ + public void addContinuation( String line ) + { + value += line.substring( 1 ); + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Attribute ) ) + { + return false; + } + + Attribute rhsAttribute = ( Attribute )rhs; + return ( name != null && rhsAttribute.name != null && + name.toLowerCase().equals( rhsAttribute.name.toLowerCase() ) && + value != null && value.equals( rhsAttribute.value ) ); + } + + /** + * Parse a line into name and value pairs + * + * @param line the line to be parsed + * @throws ManifestException if the line does not contain a colon + * separating the name and value + */ + public void parse( String line ) + throws ManifestException + { + int index = line.indexOf( ": " ); + if( index == -1 ) + { + throw new ManifestException( "Manifest line \"" + line + "\" is not valid as it does not " + + "contain a name and a value separated by ': ' " ); + } + name = line.substring( 0, index ); + value = line.substring( index + 2 ); + } + + /** + * Write the attribute out to a print writer. + * + * @param writer the Writer to which the attribute is written + * @throws IOException if the attribte value cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + String line = name + ": " + value; + while( line.getBytes().length > MAX_LINE_LENGTH ) + { + // try to find a MAX_LINE_LENGTH byte section + int breakIndex = MAX_LINE_LENGTH; + String section = line.substring( 0, breakIndex ); + while( section.getBytes().length > MAX_LINE_LENGTH && breakIndex > 0 ) + { + breakIndex--; + section = line.substring( 0, breakIndex ); + } + if( breakIndex == 0 ) + { + throw new IOException( "Unable to write manifest line " + name + ": " + value ); + } + writer.println( section ); + line = " " + line.substring( breakIndex ); + } + writer.println( line ); + } + } + + /** + * Helper class for Manifest's mode attribute. + * + * @author RT + */ + public static class Mode extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"update", "replace"}; + } + } + + /** + * Class to represent an individual section in the Manifest. A section + * consists of a set of attribute values, separated from other sections by a + * blank line. + * + * @author RT + */ + public static class Section + { + private Vector warnings = new Vector(); + + /** + * The section's name if any. The main section in a manifest is unnamed. + */ + private String name = null; + + /** + * The section's attributes. + */ + private Hashtable attributes = new Hashtable(); + + /** + * Set the Section's name + * + * @param name the section's name + */ + public void setName( String name ) + { + this.name = name; + } + + /** + * Get the value of the attribute with the name given. + * + * @param attributeName the name of the attribute to be returned. + * @return the attribute's value or null if the attribute does not exist + * in the section + */ + public String getAttributeValue( String attributeName ) + { + Object attribute = attributes.get( attributeName.toLowerCase() ); + if( attribute == null ) + { + return null; + } + if( attribute instanceof Attribute ) + { + return ( ( Attribute )attribute ).getValue(); + } + else + { + String value = ""; + for( Enumeration e = ( ( Vector )attribute ).elements(); e.hasMoreElements(); ) + { + Attribute classpathAttribute = ( Attribute )e.nextElement(); + value += classpathAttribute.getValue() + " "; + } + return value.trim(); + } + } + + /** + * Get the Section's name + * + * @return the section's name. + */ + public String getName() + { + return name; + } + + public Enumeration getWarnings() + { + return warnings.elements(); + } + + /** + * Add an attribute to the section + * + * @param attribute the attribute to be added. + * @return the value of the attribute if it is a name attribute - null + * other wise + * @throws ManifestException if the attribute already exists in this + * section. + */ + public String addAttributeAndCheck( Attribute attribute ) + throws ManifestException + { + if( attribute.getName() == null || attribute.getValue() == null ) + { + throw new BuildException( "Attributes must have name and value" ); + } + if( attribute.getName().equalsIgnoreCase( ATTRIBUTE_NAME ) ) + { + warnings.addElement( "\"" + ATTRIBUTE_NAME + "\" attributes should not occur in the " + + "main section and must be the first element in all " + + "other sections: \"" + attribute.getName() + ": " + attribute.getValue() + "\"" ); + return attribute.getValue(); + } + + if( attribute.getName().toLowerCase().startsWith( ATTRIBUTE_FROM.toLowerCase() ) ) + { + warnings.addElement( "Manifest attributes should not start with \"" + + ATTRIBUTE_FROM + "\" in \"" + attribute.getName() + ": " + attribute.getValue() + "\"" ); + } + else + { + // classpath attributes go into a vector + String attributeName = attribute.getName().toLowerCase(); + if( attributeName.equals( ATTRIBUTE_CLASSPATH ) ) + { + Vector classpathAttrs = ( Vector )attributes.get( attributeName ); + if( classpathAttrs == null ) + { + classpathAttrs = new Vector(); + attributes.put( attributeName, classpathAttrs ); + } + classpathAttrs.addElement( attribute ); + } + else if( attributes.containsKey( attributeName ) ) + { + throw new ManifestException( "The attribute \"" + attribute.getName() + "\" may not " + + "occur more than once in the same section" ); + } + else + { + attributes.put( attributeName, attribute ); + } + } + return null; + } + + public void addConfiguredAttribute( Attribute attribute ) + throws ManifestException + { + String check = addAttributeAndCheck( attribute ); + if( check != null ) + { + throw new BuildException( "Specify the section name using the \"name\" attribute of the

      element rather " + + "than using a \"Name\" manifest attribute" ); + } + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Section ) ) + { + return false; + } + + Section rhsSection = ( Section )rhs; + if( attributes.size() != rhsSection.attributes.size() ) + { + return false; + } + + for( Enumeration e = attributes.elements(); e.hasMoreElements(); ) + { + Attribute attribute = ( Attribute )e.nextElement(); + Attribute rshAttribute = ( Attribute )rhsSection.attributes.get( attribute.getName().toLowerCase() ); + if( !attribute.equals( rshAttribute ) ) + { + return false; + } + } + + return true; + } + + /** + * Merge in another section + * + * @param section the section to be merged with this one. + * @throws ManifestException if the sections cannot be merged. + */ + public void merge( Section section ) + throws ManifestException + { + if( name == null && section.getName() != null || + name != null && !( name.equalsIgnoreCase( section.getName() ) ) ) + { + throw new ManifestException( "Unable to merge sections with different names" ); + } + + for( Enumeration e = section.attributes.keys(); e.hasMoreElements(); ) + { + String attributeName = ( String )e.nextElement(); + if( attributeName.equals( ATTRIBUTE_CLASSPATH ) && + attributes.containsKey( attributeName ) ) + { + // classpath entries are vetors which are merged + Vector classpathAttrs = ( Vector )section.attributes.get( attributeName ); + Vector ourClasspathAttrs = ( Vector )attributes.get( attributeName ); + for( Enumeration e2 = classpathAttrs.elements(); e2.hasMoreElements(); ) + { + ourClasspathAttrs.addElement( e2.nextElement() ); + } + } + else + { + // the merge file always wins + attributes.put( attributeName, section.attributes.get( attributeName ) ); + } + } + + // add in the warnings + for( Enumeration e = section.warnings.elements(); e.hasMoreElements(); ) + { + warnings.addElement( e.nextElement() ); + } + } + + /** + * Read a section through a reader + * + * @param reader the reader from which the section is read + * @return the name of the next section if it has been read as part of + * this section - This only happens if the Manifest is malformed. + * @throws ManifestException if the section is not valid according to + * the JAR spec + * @throws IOException if the section cannot be read from the reader. + */ + public String read( BufferedReader reader ) + throws ManifestException, IOException + { + Attribute attribute = null; + while( true ) + { + String line = reader.readLine(); + if( line == null || line.length() == 0 ) + { + return null; + } + if( line.charAt( 0 ) == ' ' ) + { + // continuation line + if( attribute == null ) + { + if( name != null ) + { + // a continuation on the first line is a continuation of the name - concatenate + // this line and the name + name += line.substring( 1 ); + } + else + { + throw new ManifestException( "Can't start an attribute with a continuation line " + line ); + } + } + else + { + attribute.addContinuation( line ); + } + } + else + { + attribute = new Attribute( line ); + String nameReadAhead = addAttributeAndCheck( attribute ); + if( nameReadAhead != null ) + { + return nameReadAhead; + } + } + } + } + + /** + * Remove tge given attribute from the section + * + * @param attributeName the name of the attribute to be removed. + */ + public void removeAttribute( String attributeName ) + { + attributes.remove( attributeName.toLowerCase() ); + } + + /** + * Write the section out to a print writer. + * + * @param writer the Writer to which the section is written + * @throws IOException if the section cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + if( name != null ) + { + Attribute nameAttr = new Attribute( ATTRIBUTE_NAME, name ); + nameAttr.write( writer ); + } + for( Enumeration e = attributes.elements(); e.hasMoreElements(); ) + { + Object object = e.nextElement(); + if( object instanceof Attribute ) + { + Attribute attribute = ( Attribute )object; + attribute.write( writer ); + } + else + { + Vector attrList = ( Vector )object; + for( Enumeration e2 = attrList.elements(); e2.hasMoreElements(); ) + { + Attribute attribute = ( Attribute )e2.nextElement(); + attribute.write( writer ); + } + } + } + writer.println(); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ManifestException.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ManifestException.java new file mode 100644 index 000000000..813da555e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ManifestException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + + + +/** + * Exception thrown indicating problems in a JAR Manifest + * + * @author Conor MacNeill + */ +public class ManifestException extends Exception +{ + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of or information about the exception. + */ + public ManifestException( String msg ) + { + super( msg ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/MatchingTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/MatchingTask.java new file mode 100644 index 000000000..a03cb9be7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/MatchingTask.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + +/** + * This is an abstract task that should be used by all those tasks that require + * to include or exclude files based on pattern matching. + * + * @author Arnout J. Kuiper ajkuiper@wxs.nl + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Sam Ruby rubys@us.ibm.com + * @author Jon S. Stevens jon@clearink.com + * @author Stefan Bodewig + */ + +public abstract class MatchingTask extends Task +{ + + protected boolean useDefaultExcludes = true; + protected FileSet fileset = new FileSet(); + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + fileset.setExcludes( excludes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param excludesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setExcludesfile( File excludesfile ) + { + fileset.setExcludesfile( excludesfile ); + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + fileset.setIncludes( includes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param includesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setIncludesfile( File includesfile ) + { + fileset.setIncludesfile( includesfile ); + } + + /** + * List of filenames and directory names to not include. They should be + * either , or " " (space) separated. The ignored files will be logged. + * + * @param ignoreString the string containing the files to ignore. + */ + public void XsetIgnore( String ignoreString ) + { + log( "The ignore attribute is deprecated." + + "Please use the excludes attribute.", + Project.MSG_WARN ); + if( ignoreString != null && ignoreString.length() > 0 ) + { + Vector tmpExcludes = new Vector(); + StringTokenizer tok = new StringTokenizer( ignoreString, ", ", false ); + while( tok.hasMoreTokens() ) + { + createExclude().setName( "**/" + tok.nextToken().trim() + "/**" ); + } + } + } + + /** + * Set this to be the items in the base directory that you want to be + * included. You can also specify "*" for the items (ie: items="*") and it + * will include all the items in the base directory. + * + * @param itemString the string containing the files to include. + */ + public void XsetItems( String itemString ) + { + log( "The items attribute is deprecated. " + + "Please use the includes attribute.", + Project.MSG_WARN ); + if( itemString == null || itemString.equals( "*" ) + || itemString.equals( "." ) ) + { + createInclude().setName( "**" ); + } + else + { + StringTokenizer tok = new StringTokenizer( itemString, ", " ); + while( tok.hasMoreTokens() ) + { + String pattern = tok.nextToken().trim(); + if( pattern.length() > 0 ) + { + createInclude().setName( pattern + "/**" ); + } + } + } + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + return fileset.createExclude(); + } + + /** + * add a name entry on the include files list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExcludesFile() + { + return fileset.createExcludesFile(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + return fileset.createInclude(); + } + + /** + * add a name entry on the include files list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createIncludesFile() + { + return fileset.createIncludesFile(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + return fileset.createPatternSet(); + } + + /** + * Returns the directory scanner needed to access the files to process. + * + * @param baseDir Description of Parameter + * @return The DirectoryScanner value + */ + protected DirectoryScanner getDirectoryScanner( File baseDir ) + { + fileset.setDir( baseDir ); + fileset.setDefaultexcludes( useDefaultExcludes ); + return fileset.getDirectoryScanner( project ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Mkdir.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Mkdir.java new file mode 100644 index 000000000..c323430a4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Mkdir.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Creates a given directory. + * + * @author duncan@x180.com + */ + +public class Mkdir extends Task +{ + + private File dir; + + public void setDir( File dir ) + { + this.dir = dir; + } + + public void execute() + throws BuildException + { + if( dir == null ) + { + throw new BuildException( "dir attribute is required", location ); + } + + if( dir.isFile() ) + { + throw new BuildException( "Unable to create directory as a file already exists with that name: " + dir.getAbsolutePath() ); + } + + if( !dir.exists() ) + { + boolean result = dir.mkdirs(); + if( result == false ) + { + String msg = "Directory " + dir.getAbsolutePath() + " creation was not " + + "successful for an unknown reason"; + throw new BuildException( msg, location ); + } + log( "Created dir: " + dir.getAbsolutePath() ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Move.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Move.java new file mode 100644 index 000000000..75811008d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Move.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; + +/** + * Moves a file or directory to a new file or directory. By default, the + * destination is overwriten when existing. When overwrite is turned off, then + * files are only moved if the source file is newer than the destination file, + * or when the destination file does not exist.

      + * + * Source files and directories are only deleted when the file or directory has + * been copied to the destination successfully. Filtering also works.

      + * + * This implementation is based on Arnout Kuiper's initial design document, the + * following mailing list discussions, and the copyfile/copydir tasks.

      + * + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Magesh Umasankar + */ +public class Move extends Copy +{ + + public Move() + { + super(); + forceOverwrite = true; + } + + /** + * Go and delete the directory tree. + * + * @param d Description of Parameter + */ + protected void deleteDir( File d ) + { + String[] list = d.list(); + if( list == null ) + return;// on an io error list() can return null + + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + deleteDir( f ); + } + else + { + throw new BuildException( "UNEXPECTED ERROR - The file " + f.getAbsolutePath() + " should not exist!" ); + } + } + log( "Deleting directory " + d.getAbsolutePath(), verbosity ); + if( !d.delete() ) + { + throw new BuildException( "Unable to delete directory " + d.getAbsolutePath() ); + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + protected void doFileOperations() + { + //Attempt complete directory renames, if any, first. + if( completeDirMap.size() > 0 ) + { + Enumeration e = completeDirMap.keys(); + while( e.hasMoreElements() ) + { + File fromDir = ( File )e.nextElement(); + File toDir = ( File )completeDirMap.get( fromDir ); + try + { + log( "Attempting to rename dir: " + fromDir + + " to " + toDir, verbosity ); + renameFile( fromDir, toDir, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to rename dir " + fromDir + + " to " + toDir + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + if( fileCopyMap.size() > 0 ) + {// files to move + log( "Moving " + fileCopyMap.size() + " files to " + + destDir.getAbsolutePath() ); + + Enumeration e = fileCopyMap.keys(); + while( e.hasMoreElements() ) + { + String fromFile = ( String )e.nextElement(); + String toFile = ( String )fileCopyMap.get( fromFile ); + + if( fromFile.equals( toFile ) ) + { + log( "Skipping self-move of " + fromFile, verbosity ); + continue; + } + + boolean moved = false; + File f = new File( fromFile ); + + if( f.exists() ) + {//Is this file still available to be moved? + File d = new File( toFile ); + + try + { + log( "Attempting to rename: " + fromFile + + " to " + toFile, verbosity ); + moved = renameFile( f, d, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to rename " + fromFile + + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + + if( !moved ) + { + try + { + log( "Moving " + fromFile + " to " + toFile, verbosity ); + + FilterSetCollection executionFilters = new FilterSetCollection(); + if( filtering ) + { + executionFilters.addFilterSet( project.getGlobalFilterSet() ); + } + for( Enumeration filterEnum = getFilterSets().elements(); filterEnum.hasMoreElements(); ) + { + executionFilters.addFilterSet( ( FilterSet )filterEnum.nextElement() ); + } + getFileUtils().copyFile( f, d, executionFilters, + forceOverwrite ); + + f = new File( fromFile ); + if( !f.delete() ) + { + throw new BuildException( "Unable to delete file " + + f.getAbsolutePath() ); + } + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + } + } + + if( includeEmpty ) + { + Enumeration e = dirCopyMap.elements(); + int count = 0; + while( e.hasMoreElements() ) + { + File d = new File( ( String )e.nextElement() ); + if( !d.exists() ) + { + if( !d.mkdirs() ) + { + log( "Unable to create directory " + d.getAbsolutePath(), Project.MSG_ERR ); + } + else + { + count++; + } + } + } + + if( count > 0 ) + { + log( "Moved " + count + " empty directories to " + destDir.getAbsolutePath() ); + } + } + + if( filesets.size() > 0 ) + { + Enumeration e = filesets.elements(); + while( e.hasMoreElements() ) + { + FileSet fs = ( FileSet )e.nextElement(); + File dir = fs.getDir( project ); + + if( okToDelete( dir ) ) + { + deleteDir( dir ); + } + } + } + } + + /** + * Its only ok to delete a directory tree if there are no files in it. + * + * @param d Description of Parameter + * @return Description of the Returned Value + */ + protected boolean okToDelete( File d ) + { + String[] list = d.list(); + if( list == null ) + return false;// maybe io error? + + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + if( !okToDelete( f ) ) + return false; + } + else + { + return false;// found a file + } + } + + return true; + } + + /** + * Attempts to rename a file from a source to a destination. If overwrite is + * set to true, this method overwrites existing file even if the destination + * file is newer. Otherwise, the source file is renamed only if the + * destination file is older than it. Method then checks if token filtering + * is used. If it is, this method returns false assuming it is the + * responsibility to the copyFile method. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @throws IOException + */ + protected boolean renameFile( File sourceFile, File destFile, + boolean filtering, boolean overwrite ) + throws IOException, BuildException + { + + boolean renamed = true; + if( !filtering ) + { + // ensure that parent dir of dest file exists! + // not using getParentFile method to stay 1.1 compat + String parentPath = destFile.getParent(); + if( parentPath != null ) + { + File parent = new File( parentPath ); + if( !parent.exists() ) + { + parent.mkdirs(); + } + } + + if( destFile.exists() ) + { + if( !destFile.delete() ) + { + throw new BuildException( "Unable to remove existing file " + + destFile ); + } + } + renamed = sourceFile.renameTo( destFile ); + } + else + { + renamed = false; + } + return renamed; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Pack.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Pack.java new file mode 100644 index 000000000..3a9d0701d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Pack.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Abstract Base class for pack tasks. + * + * @author Magesh Umasankar + */ + +public abstract class Pack extends Task +{ + protected File source; + + protected File zipFile; + + public void setSrc( File src ) + { + source = src; + } + + public void setZipfile( File zipFile ) + { + this.zipFile = zipFile; + } + + public void execute() + throws BuildException + { + validate(); + log( "Building: " + zipFile.getAbsolutePath() ); + pack(); + } + + protected abstract void pack(); + + protected void zipFile( File file, OutputStream zOut ) + throws IOException + { + FileInputStream fIn = new FileInputStream( file ); + try + { + zipFile( fIn, zOut ); + } + finally + { + fIn.close(); + } + } + + private void validate() + { + if( zipFile == null ) + { + throw new BuildException( "zipfile attribute is required", location ); + } + + if( source == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( source.isDirectory() ) + { + throw new BuildException( "Src attribute must not " + + "represent a directory!", location ); + } + } + + private void zipFile( InputStream in, OutputStream zOut ) + throws IOException + { + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + zOut.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Parallel.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Parallel.java new file mode 100644 index 000000000..1203369ac --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Parallel.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; + + +/** + * Implements a multi threaded task execution.

      + * + * + * + * @author Thomas Christen chr@active.ch + * @author Conor MacNeill + */ +public class Parallel extends Task + implements TaskContainer +{ + + /** + * Collection holding the nested tasks + */ + private Vector nestedTasks = new Vector(); + + + /** + * Add a nested task to execute parallel (asynchron).

      + * + * + * + * @param nestedTask Nested task to be executed in parallel + * @exception BuildException Description of Exception + */ + public void addTask( Task nestedTask ) + throws BuildException + { + nestedTasks.addElement( nestedTask ); + } + + /** + * Block execution until the specified time or for a specified amount of + * milliseconds and if defined, execute the wait status. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + TaskThread[] threads = new TaskThread[nestedTasks.size()]; + int threadNumber = 0; + for( Enumeration e = nestedTasks.elements(); e.hasMoreElements(); threadNumber++ ) + { + Task nestedTask = ( Task )e.nextElement(); + threads[threadNumber] = new TaskThread( threadNumber, nestedTask ); + } + + // now start all threads + for( int i = 0; i < threads.length; ++i ) + { + threads[i].start(); + } + + // now join to all the threads + for( int i = 0; i < threads.length; ++i ) + { + try + { + threads[i].join(); + } + catch( InterruptedException ie ) + { + // who would interrupt me at a time like this? + } + } + + // now did any of the threads throw an exception + StringBuffer exceptionMessage = new StringBuffer(); + String lSep = System.getProperty( "line.separator" ); + int numExceptions = 0; + Throwable firstException = null; + Location firstLocation = Location.UNKNOWN_LOCATION; + ; + for( int i = 0; i < threads.length; ++i ) + { + Throwable t = threads[i].getException(); + if( t != null ) + { + numExceptions++; + if( firstException == null ) + { + firstException = t; + } + if( t instanceof BuildException && + firstLocation == Location.UNKNOWN_LOCATION ) + { + firstLocation = ( ( BuildException )t ).getLocation(); + } + exceptionMessage.append( lSep ); + exceptionMessage.append( t.getMessage() ); + } + } + + if( numExceptions == 1 ) + { + if( firstException instanceof BuildException ) + { + throw ( BuildException )firstException; + } + else + { + throw new BuildException( firstException ); + } + } + else if( numExceptions > 1 ) + { + throw new BuildException( exceptionMessage.toString(), firstLocation ); + } + } + + class TaskThread extends Thread + { + private Throwable exception; + private Task task; + private int taskNumber; + + /** + * Construct a new TaskThread

      + * + * + * + * @param task the Task to be executed in a seperate thread + * @param taskNumber Description of Parameter + */ + TaskThread( int taskNumber, Task task ) + { + this.task = task; + this.taskNumber = taskNumber; + } + + public Throwable getException() + { + return exception; + } + + /** + * Executes the task within a thread and takes care about Exceptions + * raised within the task. + */ + public void run() + { + try + { + task.perform(); + } + catch( Throwable t ) + { + exception = t; + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Patch.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Patch.java new file mode 100644 index 000000000..8c7657b12 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Patch.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; + +/** + * Task as a layer on top of patch. Patch applies a diff file to an original. + * + * @author Stefan Bodewig + */ +public class Patch extends Task +{ + private boolean havePatchfile = false; + private Commandline cmd = new Commandline(); + + private File originalFile; + + /** + * Shall patch write backups. + * + * @param backups The new Backups value + */ + public void setBackups( boolean backups ) + { + if( backups ) + { + cmd.createArgument().setValue( "-b" ); + } + } + + /** + * Ignore whitespace differences. + * + * @param ignore The new Ignorewhitespace value + */ + public void setIgnorewhitespace( boolean ignore ) + { + if( ignore ) + { + cmd.createArgument().setValue( "-l" ); + } + } + + /** + * The file to patch. + * + * @param file The new Originalfile value + */ + public void setOriginalfile( File file ) + { + originalFile = file; + } + + /** + * The file containing the diff output. + * + * @param file The new Patchfile value + */ + public void setPatchfile( File file ) + { + if( !file.exists() ) + { + throw new BuildException( "patchfile " + file + " doesn\'t exist", + location ); + } + cmd.createArgument().setValue( "-i" ); + cmd.createArgument().setFile( file ); + havePatchfile = true; + } + + /** + * Work silently unless an error occurs. + * + * @param q The new Quiet value + */ + public void setQuiet( boolean q ) + { + if( q ) + { + cmd.createArgument().setValue( "-s" ); + } + } + + /** + * Assume patch was created with old and new files swapped. + * + * @param r The new Reverse value + */ + public void setReverse( boolean r ) + { + if( r ) + { + cmd.createArgument().setValue( "-R" ); + } + } + + /** + * Strip the smallest prefix containing num leading slashes from + * filenames.

      + * + * patch's -p option. + * + * @param num The new Strip value + * @exception BuildException Description of Exception + */ + public void setStrip( int num ) + throws BuildException + { + if( num < 0 ) + { + throw new BuildException( "strip has to be >= 0", location ); + } + cmd.createArgument().setValue( "-p" + num ); + } + + public void execute() + throws BuildException + { + if( !havePatchfile ) + { + throw new BuildException( "patchfile argument is required", + location ); + } + + Commandline toExecute = ( Commandline )cmd.clone(); + toExecute.setExecutable( "patch" ); + + if( originalFile != null ) + { + toExecute.createArgument().setFile( originalFile ); + } + + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), + null ); + exe.setCommandline( toExecute.getCommandline() ); + try + { + exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + +}// Patch diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PathConvert.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PathConvert.java new file mode 100644 index 000000000..e508b580a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PathConvert.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * This task converts path and classpath information to a specific target OS + * format. The resulting formatted path is placed into a specified property.

      + * + * LIMITATION: Currently this implementation groups all machines into one of two + * types: Unix or Windows. Unix is defined as NOT windows. + * + * @author Larry Streepy + * streepy@healthlanguage.com + */ +public class PathConvert extends Task +{ + + // Members + private Path path = null;// Path to be converted + private Reference refid = null;// Reference to path/fileset to convert + private String targetOS = null;// The target OS type + private boolean targetWindows = false;// Set when targetOS is set + private boolean onWindows = false;// Set if we're running on windows + private String property = null;// The property to receive the results + private Vector prefixMap = new Vector();// Path prefix map + private String pathSep = null;// User override on path sep char + private String dirSep = null; + + /** + * Override the default directory separator string for the target os + * + * @param sep The new DirSep value + */ + public void setDirSep( String sep ) + { + dirSep = sep; + } + + /** + * Override the default path separator string for the target os + * + * @param sep The new PathSep value + */ + public void setPathSep( String sep ) + { + pathSep = sep; + } + + /** + * Set the value of the proprty attribute - this is the property into which + * our converted path will be placed. + * + * @param p The new Property value + */ + public void setProperty( String p ) + { + property = p; + } + + /** + * Adds a reference to a PATH or FILESET defined elsewhere. + * + * @param r The new Refid value + */ + public void setRefid( Reference r ) + { + if( path != null ) + throw noChildrenAllowed(); + + refid = r; + } + + /** + * Set the value of the targetos attribute + * + * @param target The new Targetos value + */ + public void setTargetos( String target ) + { + + targetOS = target.toLowerCase(); + + if( !targetOS.equals( "windows" ) && !target.equals( "unix" ) && + !targetOS.equals( "netware" ) ) + { + throw new BuildException( "targetos must be one of 'unix', 'netware', or 'windows'" ); + } + + // Currently, we deal with only two path formats: Unix and Windows + // And Unix is everything that is not Windows + + // for NetWare, piggy-back on Windows, since in the validateSetup code, + // the same assumptions can be made as with windows - + // that ; is the path separator + + targetWindows = ( targetOS.equals( "windows" ) || targetOS.equals( "netware" ) ); + } + + /** + * Has the refid attribute of this element been set? + * + * @return The Reference value + */ + public boolean isReference() + { + return refid != null; + } + + /** + * Create a nested MAP element + * + * @return Description of the Returned Value + */ + public MapEntry createMap() + { + + MapEntry entry = new MapEntry(); + prefixMap.addElement( entry ); + return entry; + } + + /** + * Create a nested PATH element + * + * @return Description of the Returned Value + */ + public Path createPath() + { + + if( isReference() ) + throw noChildrenAllowed(); + + if( path == null ) + { + path = new Path( getProject() ); + } + return path.createPath(); + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // If we are a reference, the create a Path from the reference + if( isReference() ) + { + path = new Path( getProject() ).createPath(); + + Object obj = refid.getReferencedObject( getProject() ); + + if( obj instanceof Path ) + { + path.setRefid( refid ); + } + else if( obj instanceof FileSet ) + { + FileSet fs = ( FileSet )obj; + path.addFileset( fs ); + } + else + { + throw new BuildException( "'refid' does not refer to a path or fileset" ); + } + } + + validateSetup();// validate our setup + + // Currently, we deal with only two path formats: Unix and Windows + // And Unix is everything that is not Windows + // (with the exception for NetWare below) + + String osname = System.getProperty( "os.name" ).toLowerCase(); + + // for NetWare, piggy-back on Windows, since here and in the + // apply code, the same assumptions can be made as with windows - + // that \\ is an OK separator, and do comparisons case-insensitive. + onWindows = ( ( osname.indexOf( "windows" ) >= 0 ) || + ( osname.indexOf( "netware" ) >= 0 ) ); + + // Determine the from/to char mappings for dir sep + char fromDirSep = onWindows ? '\\' : '/'; + char toDirSep = dirSep.charAt( 0 ); + + StringBuffer rslt = new StringBuffer( 100 ); + + // Get the list of path components in canonical form + String[] elems = path.list(); + + for( int i = 0; i < elems.length; i++ ) + { + String elem = elems[i]; + + elem = mapElement( elem );// Apply the path prefix map + + // Now convert the path and file separator characters from the + // current os to the target os. + + elem = elem.replace( fromDirSep, toDirSep ); + + if( i != 0 ) + rslt.append( pathSep ); + rslt.append( elem ); + } + + // Place the result into the specified property + String value = rslt.toString(); + + log( "Set property " + property + " = " + value, Project.MSG_VERBOSE ); + + getProject().setNewProperty( property, value ); + } + + /** + * Apply the configured map to a path element. The map is used to convert + * between Windows drive letters and Unix paths. If no map is configured, + * then the input string is returned unchanged. + * + * @param elem The path element to apply the map to + * @return String Updated element + */ + private String mapElement( String elem ) + { + + int size = prefixMap.size(); + + if( size != 0 ) + { + + // Iterate over the map entries and apply each one. Stop when one of the + // entries actually changes the element + + for( int i = 0; i < size; i++ ) + { + MapEntry entry = ( MapEntry )prefixMap.elementAt( i ); + String newElem = entry.apply( elem ); + + // Note I'm using "!=" to see if we got a new object back from + // the apply method. + + if( newElem != elem ) + { + elem = newElem; + break;// We applied one, so we're done + } + } + } + + return elem; + } + + /** + * Creates an exception that indicates that this XML element must not have + * child elements if the refid attribute is set. + * + * @return Description of the Returned Value + */ + private BuildException noChildrenAllowed() + { + return new BuildException( "You must not specify nested PATH elements when using refid" ); + } + + /** + * Validate that all our parameters have been properly initialized. + * + * @throws BuildException if something is not setup properly + */ + private void validateSetup() + throws BuildException + { + + if( path == null ) + throw new BuildException( "You must specify a path to convert" ); + + if( property == null ) + throw new BuildException( "You must specify a property" ); + + // Must either have a target OS or both a dirSep and pathSep + + if( targetOS == null && pathSep == null && dirSep == null ) + throw new BuildException( "You must specify at least one of targetOS, dirSep, or pathSep" ); + + // Determine the separator strings. The dirsep and pathsep attributes + // override the targetOS settings. + String dsep = File.separator; + String psep = File.pathSeparator; + + if( targetOS != null ) + { + psep = targetWindows ? ";" : ":"; + dsep = targetWindows ? "\\" : "/"; + } + + if( pathSep != null ) + {// override with pathsep= + psep = pathSep; + } + + if( dirSep != null ) + {// override with dirsep= + dsep = dirSep; + } + + pathSep = psep; + dirSep = dsep; + } + + /** + * Helper class, holds the nested values. Elements will look like + * this: <map from="d:" to="/foo"/>

      + * + * When running on windows, the prefix comparison will be case insensitive. + * + * @author RT + */ + public class MapEntry + { + + // Members + private String from = null; + private String to = null; + + /** + * Set the "from" attribute of the map entry + * + * @param from The new From value + */ + public void setFrom( String from ) + { + this.from = from; + } + + /** + * Set the "to" attribute of the map entry + * + * @param to The new To value + */ + public void setTo( String to ) + { + this.to = to; + } + + /** + * Apply this map entry to a given path element + * + * @param elem Path element to process + * @return String Updated path element after mapping + */ + public String apply( String elem ) + { + if( from == null || to == null ) + { + throw new BuildException( "Both 'from' and 'to' must be set in a map entry" ); + } + + // If we're on windows, then do the comparison ignoring case + String cmpElem = onWindows ? elem.toLowerCase() : elem; + String cmpFrom = onWindows ? from.toLowerCase() : from; + + // If the element starts with the configured prefix, then convert the prefix + // to the configured 'to' value. + + if( cmpElem.startsWith( cmpFrom ) ) + { + int len = from.length(); + + if( len >= elem.length() ) + { + elem = to; + } + else + { + elem = to + elem.substring( len ); + } + } + + return elem; + } + }// User override on directory sep char +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ProcessDestroyer.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ProcessDestroyer.java new file mode 100644 index 000000000..590e86fd1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ProcessDestroyer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Vector; + +/** + * Destroys all registered Processes when the VM exits. + * + * @author Michael Newcomb + */ +class ProcessDestroyer + extends Thread +{ + + private Vector processes = new Vector(); + + /** + * Constructs a ProcessDestroyer and registers it as a shutdown + * hook. + */ + public ProcessDestroyer() + { + try + { + // check to see if the method exists (support pre-JDK 1.3 VMs) + // + Class[] paramTypes = {Thread.class}; + Method addShutdownHook = + Runtime.class.getMethod( "addShutdownHook", paramTypes ); + + // add the hook + // + Object[] args = {this}; + addShutdownHook.invoke( Runtime.getRuntime(), args ); + } + catch( Exception e ) + { + // it just won't be added as a shutdown hook... :( + } + } + + /** + * Returns true if the specified Process was + * successfully added to the list of processes to destroy upon VM exit. + * + * @param process the process to add + * @return true if the specified Process was + * successfully added + */ + public boolean add( Process process ) + { + processes.addElement( process ); + return processes.contains( process ); + } + + /** + * Returns true if the specified Process was + * successfully removed from the list of processes to destroy upon VM exit. + * + * @param process the process to remove + * @return true if the specified Process was + * successfully removed + */ + public boolean remove( Process process ) + { + return processes.removeElement( process ); + } + + /** + * Invoked by the VM when it is exiting. + */ + public void run() + { + synchronized( processes ) + { + Enumeration e = processes.elements(); + while( e.hasMoreElements() ) + { + ( ( Process )e.nextElement() ).destroy(); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Property.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Property.java new file mode 100644 index 000000000..15b182ecf --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Property.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Will set a Project property. Used to be a hack in ProjectHelper Will not + * override values set by the command line or parent projects. + * + * @author costin@dnt.ro + * @author Sam Ruby + * @author Glenn McAllister + */ +public class Property extends Task +{ + protected Path classpath; + protected String env; + protected File file; + + protected String name; + protected Reference ref; + protected String resource; + + protected boolean userProperty; + protected String value;// set read-only properties + + public Property() + { + super(); + } + + protected Property( boolean userProperty ) + { + this.userProperty = userProperty; + } + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setEnvironment( String env ) + { + this.env = env; + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setLocation( File location ) + { + setValue( location.getAbsolutePath() ); + } + + public void setName( String name ) + { + this.name = name; + } + + public void setRefid( Reference ref ) + { + this.ref = ref; + } + + public void setResource( String resource ) + { + this.resource = resource; + } + + /** + * @param userProperty The new UserProperty value + * @deprecated This was never a supported feature and has been deprecated + * without replacement + */ + public void setUserProperty( boolean userProperty ) + { + log( "DEPRECATED: Ignoring request to set user property in Property task.", + Project.MSG_WARN ); + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getEnvironment() + { + return env; + } + + public File getFile() + { + return file; + } + + public String getName() + { + return name; + } + + public Reference getRefid() + { + return ref; + } + + public String getResource() + { + return resource; + } + + public String getValue() + { + return value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public void execute() + throws BuildException + { + if( name != null ) + { + if( value == null && ref == null ) + { + throw new BuildException( "You must specify value, location or refid with the name attribute", + location ); + } + } + else + { + if( file == null && resource == null && env == null ) + { + throw new BuildException( "You must specify file, resource or environment when not using the name attribute", + location ); + } + } + + if( ( name != null ) && ( value != null ) ) + { + addProperty( name, value ); + } + + if( file != null ) + loadFile( file ); + + if( resource != null ) + loadResource( resource ); + + if( env != null ) + loadEnvironment( env ); + + if( ( name != null ) && ( ref != null ) ) + { + Object obj = ref.getReferencedObject( getProject() ); + if( obj != null ) + { + addProperty( name, obj.toString() ); + } + } + } + + public String toString() + { + return value == null ? "" : value; + } + + protected void addProperties( Properties props ) + { + resolveAllProperties( props ); + Enumeration e = props.keys(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + String value = ( String )props.getProperty( name ); + + String v = project.replaceProperties( value ); + addProperty( name, v ); + } + } + + protected void addProperty( String n, String v ) + { + if( userProperty ) + { + if( project.getUserProperty( n ) == null ) + { + project.setUserProperty( n, v ); + } + else + { + log( "Override ignored for " + n, Project.MSG_VERBOSE ); + } + } + else + { + project.setNewProperty( n, v ); + } + } + + protected void loadEnvironment( String prefix ) + { + Properties props = new Properties(); + if( !prefix.endsWith( "." ) ) + prefix += "."; + log( "Loading Environment " + prefix, Project.MSG_VERBOSE ); + Vector osEnv = Execute.getProcEnvironment(); + for( Enumeration e = osEnv.elements(); e.hasMoreElements(); ) + { + String entry = ( String )e.nextElement(); + int pos = entry.indexOf( '=' ); + if( pos == -1 ) + { + log( "Ignoring: " + entry, Project.MSG_WARN ); + } + else + { + props.put( prefix + entry.substring( 0, pos ), + entry.substring( pos + 1 ) ); + } + } + addProperties( props ); + } + + protected void loadFile( File file ) + throws BuildException + { + Properties props = new Properties(); + log( "Loading " + file.getAbsolutePath(), Project.MSG_VERBOSE ); + try + { + if( file.exists() ) + { + FileInputStream fis = new FileInputStream( file ); + try + { + props.load( fis ); + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + addProperties( props ); + } + else + { + log( "Unable to find property file: " + file.getAbsolutePath(), + Project.MSG_VERBOSE ); + } + } + catch( IOException ex ) + { + throw new BuildException( ex ); + } + } + + protected void loadResource( String name ) + { + Properties props = new Properties(); + log( "Resource Loading " + name, Project.MSG_VERBOSE ); + try + { + ClassLoader cL = null; + InputStream is = null; + + if( classpath != null ) + { + cL = new AntClassLoader( project, classpath ); + } + else + { + cL = this.getClass().getClassLoader(); + } + + if( cL == null ) + { + is = ClassLoader.getSystemResourceAsStream( name ); + } + else + { + is = cL.getResourceAsStream( name ); + } + + if( is != null ) + { + props.load( is ); + addProperties( props ); + } + else + { + log( "Unable to find resource " + name, Project.MSG_WARN ); + } + } + catch( IOException ex ) + { + throw new BuildException( ex ); + } + } + + private void resolveAllProperties( Properties props ) + throws BuildException + { + for( Enumeration e = props.keys(); e.hasMoreElements(); ) + { + String name = ( String )e.nextElement(); + String value = props.getProperty( name ); + + boolean resolved = false; + while( !resolved ) + { + Vector fragments = new Vector(); + Vector propertyRefs = new Vector(); + ProjectHelper.parsePropertyString( value, fragments, propertyRefs ); + + resolved = true; + if( propertyRefs.size() != 0 ) + { + StringBuffer sb = new StringBuffer(); + Enumeration i = fragments.elements(); + Enumeration j = propertyRefs.elements(); + while( i.hasMoreElements() ) + { + String fragment = ( String )i.nextElement(); + if( fragment == null ) + { + String propertyName = ( String )j.nextElement(); + if( propertyName.equals( name ) ) + { + throw new BuildException( "Property " + name + " was circularly defined." ); + } + fragment = getProject().getProperty( propertyName ); + if( fragment == null ) + { + if( props.containsKey( propertyName ) ) + { + fragment = props.getProperty( propertyName ); + resolved = false; + } + else + { + fragment = "${" + propertyName + "}"; + } + } + } + sb.append( fragment ); + } + value = sb.toString(); + props.put( name, value ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java new file mode 100644 index 000000000..ab5109052 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copies standard output and error of subprocesses to standard output and error + * of the parent process. TODO: standard input of the subprocess is not + * implemented. + * + * @author thomas.haas@softwired-inc.com + */ +public class PumpStreamHandler implements ExecuteStreamHandler +{ + private Thread errorThread; + + private Thread inputThread; + + private OutputStream out, err; + + public PumpStreamHandler( OutputStream out, OutputStream err ) + { + this.out = out; + this.err = err; + } + + public PumpStreamHandler( OutputStream outAndErr ) + { + this( outAndErr, outAndErr ); + } + + public PumpStreamHandler() + { + this( System.out, System.err ); + } + + + public void setProcessErrorStream( InputStream is ) + { + createProcessErrorPump( is, err ); + } + + + public void setProcessInputStream( OutputStream os ) { } + + public void setProcessOutputStream( InputStream is ) + { + createProcessOutputPump( is, out ); + } + + + public void start() + { + inputThread.start(); + errorThread.start(); + } + + + public void stop() + { + try + { + inputThread.join(); + } + catch( InterruptedException e ) + {} + try + { + errorThread.join(); + } + catch( InterruptedException e ) + {} + try + { + err.flush(); + } + catch( IOException e ) + {} + try + { + out.flush(); + } + catch( IOException e ) + {} + } + + protected OutputStream getErr() + { + return err; + } + + protected OutputStream getOut() + { + return out; + } + + protected void createProcessErrorPump( InputStream is, OutputStream os ) + { + errorThread = createPump( is, os ); + } + + protected void createProcessOutputPump( InputStream is, OutputStream os ) + { + inputThread = createPump( is, os ); + } + + + /** + * Creates a stream pumper to copy the given input stream to the given + * output stream. + * + * @param is Description of Parameter + * @param os Description of Parameter + * @return Description of the Returned Value + */ + protected Thread createPump( InputStream is, OutputStream os ) + { + final Thread result = new Thread( new StreamPumper( is, os ) ); + result.setDaemon( true ); + return result; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Recorder.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Recorder.java new file mode 100644 index 000000000..ebe3fcc92 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Recorder.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * This task is the manager for RecorderEntry's. It is this class that holds all + * entries, modifies them every time the <recorder> task is called, and + * addes them to the build listener process. + * + * @author J D Glanville + * @version 0.5 + * @see RecorderEntry + */ +public class Recorder extends Task +{ + /** + * The list of recorder entries. + */ + private static Hashtable recorderEntries = new Hashtable(); + + ////////////////////////////////////////////////////////////////////// + // ATTRIBUTES + + /** + * The name of the file to record to. + */ + private String filename = null; + /** + * Whether or not to append. Need Boolean to record an unset state (null). + */ + private Boolean append = null; + /** + * Whether to start or stop recording. Need Boolean to record an unset state + * (null). + */ + private Boolean start = null; + /** + * What level to log? -1 means not initialized yet. + */ + private int loglevel = -1; + + /** + * Sets the action for the associated recorder entry. + * + * @param action The action for the entry to take: start or stop. + */ + public void setAction( ActionChoices action ) + { + if( action.getValue().equalsIgnoreCase( "start" ) ) + { + start = Boolean.TRUE; + } + else + { + start = Boolean.FALSE; + } + } + + /** + * Whether or not the logger should append to a previous file. + * + * @param append The new Append value + */ + public void setAppend( boolean append ) + { + this.append = new Boolean( append ); + } + + /** + * Sets the level to which this recorder entry should log to. + * + * @param level The new Loglevel value + * @see VerbosityLevelChoices + */ + public void setLoglevel( VerbosityLevelChoices level ) + { + //I hate cascading if/elseif clauses !!! + String lev = level.getValue(); + if( lev.equalsIgnoreCase( "error" ) ) + { + loglevel = Project.MSG_ERR; + } + else if( lev.equalsIgnoreCase( "warn" ) ) + { + loglevel = Project.MSG_WARN; + } + else if( lev.equalsIgnoreCase( "info" ) ) + { + loglevel = Project.MSG_INFO; + } + else if( lev.equalsIgnoreCase( "verbose" ) ) + { + loglevel = Project.MSG_VERBOSE; + } + else if( lev.equalsIgnoreCase( "debug" ) ) + { + loglevel = Project.MSG_DEBUG; + } + } + + ////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS / INITIALIZERS + + ////////////////////////////////////////////////////////////////////// + // ACCESSOR METHODS + + /** + * Sets the name of the file to log to, and the name of the recorder entry. + * + * @param fname File name of logfile. + */ + public void setName( String fname ) + { + filename = fname; + } + + ////////////////////////////////////////////////////////////////////// + // CORE / MAIN BODY + + /** + * The main execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( filename == null ) + throw new BuildException( "No filename specified" ); + + getProject().log( "setting a recorder for name " + filename, + Project.MSG_DEBUG ); + + // get the recorder entry + RecorderEntry recorder = getRecorder( filename, getProject() ); + // set the values on the recorder + recorder.setMessageOutputLevel( loglevel ); + recorder.setRecordState( start ); + } + + /** + * Gets the recorder that's associated with the passed in name. If the + * recorder doesn't exist, then a new one is created. + * + * @param name Description of Parameter + * @param proj Description of Parameter + * @return The Recorder value + * @exception BuildException Description of Exception + */ + protected RecorderEntry getRecorder( String name, Project proj ) + throws BuildException + { + Object o = recorderEntries.get( name ); + RecorderEntry entry; + if( o == null ) + { + // create a recorder entry + try + { + entry = new RecorderEntry( name ); + PrintStream out = null; + if( append == null ) + { + out = new PrintStream( + new FileOutputStream( name ) ); + } + else + { + out = new PrintStream( + new FileOutputStream( name, append.booleanValue() ) ); + } + entry.setErrorPrintStream( out ); + entry.setOutputPrintStream( out ); + } + catch( IOException ioe ) + { + throw new BuildException( "Problems creating a recorder entry", + ioe ); + } + proj.addBuildListener( entry ); + recorderEntries.put( name, entry ); + } + else + { + entry = ( RecorderEntry )o; + } + return entry; + } + + ////////////////////////////////////////////////////////////////////// + // INNER CLASSES + + /** + * A list of possible values for the setAction() method. + * Possible values include: start and stop. + * + * @author RT + */ + public static class ActionChoices extends EnumeratedAttribute + { + private final static String[] values = {"start", "stop"}; + + public String[] getValues() + { + return values; + } + } + + /** + * A list of possible values for the setLoglevel() method. + * Possible values include: error, warn, info, verbose, debug. + * + * @author RT + */ + public static class VerbosityLevelChoices extends EnumeratedAttribute + { + private final static String[] values = {"error", "warn", "info", + "verbose", "debug"}; + + public String[] getValues() + { + return values; + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/RecorderEntry.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/RecorderEntry.java new file mode 100644 index 000000000..4dad6229e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/RecorderEntry.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.PrintStream; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildLogger; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + + +/** + * This is a class that represents a recorder. This is the listener to the build + * process. + * + * @author J D Glanville + * @version 0.5 + */ +public class RecorderEntry implements BuildLogger +{ + + ////////////////////////////////////////////////////////////////////// + // ATTRIBUTES + + /** + * The name of the file associated with this recorder entry. + */ + private String filename = null; + /** + * The state of the recorder (recorder on or off). + */ + private boolean record = true; + /** + * The current verbosity level to record at. + */ + private int loglevel = Project.MSG_INFO; + /** + * The output PrintStream to record to. + */ + private PrintStream out = null; + /** + * The start time of the last know target. + */ + private long targetStartTime = 0l; + + ////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS / INITIALIZERS + + /** + * @param name The name of this recorder (used as the filename). + */ + protected RecorderEntry( String name ) + { + filename = name; + } + + private static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + + } + + public void setEmacsMode( boolean emacsMode ) + { + throw new java.lang.RuntimeException( "Method setEmacsMode() not yet implemented." ); + } + + public void setErrorPrintStream( PrintStream err ) + { + out = err; + } + + public void setMessageOutputLevel( int level ) + { + if( level >= Project.MSG_ERR && level <= Project.MSG_DEBUG ) + loglevel = level; + } + + public void setOutputPrintStream( PrintStream output ) + { + out = output; + } + + /** + * Turns off or on this recorder. + * + * @param state true for on, false for off, null for no change. + */ + public void setRecordState( Boolean state ) + { + if( state != null ) + record = state.booleanValue(); + } + + ////////////////////////////////////////////////////////////////////// + // ACCESSOR METHODS + + /** + * @return the name of the file the output is sent to. + */ + public String getFilename() + { + return filename; + } + + public void buildFinished( BuildEvent event ) + { + log( "< BUILD FINISHED", Project.MSG_DEBUG ); + + Throwable error = event.getException(); + if( error == null ) + { + out.println( StringUtils.LINE_SEP + "BUILD SUCCESSFUL" ); + } + else + { + out.println( StringUtils.LINE_SEP + "BUILD FAILED" + StringUtils.LINE_SEP ); + error.printStackTrace( out ); + } + out.flush(); + out.close(); + } + + public void buildStarted( BuildEvent event ) + { + log( "> BUILD STARTED", Project.MSG_DEBUG ); + } + + public void messageLogged( BuildEvent event ) + { + log( "--- MESSAGE LOGGED", Project.MSG_DEBUG ); + + StringBuffer buf = new StringBuffer(); + if( event.getTask() != null ) + { + String name = "[" + event.getTask().getTaskName() + "]"; + /** + * @todo replace 12 with DefaultLogger.LEFT_COLUMN_SIZE + */ + for( int i = 0; i < ( 12 - name.length() ); i++ ) + { + buf.append( " " ); + }// for + buf.append( name ); + }// if + buf.append( event.getMessage() ); + + log( buf.toString(), event.getPriority() ); + } + + public void targetFinished( BuildEvent event ) + { + log( "<< TARGET FINISHED -- " + event.getTarget(), Project.MSG_DEBUG ); + String time = formatTime( System.currentTimeMillis() - targetStartTime ); + log( event.getTarget() + ": duration " + time, Project.MSG_VERBOSE ); + out.flush(); + } + + public void targetStarted( BuildEvent event ) + { + log( ">> TARGET STARTED -- " + event.getTarget(), Project.MSG_DEBUG ); + log( StringUtils.LINE_SEP + event.getTarget().getName() + ":", Project.MSG_INFO ); + targetStartTime = System.currentTimeMillis(); + } + + public void taskFinished( BuildEvent event ) + { + log( "<<< TASK FINISHED -- " + event.getTask(), Project.MSG_DEBUG ); + out.flush(); + } + + public void taskStarted( BuildEvent event ) + { + log( ">>> TASK STARTED -- " + event.getTask(), Project.MSG_DEBUG ); + } + + /** + * The thing that actually sends the information to the output. + * + * @param mesg The message to log. + * @param level The verbosity level of the message. + */ + private void log( String mesg, int level ) + { + if( record && ( level <= loglevel ) ) + { + out.println( mesg ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rename.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rename.java new file mode 100644 index 000000000..7e1090049 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rename.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Renames a file. + * + * @author haas@softwired.ch + * @deprecated The rename task is deprecated. Use move instead. + */ +public class Rename extends Task +{ + private boolean replace = true; + private File dest; + + private File src; + + /** + * Sets the new name of the file. + * + * @param dest the new name of the file. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Sets wheter an existing file should be replaced. + * + * @param replace on, if an existing file should be replaced. + */ + public void setReplace( String replace ) + { + this.replace = project.toBoolean( replace ); + } + + + /** + * Sets the file to be renamed. + * + * @param src the file to rename + */ + public void setSrc( File src ) + { + this.src = src; + } + + + /** + * Renames the file src to dest + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + log( "DEPRECATED - The rename task is deprecated. Use move instead." ); + + if( dest == null ) + { + throw new BuildException( "dest attribute is required", location ); + } + + if( src == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( replace && dest.exists() ) + { + if( !dest.delete() ) + { + throw new BuildException( "Unable to remove existing file " + + dest ); + } + } + if( !src.renameTo( dest ) ) + { + throw new BuildException( "Unable to rename " + src + " to " + + dest ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Replace.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Replace.java new file mode 100644 index 000000000..626ac8216 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Replace.java @@ -0,0 +1,591 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.FileUtils; + +/** + * Replaces all occurrences of one or more string tokens with given values in + * the indicated files. Each value can be either a string or the value of a + * property available in a designated property file. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Erik Langenbach + */ +public class Replace extends MatchingTask +{ + + private File src = null; + private NestedString token = null; + private NestedString value = new NestedString(); + + private File propertyFile = null; + private Properties properties = null; + private Vector replacefilters = new Vector(); + + private File dir = null; + private boolean summary = false; + + /** + * The encoding used to read and write files - if null, uses default + */ + private String encoding = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + private int fileCount; + private int replaceCount; + + + /** + * Set the source files path when using matching tasks. + * + * @param dir The new Dir value + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Set the file encoding to use on the files read and written by replace + * + * @param encoding the encoding to use on the files + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + + /** + * Set the source file. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.src = file; + } + + /** + * Sets a file to be searched for property values. + * + * @param filename The new PropertyFile value + */ + public void setPropertyFile( File filename ) + { + propertyFile = filename; + } + + /** + * Request a summary + * + * @param summary true if you would like a summary logged of the replace + * operation + */ + public void setSummary( boolean summary ) + { + this.summary = summary; + } + + /** + * Set the string token to replace. + * + * @param token The new Token value + */ + public void setToken( String token ) + { + createReplaceToken().addText( token ); + } + + /** + * Set the string value to use as token replacement. + * + * @param value The new Value value + */ + public void setValue( String value ) + { + createReplaceValue().addText( value ); + } + + public Properties getProperties( File propertyFile ) + throws BuildException + { + Properties properties = new Properties(); + + try + { + properties.load( new FileInputStream( propertyFile ) ); + } + catch( FileNotFoundException e ) + { + String message = "Property file (" + propertyFile.getPath() + ") not found."; + throw new BuildException( message ); + } + catch( IOException e ) + { + String message = "Property file (" + propertyFile.getPath() + ") cannot be loaded."; + throw new BuildException( message ); + } + + return properties; + } + + /** + * Nested <replacetoken> element. + * + * @return Description of the Returned Value + */ + public NestedString createReplaceToken() + { + if( token == null ) + { + token = new NestedString(); + } + return token; + } + + /** + * Nested <replacevalue> element. + * + * @return Description of the Returned Value + */ + public NestedString createReplaceValue() + { + return value; + } + + /** + * Add nested <replacefilter> element. + * + * @return Description of the Returned Value + */ + public Replacefilter createReplacefilter() + { + Replacefilter filter = new Replacefilter(); + replacefilters.addElement( filter ); + return filter; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + validateAttributes(); + + if( propertyFile != null ) + { + properties = getProperties( propertyFile ); + } + + validateReplacefilters(); + fileCount = 0; + replaceCount = 0; + + if( src != null ) + { + processFile( src ); + } + + if( dir != null ) + { + DirectoryScanner ds = super.getDirectoryScanner( dir ); + String[] srcs = ds.getIncludedFiles(); + + for( int i = 0; i < srcs.length; i++ ) + { + File file = new File( dir, srcs[i] ); + processFile( file ); + } + } + + if( summary ) + { + log( "Replaced " + replaceCount + " occurrences in " + fileCount + " files.", Project.MSG_INFO ); + } + } + + /** + * Validate attributes provided for this task in .xml build file. + * + * @exception BuildException if any supplied attribute is invalid or any + * mandatory attribute is missing + */ + public void validateAttributes() + throws BuildException + { + if( src == null && dir == null ) + { + String message = "Either the file or the dir attribute " + "must be specified"; + throw new BuildException( message, location ); + } + if( propertyFile != null && !propertyFile.exists() ) + { + String message = "Property file " + propertyFile.getPath() + " does not exist."; + throw new BuildException( message, location ); + } + if( token == null && replacefilters.size() == 0 ) + { + String message = "Either token or a nested replacefilter " + + "must be specified"; + throw new BuildException( message, location ); + } + if( token != null && "".equals( token.getText() ) ) + { + String message = "The token attribute must not be an empty string."; + throw new BuildException( message, location ); + } + } + + /** + * Validate nested elements. + * + * @exception BuildException if any supplied attribute is invalid or any + * mandatory attribute is missing + */ + public void validateReplacefilters() + throws BuildException + { + for( int i = 0; i < replacefilters.size(); i++ ) + { + Replacefilter element = ( Replacefilter )replacefilters.elementAt( i ); + element.validate(); + } + } + + /** + * Perform the replacement on the given file. The replacement is performed + * on a temporary file which then replaces the original file. + * + * @param src the source file + * @exception BuildException Description of Exception + */ + private void processFile( File src ) + throws BuildException + { + if( !src.exists() ) + { + throw new BuildException( "Replace: source file " + src.getPath() + " doesn't exist", location ); + } + + File temp = fileUtils.createTempFile( "rep", ".tmp", + fileUtils.getParentFile( src ) ); + + Reader reader = null; + Writer writer = null; + try + { + reader = encoding == null ? new FileReader( src ) + : new InputStreamReader( new FileInputStream( src ), encoding ); + writer = encoding == null ? new FileWriter( temp ) + : new OutputStreamWriter( new FileOutputStream( temp ), encoding ); + + BufferedReader br = new BufferedReader( reader ); + BufferedWriter bw = new BufferedWriter( writer ); + + // read the entire file into a StringBuffer + // size of work buffer may be bigger than needed + // when multibyte characters exist in the source file + // but then again, it might be smaller than needed on + // platforms like Windows where length can't be trusted + int fileLengthInBytes = ( int )( src.length() ); + StringBuffer tmpBuf = new StringBuffer( fileLengthInBytes ); + int readChar = 0; + int totread = 0; + while( true ) + { + readChar = br.read(); + if( readChar < 0 ) + { + break; + } + tmpBuf.append( ( char )readChar ); + totread++; + } + + // create a String so we can use indexOf + String buf = tmpBuf.toString(); + + //Preserve original string (buf) so we can compare the result + String newString = new String( buf ); + + if( token != null ) + { + // line separators in values and tokens are "\n" + // in order to compare with the file contents, replace them + // as needed + String linesep = System.getProperty( "line.separator" ); + String val = stringReplace( value.getText(), "\n", linesep ); + String tok = stringReplace( token.getText(), "\n", linesep ); + + // for each found token, replace with value + log( "Replacing in " + src.getPath() + ": " + token.getText() + " --> " + value.getText(), Project.MSG_VERBOSE ); + newString = stringReplace( newString, tok, val ); + } + + if( replacefilters.size() > 0 ) + { + newString = processReplacefilters( newString, src.getPath() ); + } + + boolean changes = !newString.equals( buf ); + if( changes ) + { + bw.write( newString, 0, newString.length() ); + bw.flush(); + } + + // cleanup + bw.close(); + writer = null; + br.close(); + reader = null; + + // If there were changes, move the new one to the old one; + // otherwise, delete the new one + if( changes ) + { + ++fileCount; + src.delete(); + temp.renameTo( src ); + temp = null; + } + } + catch( IOException ioe ) + { + throw new BuildException( "IOException in " + src + " - " + + ioe.getClass().getName() + ":" + ioe.getMessage(), ioe, location ); + } + finally + { + if( reader != null ) + { + try + { + reader.close(); + } + catch( IOException e ) + {} + } + if( writer != null ) + { + try + { + writer.close(); + } + catch( IOException e ) + {} + } + if( temp != null ) + { + temp.delete(); + } + } + + } + + private String processReplacefilters( String buffer, String filename ) + { + String newString = new String( buffer ); + + for( int i = 0; i < replacefilters.size(); i++ ) + { + Replacefilter filter = ( Replacefilter )replacefilters.elementAt( i ); + + //for each found token, replace with value + log( "Replacing in " + filename + ": " + filter.getToken() + " --> " + filter.getReplaceValue(), Project.MSG_VERBOSE ); + newString = stringReplace( newString, filter.getToken(), filter.getReplaceValue() ); + } + + return newString; + } + + /** + * Replace occurrences of str1 in string str with str2 + * + * @param str Description of Parameter + * @param str1 Description of Parameter + * @param str2 Description of Parameter + * @return Description of the Returned Value + */ + private String stringReplace( String str, String str1, String str2 ) + { + StringBuffer ret = new StringBuffer(); + int start = 0; + int found = str.indexOf( str1 ); + while( found >= 0 ) + { + // write everything up to the found str1 + if( found > start ) + { + ret.append( str.substring( start, found ) ); + } + + // write the replacement str2 + if( str2 != null ) + { + ret.append( str2 ); + } + + // search again + start = found + str1.length(); + found = str.indexOf( str1, start ); + ++replaceCount; + } + + // write the remaining characters + if( str.length() > start ) + { + ret.append( str.substring( start, str.length() ) ); + } + + return ret.toString(); + } + + //Inner class + public class NestedString + { + + private StringBuffer buf = new StringBuffer(); + + public String getText() + { + return buf.toString(); + } + + public void addText( String val ) + { + buf.append( val ); + } + } + + //Inner class + public class Replacefilter + { + private String property; + private String token; + private String value; + + public void setProperty( String property ) + { + this.property = property; + } + + public void setToken( String token ) + { + this.token = token; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getProperty() + { + return property; + } + + public String getReplaceValue() + { + if( property != null ) + { + return ( String )properties.getProperty( property ); + } + else if( value != null ) + { + return value; + } + else if( Replace.this.value != null ) + { + return Replace.this.value.getText(); + } + else + { + //Default is empty string + return new String( "" ); + } + } + + public String getToken() + { + return token; + } + + public String getValue() + { + return value; + } + + public void validate() + throws BuildException + { + //Validate mandatory attributes + if( token == null ) + { + String message = "token is a mandatory attribute " + "of replacefilter."; + throw new BuildException( message ); + } + + if( "".equals( token ) ) + { + String message = "The token attribute must not be an empty string."; + throw new BuildException( message ); + } + + //value and property are mutually exclusive attributes + if( ( value != null ) && ( property != null ) ) + { + String message = "Either value or property " + "can be specified, but a replacefilter " + "element cannot have both."; + throw new BuildException( message ); + } + + if( ( property != null ) ) + { + //the property attribute must have access to a property file + if( propertyFile == null ) + { + String message = "The replacefilter's property attribute " + "can only be used with the replacetask's " + "propertyFile attribute."; + throw new BuildException( message ); + } + + //Make sure property exists in property file + if( properties == null || + properties.getProperty( property ) == null ) + { + String message = "property \"" + property + "\" was not found in " + propertyFile.getPath(); + throw new BuildException( message ); + } + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rmic.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rmic.java new file mode 100644 index 000000000..e37fac08e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rmic.java @@ -0,0 +1,688 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.rmi.Remote; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.rmic.RmicAdapter; +import org.apache.tools.ant.taskdefs.rmic.RmicAdapterFactory; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Task to compile RMI stubs and skeletons. This task can take the following + * arguments: + *

        + *
      • base: The base directory for the compiled stubs and skeletons + *
      • class: The name of the class to generate the stubs from + *
      • stubVersion: The version of the stub prototol to use (1.1, 1.2, + * compat) + *
      • sourceBase: The base directory for the generated stubs and skeletons + * + *
      • classpath: Additional classpath, appended before the system classpath + * + *
      • iiop: Generate IIOP compatable output + *
      • iiopopts: Include IIOP options + *
      • idl: Generate IDL output + *
      • idlopts: Include IDL options + *
      • includeantruntime + *
      • includejavaruntime + *
      • extdirs + *
      + * Of these arguments, base is required.

      + * + * If classname is specified then only that classname will be compiled. If it is + * absent, then base is traversed for classes according to patterns.

      + * + * + * + * @author duncan@x180.com + * @author ludovic.claude@websitewatchers.co.uk + * @author David Maclean david@cm.co.za + * @author Stefan Bodewig + * @author Takashi Okamoto tokamoto@rd.nttdata.co.jp + */ + +public class Rmic extends MatchingTask +{ + + private final static String FAIL_MSG + = "Rmic failed, messages should have been provided."; + private boolean verify = false; + private boolean filtering = false; + + private boolean iiop = false; + private boolean idl = false; + private boolean debug = false; + private boolean includeAntRuntime = true; + private boolean includeJavaRuntime = false; + + private Vector compileList = new Vector(); + + private ClassLoader loader = null; + + private File baseDir; + private String classname; + private Path compileClasspath; + private Path extdirs; + private String idlopts; + private String iiopopts; + private File sourceBase; + private String stubVersion; + + /** + * Sets the base directory to output generated class. + * + * @param base The new Base value + */ + public void setBase( File base ) + { + this.baseDir = base; + } + + /** + * Sets the class name to compile. + * + * @param classname The new Classname value + */ + public void setClassname( String classname ) + { + this.classname = classname; + } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Sets the debug flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Sets the extension directories that will be used during the compilation. + * + * @param extdirs The new Extdirs value + */ + public void setExtdirs( Path extdirs ) + { + if( this.extdirs == null ) + { + this.extdirs = extdirs; + } + else + { + this.extdirs.append( extdirs ); + } + } + + public void setFiltering( boolean filter ) + { + filtering = filter; + } + + /** + * Indicates that IDL output should be generated. This defaults to false if + * not set. + * + * @param idl The new Idl value + */ + public void setIdl( boolean idl ) + { + this.idl = idl; + } + + /** + * pass additional arguments for idl compile + * + * @param idlopts The new Idlopts value + */ + public void setIdlopts( String idlopts ) + { + this.idlopts = idlopts; + } + + /** + * Indicates that IIOP compatible stubs should be generated. This defaults + * to false if not set. + * + * @param iiop The new Iiop value + */ + public void setIiop( boolean iiop ) + { + this.iiop = iiop; + } + + /** + * pass additional arguments for iiop + * + * @param iiopopts The new Iiopopts value + */ + public void setIiopopts( String iiopopts ) + { + this.iiopopts = iiopopts; + } + + /** + * Include ant's own classpath in this task's classpath? + * + * @param include The new Includeantruntime value + */ + public void setIncludeantruntime( boolean include ) + { + includeAntRuntime = include; + } + + /** + * Sets whether or not to include the java runtime libraries to this task's + * classpath. + * + * @param include The new Includejavaruntime value + */ + public void setIncludejavaruntime( boolean include ) + { + includeJavaRuntime = include; + } + + /** + * Sets the source dirs to find the source java files. + * + * @param sourceBase The new SourceBase value + */ + public void setSourceBase( File sourceBase ) + { + this.sourceBase = sourceBase; + } + + /** + * Sets the stub version. + * + * @param stubVersion The new StubVersion value + */ + public void setStubVersion( String stubVersion ) + { + this.stubVersion = stubVersion; + } + + /** + * Indicates that the classes found by the directory match should be checked + * to see if they implement java.rmi.Remote. This defaults to false if not + * set. + * + * @param verify The new Verify value + */ + public void setVerify( boolean verify ) + { + this.verify = verify; + } + + /** + * Gets the base directory to output generated class. + * + * @return The Base value + */ + public File getBase() + { + return this.baseDir; + } + + /** + * Gets the class name to compile. + * + * @return The Classname value + */ + public String getClassname() + { + return classname; + } + + /** + * Gets the classpath. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return compileClasspath; + } + + public Vector getCompileList() + { + return compileList; + } + + /** + * Gets the debug flag. + * + * @return The Debug value + */ + public boolean getDebug() + { + return debug; + } + + /** + * Gets the extension directories that will be used during the compilation. + * + * @return The Extdirs value + */ + public Path getExtdirs() + { + return extdirs; + } + + /** + * Gets file list to compile. + * + * @return The FileList value + */ + public Vector getFileList() + { + return compileList; + } + + public boolean getFiltering() + { + return filtering; + } + + /* + * Gets IDL flags. + */ + public boolean getIdl() + { + return idl; + } + + /** + * Gets additional arguments for idl compile. + * + * @return The Idlopts value + */ + public String getIdlopts() + { + return idlopts; + } + + /** + * Gets iiop flags. + * + * @return The Iiop value + */ + public boolean getIiop() + { + return iiop; + } + + /** + * Gets additional arguments for iiop. + * + * @return The Iiopopts value + */ + public String getIiopopts() + { + return iiopopts; + } + + /** + * Gets whether or not the ant classpath is to be included in the task's + * classpath. + * + * @return The Includeantruntime value + */ + public boolean getIncludeantruntime() + { + return includeAntRuntime; + } + + /** + * Gets whether or not the java runtime should be included in this task's + * classpath. + * + * @return The Includejavaruntime value + */ + public boolean getIncludejavaruntime() + { + return includeJavaRuntime; + } + + /** + * Classloader for the user-specified classpath. + * + * @return The Loader value + */ + public ClassLoader getLoader() + { + return loader; + } + + /** + * Returns the topmost interface that extends Remote for a given class - if + * one exists. + * + * @param testClass Description of Parameter + * @return The RemoteInterface value + */ + public Class getRemoteInterface( Class testClass ) + { + if( Remote.class.isAssignableFrom( testClass ) ) + { + Class[] interfaces = testClass.getInterfaces(); + if( interfaces != null ) + { + for( int i = 0; i < interfaces.length; i++ ) + { + if( Remote.class.isAssignableFrom( interfaces[i] ) ) + { + return interfaces[i]; + } + } + } + } + return null; + } + + /** + * Gets the source dirs to find the source java files. + * + * @return The SourceBase value + */ + public File getSourceBase() + { + return sourceBase; + } + + public String getStubVersion() + { + return stubVersion; + } + + /** + * Get verify flag. + * + * @return The Verify value + */ + public boolean getVerify() + { + return verify; + } + + /** + * Load named class and test whether it can be rmic'ed + * + * @param classname Description of Parameter + * @return The ValidRmiRemote value + */ + public boolean isValidRmiRemote( String classname ) + { + try + { + Class testClass = loader.loadClass( classname ); + // One cannot RMIC an interface for "classic" RMI (JRMP) + if( testClass.isInterface() && !iiop && !idl ) + { + return false; + } + return isValidRmiRemote( testClass ); + } + catch( ClassNotFoundException e ) + { + log( "Unable to verify class " + classname + + ". It could not be found.", Project.MSG_WARN ); + } + catch( NoClassDefFoundError e ) + { + log( "Unable to verify class " + classname + + ". It is not defined.", Project.MSG_WARN ); + } + catch( Throwable t ) + { + log( "Unable to verify class " + classname + + ". Loading caused Exception: " + + t.getMessage(), Project.MSG_WARN ); + } + // we only get here if an exception has been thrown + return false; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath.createPath(); + } + + /** + * Maybe creates a nested extdirs element. + * + * @return Description of the Returned Value + */ + public Path createExtdirs() + { + if( extdirs == null ) + { + extdirs = new Path( project ); + } + return extdirs.createPath(); + } + + public void execute() + throws BuildException + { + if( baseDir == null ) + { + throw new BuildException( "base attribute must be set!", location ); + } + if( !baseDir.exists() ) + { + throw new BuildException( "base does not exist!", location ); + } + + if( verify ) + { + log( "Verify has been turned on.", Project.MSG_INFO ); + } + + String compiler = project.getProperty( "build.rmic" ); + RmicAdapter adapter = RmicAdapterFactory.getRmic( compiler, this ); + + // now we need to populate the compiler adapter + adapter.setRmic( this ); + + Path classpath = adapter.getClasspath(); + loader = new AntClassLoader( project, classpath ); + + // scan base dirs to build up compile lists only if a + // specific classname is not given + if( classname == null ) + { + DirectoryScanner ds = this.getDirectoryScanner( baseDir ); + String[] files = ds.getIncludedFiles(); + scanDir( baseDir, files, adapter.getMapper() ); + } + else + { + // otherwise perform a timestamp comparison - at least + scanDir( baseDir, + new String[]{classname.replace( '.', File.separatorChar ) + ".class"}, + adapter.getMapper() ); + } + + int fileCount = compileList.size(); + if( fileCount > 0 ) + { + log( "RMI Compiling " + fileCount + + " class" + ( fileCount > 1 ? "es" : "" ) + " to " + baseDir, + Project.MSG_INFO ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + throw new BuildException( FAIL_MSG, location ); + } + } + + /* + * Move the generated source file to the base directory. If + * base directory and sourcebase are the same, the generated + * sources are already in place. + */ + if( null != sourceBase && !baseDir.equals( sourceBase ) ) + { + if( idl ) + { + log( "Cannot determine sourcefiles in idl mode, ", + Project.MSG_WARN ); + log( "sourcebase attribute will be ignored.", Project.MSG_WARN ); + } + else + { + for( int j = 0; j < fileCount; j++ ) + { + moveGeneratedFile( baseDir, sourceBase, + ( String )compileList.elementAt( j ), + adapter ); + } + } + } + compileList.removeAllElements(); + } + + /** + * Scans the directory looking for class files to be compiled. The result is + * returned in the class variable compileList. + * + * @param baseDir Description of Parameter + * @param files Description of Parameter + * @param mapper Description of Parameter + */ + protected void scanDir( File baseDir, String files[], + FileNameMapper mapper ) + { + + String[] newFiles = files; + if( idl ) + { + log( "will leave uptodate test to rmic implementation in idl mode.", + Project.MSG_VERBOSE ); + } + else if( iiop + && iiopopts != null && iiopopts.indexOf( "-always" ) > -1 ) + { + log( "no uptodate test as -always option has been specified", + Project.MSG_VERBOSE ); + } + else + { + SourceFileScanner sfs = new SourceFileScanner( this ); + newFiles = sfs.restrict( files, baseDir, baseDir, mapper ); + } + + for( int i = 0; i < newFiles.length; i++ ) + { + String classname = newFiles[i].replace( File.separatorChar, '.' ); + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + compileList.addElement( classname ); + } + } + + /** + * Check to see if the class or (super)interfaces implement java.rmi.Remote. + * + * @param testClass Description of Parameter + * @return The ValidRmiRemote value + */ + private boolean isValidRmiRemote( Class testClass ) + { + return getRemoteInterface( testClass ) != null; + } + + /** + * Move the generated source file(s) to the base directory + * + * @param baseDir Description of Parameter + * @param sourceBaseFile Description of Parameter + * @param classname Description of Parameter + * @param adapter Description of Parameter + * @exception BuildException Description of Exception + */ + private void moveGeneratedFile( File baseDir, File sourceBaseFile, + String classname, + RmicAdapter adapter ) + throws BuildException + { + + String classFileName = + classname.replace( '.', File.separatorChar ) + ".class"; + String[] generatedFiles = + adapter.getMapper().mapFileName( classFileName ); + + for( int i = 0; i < generatedFiles.length; i++ ) + { + String sourceFileName = + classFileName.substring( 0, classFileName.length() - 6 ) + ".java"; + File oldFile = new File( baseDir, sourceFileName ); + File newFile = new File( sourceBaseFile, sourceFileName ); + try + { + project.copyFile( oldFile, newFile, filtering ); + oldFile.delete(); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + oldFile + " to " + + newFile + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SQLExec.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SQLExec.java new file mode 100644 index 000000000..27acf92a0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SQLExec.java @@ -0,0 +1,867 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Reader; +import java.io.StringReader; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.Enumeration; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Reads in a text file containing SQL statements seperated with semicolons and + * executes it in a given db. Comments may be created with REM -- or //. + * + * @author Jeff Martin + * @author Michael McCallum + * @author Tim Stephenson + */ +public class SQLExec extends Task +{ + + private int goodSql = 0, totalSql = 0; + + private Vector filesets = new Vector(); + + /** + * Database connection + */ + private Connection conn = null; + + /** + * Autocommit flag. Default value is false + */ + private boolean autocommit = false; + + /** + * SQL statement + */ + private Statement statement = null; + + /** + * DB driver. + */ + private String driver = null; + + /** + * DB url. + */ + private String url = null; + + /** + * User name. + */ + private String userId = null; + + /** + * Password + */ + private String password = null; + + /** + * SQL input file + */ + private File srcFile = null; + + /** + * SQL input command + */ + private String sqlCommand = ""; + + /** + * SQL transactions to perform + */ + private Vector transactions = new Vector(); + + /** + * SQL Statement delimiter + */ + private String delimiter = ";"; + + /** + * The delimiter type indicating whether the delimiter will only be + * recognized on a line by itself + */ + private String delimiterType = DelimiterType.NORMAL; + + /** + * Print SQL results. + */ + private boolean print = false; + + /** + * Print header columns. + */ + private boolean showheaders = true; + + /** + * Results Output file. + */ + private File output = null; + + /** + * RDBMS Product needed for this SQL. + */ + private String rdbms = null; + + /** + * RDBMS Version needed for this SQL. + */ + private String version = null; + + /** + * Action to perform if an error is found + */ + private String onError = "abort"; + + /** + * Encoding to use when reading SQL statements from a file + */ + private String encoding = null; + + private Path classpath; + + private AntClassLoader loader; + + /** + * Set the autocommit flag for the DB connection. + * + * @param autocommit The new Autocommit value + */ + public void setAutocommit( boolean autocommit ) + { + this.autocommit = autocommit; + } + + /** + * Set the classpath for loading the driver. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * Set the classpath for loading the driver using the classpath reference. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the statement delimiter.

      + * + * For example, set this to "go" and delimitertype to "ROW" for Sybase ASE + * or MS SQL Server.

      + * + * @param delimiter The new Delimiter value + */ + public void setDelimiter( String delimiter ) + { + this.delimiter = delimiter; + } + + /** + * Set the Delimiter type for this sql task. The delimiter type takes two + * values - normal and row. Normal means that any occurence of the delimiter + * terminate the SQL command whereas with row, only a line containing just + * the delimiter is recognized as the end of the command. + * + * @param delimiterType The new DelimiterType value + */ + public void setDelimiterType( DelimiterType delimiterType ) + { + this.delimiterType = delimiterType.getValue(); + } + + /** + * Set the JDBC driver to be used. + * + * @param driver The new Driver value + */ + public void setDriver( String driver ) + { + this.driver = driver; + } + + /** + * Set the file encoding to use on the sql files read in + * + * @param encoding the encoding to use on the files + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Set the action to perform onerror + * + * @param action The new Onerror value + */ + public void setOnerror( OnError action ) + { + this.onError = action.getValue(); + } + + /** + * Set the output file. + * + * @param output The new Output value + */ + public void setOutput( File output ) + { + this.output = output; + } + + + /** + * Set the password for the DB connection. + * + * @param password The new Password value + */ + public void setPassword( String password ) + { + this.password = password; + } + + /** + * Set the print flag. + * + * @param print The new Print value + */ + public void setPrint( boolean print ) + { + this.print = print; + } + + /** + * Set the rdbms required + * + * @param vendor The new Rdbms value + */ + public void setRdbms( String vendor ) + { + this.rdbms = vendor.toLowerCase(); + } + + /** + * Set the showheaders flag. + * + * @param showheaders The new Showheaders value + */ + public void setShowheaders( boolean showheaders ) + { + this.showheaders = showheaders; + } + + /** + * Set the name of the sql file to be run. + * + * @param srcFile The new Src value + */ + public void setSrc( File srcFile ) + { + this.srcFile = srcFile; + } + + /** + * Set the DB connection url. + * + * @param url The new Url value + */ + public void setUrl( String url ) + { + this.url = url; + } + + /** + * Set the user name for the DB connection. + * + * @param userId The new Userid value + */ + public void setUserid( String userId ) + { + this.userId = userId; + } + + /** + * Set the version required + * + * @param version The new Version value + */ + public void setVersion( String version ) + { + this.version = version.toLowerCase(); + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Set the sql command to execute + * + * @param sql The feature to be added to the Text attribute + */ + public void addText( String sql ) + { + this.sqlCommand += sql; + } + + /** + * Create the classpath for loading the driver. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + + /** + * Set the sql command to execute + * + * @return Description of the Returned Value + */ + public Transaction createTransaction() + { + Transaction t = new Transaction(); + transactions.addElement( t ); + return t; + } + + /** + * Load the sql file and then execute it + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + sqlCommand = sqlCommand.trim(); + + if( srcFile == null && sqlCommand.length() == 0 && filesets.isEmpty() ) + { + if( transactions.size() == 0 ) + { + throw new BuildException( "Source file or fileset, transactions or sql statement must be set!", location ); + } + } + else + { + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File srcDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + + // Make a transaction for each file + for( int j = 0; j < srcFiles.length; j++ ) + { + Transaction t = createTransaction(); + t.setSrc( new File( srcDir, srcFiles[j] ) ); + } + } + + // Make a transaction group for the outer command + Transaction t = createTransaction(); + t.setSrc( srcFile ); + t.addText( sqlCommand ); + } + + if( driver == null ) + { + throw new BuildException( "Driver attribute must be set!", location ); + } + if( userId == null ) + { + throw new BuildException( "User Id attribute must be set!", location ); + } + if( password == null ) + { + throw new BuildException( "Password attribute must be set!", location ); + } + if( url == null ) + { + throw new BuildException( "Url attribute must be set!", location ); + } + if( srcFile != null && !srcFile.exists() ) + { + throw new BuildException( "Source file does not exist!", location ); + } + Driver driverInstance = null; + // Load the driver using the + try + { + Class dc; + if( classpath != null ) + { + log( "Loading " + driver + " using AntClassLoader with classpath " + classpath, + Project.MSG_VERBOSE ); + + loader = new AntClassLoader( project, classpath ); + dc = loader.loadClass( driver ); + } + else + { + log( "Loading " + driver + " using system loader.", Project.MSG_VERBOSE ); + dc = Class.forName( driver ); + } + driverInstance = ( Driver )dc.newInstance(); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( "Class Not Found: JDBC driver " + driver + " could not be loaded", location ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( "Illegal Access: JDBC driver " + driver + " could not be loaded", location ); + } + catch( InstantiationException e ) + { + throw new BuildException( "Instantiation Exception: JDBC driver " + driver + " could not be loaded", location ); + } + + try + { + log( "connecting to " + url, Project.MSG_VERBOSE ); + Properties info = new Properties(); + info.put( "user", userId ); + info.put( "password", password ); + conn = driverInstance.connect( url, info ); + + if( conn == null ) + { + // Driver doesn't understand the URL + throw new SQLException( "No suitable Driver for " + url ); + } + + if( !isValidRdbms( conn ) ) + return; + + conn.setAutoCommit( autocommit ); + + statement = conn.createStatement(); + + PrintStream out = System.out; + try + { + if( output != null ) + { + log( "Opening PrintStream to output file " + output, Project.MSG_VERBOSE ); + out = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + + // Process all transactions + for( Enumeration e = transactions.elements(); + e.hasMoreElements(); ) + { + + ( ( Transaction )e.nextElement() ).runTransaction( out ); + if( !autocommit ) + { + log( "Commiting transaction", Project.MSG_VERBOSE ); + conn.commit(); + } + } + } + finally + { + if( out != null && out != System.out ) + { + out.close(); + } + } + } + catch( IOException e ) + { + if( !autocommit && conn != null && onError.equals( "abort" ) ) + { + try + { + conn.rollback(); + } + catch( SQLException ex ) + {} + } + throw new BuildException( e ); + } + catch( SQLException e ) + { + if( !autocommit && conn != null && onError.equals( "abort" ) ) + { + try + { + conn.rollback(); + } + catch( SQLException ex ) + {} + } + throw new BuildException( e ); + } + finally + { + try + { + if( statement != null ) + { + statement.close(); + } + if( conn != null ) + { + conn.close(); + } + } + catch( SQLException e ) + {} + } + + log( goodSql + " of " + totalSql + + " SQL statements executed successfully" ); + } + + /** + * Verify if connected to the correct RDBMS + * + * @param conn Description of Parameter + * @return The ValidRdbms value + */ + protected boolean isValidRdbms( Connection conn ) + { + if( rdbms == null && version == null ) + return true; + + try + { + DatabaseMetaData dmd = conn.getMetaData(); + + if( rdbms != null ) + { + String theVendor = dmd.getDatabaseProductName().toLowerCase(); + + log( "RDBMS = " + theVendor, Project.MSG_VERBOSE ); + if( theVendor == null || theVendor.indexOf( rdbms ) < 0 ) + { + log( "Not the required RDBMS: " + rdbms, Project.MSG_VERBOSE ); + return false; + } + } + + if( version != null ) + { + String theVersion = dmd.getDatabaseProductVersion().toLowerCase(); + + log( "Version = " + theVersion, Project.MSG_VERBOSE ); + if( theVersion == null || + !( theVersion.startsWith( version ) || + theVersion.indexOf( " " + version ) >= 0 ) ) + { + log( "Not the required version: \"" + version + "\"", Project.MSG_VERBOSE ); + return false; + } + } + } + catch( SQLException e ) + { + // Could not get the required information + log( "Failed to obtain required RDBMS information", Project.MSG_ERR ); + return false; + } + + return true; + } + + /** + * Exec the sql statement. + * + * @param sql Description of Parameter + * @param out Description of Parameter + * @exception SQLException Description of Exception + */ + protected void execSQL( String sql, PrintStream out ) + throws SQLException + { + // Check and ignore empty statements + if( "".equals( sql.trim() ) ) + return; + + try + { + totalSql++; + if( !statement.execute( sql ) ) + { + log( statement.getUpdateCount() + " rows affected", + Project.MSG_VERBOSE ); + } + else + { + if( print ) + { + printResults( out ); + } + } + + SQLWarning warning = conn.getWarnings(); + while( warning != null ) + { + log( warning + " sql warning", Project.MSG_VERBOSE ); + warning = warning.getNextWarning(); + } + conn.clearWarnings(); + goodSql++; + } + catch( SQLException e ) + { + log( "Failed to execute: " + sql, Project.MSG_ERR ); + if( !onError.equals( "continue" ) ) + throw e; + log( e.toString(), Project.MSG_ERR ); + } + } + + /** + * print any results in the statement. + * + * @param out Description of Parameter + * @exception java.sql.SQLException Description of Exception + */ + protected void printResults( PrintStream out ) + throws java.sql.SQLException + { + ResultSet rs = null; + do + { + rs = statement.getResultSet(); + if( rs != null ) + { + log( "Processing new result set.", Project.MSG_VERBOSE ); + ResultSetMetaData md = rs.getMetaData(); + int columnCount = md.getColumnCount(); + StringBuffer line = new StringBuffer(); + if( showheaders ) + { + for( int col = 1; col < columnCount; col++ ) + { + line.append( md.getColumnName( col ) ); + line.append( "," ); + } + line.append( md.getColumnName( columnCount ) ); + out.println( line ); + line.setLength( 0 ); + } + while( rs.next() ) + { + boolean first = true; + for( int col = 1; col <= columnCount; col++ ) + { + String columnValue = rs.getString( col ); + if( columnValue != null ) + { + columnValue = columnValue.trim(); + } + + if( first ) + { + first = false; + } + else + { + line.append( "," ); + } + line.append( columnValue ); + } + out.println( line ); + line.setLength( 0 ); + } + } + }while ( statement.getMoreResults() ); + out.println(); + } + + protected void runStatements( Reader reader, PrintStream out ) + throws SQLException, IOException + { + String sql = ""; + String line = ""; + + BufferedReader in = new BufferedReader( reader ); + + try + { + while( ( line = in.readLine() ) != null ) + { + line = line.trim(); + line = project.replaceProperties( line ); + if( line.startsWith( "//" ) ) + continue; + if( line.startsWith( "--" ) ) + continue; + StringTokenizer st = new StringTokenizer( line ); + if( st.hasMoreTokens() ) + { + String token = st.nextToken(); + if( "REM".equalsIgnoreCase( token ) ) + { + continue; + } + } + + sql += " " + line; + sql = sql.trim(); + + // SQL defines "--" as a comment to EOL + // and in Oracle it may contain a hint + // so we cannot just remove it, instead we must end it + if( line.indexOf( "--" ) >= 0 ) + sql += "\n"; + + if( delimiterType.equals( DelimiterType.NORMAL ) && sql.endsWith( delimiter ) || + delimiterType.equals( DelimiterType.ROW ) && line.equals( delimiter ) ) + { + log( "SQL: " + sql, Project.MSG_VERBOSE ); + execSQL( sql.substring( 0, sql.length() - delimiter.length() ), out ); + sql = ""; + } + } + + // Catch any statements not followed by ; + if( !sql.equals( "" ) ) + { + execSQL( sql, out ); + } + } + catch( SQLException e ) + { + throw e; + } + + } + + public static class DelimiterType extends EnumeratedAttribute + { + public final static String NORMAL = "normal"; + public final static String ROW = "row"; + + public String[] getValues() + { + return new String[]{NORMAL, ROW}; + } + } + + /** + * Enumerated attribute with the values "continue", "stop" and "abort" for + * the onerror attribute. + * + * @author RT + */ + public static class OnError extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"continue", "stop", "abort"}; + } + } + + /** + * Contains the definition of a new transaction element. Transactions allow + * several files or blocks of statements to be executed using the same JDBC + * connection and commit operation in between. + * + * @author RT + */ + public class Transaction + { + private File tSrcFile = null; + private String tSqlCommand = ""; + + public void setSrc( File src ) + { + this.tSrcFile = src; + } + + public void addText( String sql ) + { + this.tSqlCommand += sql; + } + + private void runTransaction( PrintStream out ) + throws IOException, SQLException + { + if( tSqlCommand.length() != 0 ) + { + log( "Executing commands", Project.MSG_INFO ); + runStatements( new StringReader( tSqlCommand ), out ); + } + + if( tSrcFile != null ) + { + log( "Executing file: " + tSrcFile.getAbsolutePath(), + Project.MSG_INFO ); + Reader reader = ( encoding == null ) ? new FileReader( tSrcFile ) + : new InputStreamReader( new FileInputStream( tSrcFile ), encoding ); + runStatements( reader, out ); + reader.close(); + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SendEmail.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SendEmail.java new file mode 100644 index 000000000..052ed4940 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SendEmail.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.mail.MailMessage; + +/** + * A task to send SMTP email.

      + * + * + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * from + * + * + * + * Email address of sender. + * + * + * + * Yes + * + * + * + * + * + * + * + * mailhost + * + * + * + * Host name of the mail server. + * + * + * + * No, default to "localhost" + * + * + * + * + * + * + * + * toList + * + * + * + * Comma-separated list of recipients. + * + * + * + * Yes + * + * + * + * + * + * + * + * subject + * + * + * + * Email subject line. + * + * + * + * No + * + * + * + * + * + * + * + * files + * + * + * + * Filename(s) of text to send in the body of the email. Multiple files + * are comma-separated. + * + * + * + * One of these two attributes + * + * + * + * + * + * + * + * message + * + * + * + * Message to send inthe body of the email. + * + * + * + * + * + * + * + * + * + * includefilenames + * + * + * + * Includes filenames before file contents when set to true. + * + * + * + * No, default is false + * + * + * + *

      + * + * + * + * @author glenn_twiggs@bmc.com + * @author Magesh Umasankar + */ +public class SendEmail extends Task +{ + private String mailhost = "localhost"; + private int mailport = MailMessage.DEFAULT_PORT; + private Vector files = new Vector(); + /** + * failure flag + */ + private boolean failOnError = true; + private String from; + private boolean includefilenames; + private String message; + private String subject; + private String toList; + + + /** + * Creates new SendEmail + */ + public SendEmail() { } + + /** + * Sets the FailOnError attribute of the MimeMail object + * + * @param failOnError The new FailOnError value + * @since 1.5 + */ + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + /** + * Sets the file parameter of this build task. + * + * @param filenames Filenames to include as the message body of this email. + */ + public void setFiles( String filenames ) + { + StringTokenizer t = new StringTokenizer( filenames, ", " ); + + while( t.hasMoreTokens() ) + { + files.addElement( project.resolveFile( t.nextToken() ) ); + } + } + + /** + * Sets the from parameter of this build task. + * + * @param from Email address of sender. + */ + public void setFrom( String from ) + { + this.from = from; + } + + /** + * Sets Includefilenames attribute + * + * @param includefilenames Set to true if file names are to be included. + * @since 1.5 + */ + public void setIncludefilenames( boolean includefilenames ) + { + this.includefilenames = includefilenames; + } + + /** + * Sets the mailhost parameter of this build task. + * + * @param mailhost Mail host name. + */ + public void setMailhost( String mailhost ) + { + this.mailhost = mailhost; + } + + /** + * Sets the mailport parameter of this build task. + * + * @param value mail port name. + */ + public void setMailport( Integer value ) + { + this.mailport = value.intValue(); + } + + /** + * Sets the message parameter of this build task. + * + * @param message Message body of this email. + */ + public void setMessage( String message ) + { + this.message = message; + } + + /** + * Sets the subject parameter of this build task. + * + * @param subject Subject of this email. + */ + public void setSubject( String subject ) + { + this.subject = subject; + } + + /** + * Sets the toList parameter of this build task. + * + * @param toList Comma-separated list of email recipient addreses. + */ + public void setToList( String toList ) + { + this.toList = toList; + } + + /** + * Executes this build task. + * + * @throws BuildException if there is an error during task execution. + */ + public void execute() + throws BuildException + { + try + { + MailMessage mailMessage = new MailMessage( mailhost ); + mailMessage.setPort( mailport ); + + if( from != null ) + { + mailMessage.from( from ); + } + else + { + throw new BuildException( "Attribute \"from\" is required." ); + } + + if( toList != null ) + { + StringTokenizer t = new StringTokenizer( toList, ", ", false ); + + while( t.hasMoreTokens() ) + { + mailMessage.to( t.nextToken() ); + } + } + else + { + throw new BuildException( "Attribute \"toList\" is required." ); + } + + if( subject != null ) + { + mailMessage.setSubject( subject ); + } + + if( !files.isEmpty() ) + { + PrintStream out = mailMessage.getPrintStream(); + + for( Enumeration e = files.elements(); e.hasMoreElements(); ) + { + File file = ( File )e.nextElement(); + + if( file.exists() && file.canRead() ) + { + int bufsize = 1024; + int length; + byte[] buf = new byte[bufsize]; + if( includefilenames ) + { + String filename = file.getName(); + int filenamelength = filename.length(); + out.println( filename ); + for( int star = 0; star < filenamelength; star++ ) + { + out.print( '=' ); + } + out.println(); + } + BufferedInputStream in = null; + try + { + in = new BufferedInputStream( + new FileInputStream( file ), bufsize ); + while( ( length = in.read( buf, 0, bufsize ) ) != -1 ) + { + out.write( buf, 0, length ); + } + if( includefilenames ) + { + out.println(); + } + } + finally + { + if( in != null ) + { + try + { + in.close(); + } + catch( IOException ioe ) + {} + } + } + + } + else + { + throw new BuildException( "File \"" + file.getName() + + "\" does not exist or is not readable." ); + } + } + } + else if( message != null ) + { + PrintStream out = mailMessage.getPrintStream(); + out.print( message ); + } + else + { + throw new BuildException( "Attribute \"file\" or \"message\" is required." ); + } + + log( "Sending email" ); + mailMessage.sendAndClose(); + } + catch( IOException ioe ) + { + String err = "IO error sending mail " + ioe.toString(); + if( failOnError ) + { + throw new BuildException( err, ioe, location ); + } + else + { + log( err, Project.MSG_ERR ); + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sequential.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sequential.java new file mode 100644 index 000000000..59fa575be --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sequential.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; + + +/** + * Implements a single threaded task execution.

      + * + * + * + * @author Thomas Christen chr@active.ch + */ +public class Sequential extends Task + implements TaskContainer +{ + + /** + * Optional Vector holding the nested tasks + */ + private Vector nestedTasks = new Vector(); + + /** + * Add a nested task to Sequential.

      + * + * + * + * @param nestedTask Nested task to execute Sequential

      + * + * + */ + public void addTask( Task nestedTask ) + { + nestedTasks.addElement( nestedTask ); + } + + /** + * Execute all nestedTasks. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + for( Enumeration e = nestedTasks.elements(); e.hasMoreElements(); ) + { + Task nestedTask = ( Task )e.nextElement(); + nestedTask.perform(); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SignJar.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SignJar.java new file mode 100644 index 000000000..29ffddd1a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SignJar.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; + +/** + * Sign a archive. + * + * @author Peter Donald donaldp@apache.org + * + * @author Nick Fortescue + * nick@ox.compsoc.net + */ +public class SignJar extends Task +{ + + /** + * the filesets of the jars to sign + */ + protected Vector filesets = new Vector(); + + /** + * The alias of signer. + */ + protected String alias; + protected boolean internalsf; + + /** + * The name of the jar file. + */ + protected File jar; + protected String keypass; + + /** + * The name of keystore file. + */ + protected File keystore; + /** + * Whether to assume a jar which has an appropriate .SF file in is already + * signed. + */ + protected boolean lazy; + protected boolean sectionsonly; + protected File sigfile; + protected File signedjar; + + protected String storepass; + protected String storetype; + protected boolean verbose; + + public void setAlias( final String alias ) + { + this.alias = alias; + } + + public void setInternalsf( final boolean internalsf ) + { + this.internalsf = internalsf; + } + + public void setJar( final File jar ) + { + this.jar = jar; + } + + public void setKeypass( final String keypass ) + { + this.keypass = keypass; + } + + public void setKeystore( final File keystore ) + { + this.keystore = keystore; + } + + public void setLazy( final boolean lazy ) + { + this.lazy = lazy; + } + + public void setSectionsonly( final boolean sectionsonly ) + { + this.sectionsonly = sectionsonly; + } + + public void setSigfile( final File sigfile ) + { + this.sigfile = sigfile; + } + + public void setSignedjar( final File signedjar ) + { + this.signedjar = signedjar; + } + + public void setStorepass( final String storepass ) + { + this.storepass = storepass; + } + + public void setStoretype( final String storetype ) + { + this.storetype = storetype; + } + + public void setVerbose( final boolean verbose ) + { + this.verbose = verbose; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( final FileSet set ) + { + filesets.addElement( set ); + } + + + public void execute() + throws BuildException + { + if( null == jar && null == filesets ) + { + throw new BuildException( "jar must be set through jar attribute or nested filesets" ); + } + if( null != jar ) + { + doOneJar( jar, signedjar ); + return; + } + else + { + //Assume null != filesets + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] jarFiles = ds.getIncludedFiles(); + for( int j = 0; j < jarFiles.length; j++ ) + { + doOneJar( new File( fs.getDir( project ), jarFiles[j] ), null ); + } + } + } + } + + protected boolean isSigned( File file ) + { + final String SIG_START = "META-INF/"; + final String SIG_END = ".SF"; + + if( !file.exists() ) + { + return false; + } + ZipFile jarFile = null; + try + { + jarFile = new ZipFile( file ); + if( null == alias ) + { + Enumeration entries = jarFile.entries(); + while( entries.hasMoreElements() ) + { + String name = ( ( ZipEntry )entries.nextElement() ).getName(); + if( name.startsWith( SIG_START ) && name.endsWith( SIG_END ) ) + { + return true; + } + } + return false; + } + else + { + return jarFile.getEntry( SIG_START + alias.toUpperCase() + + SIG_END ) != null; + } + } + catch( IOException e ) + { + return false; + } + finally + { + if( jarFile != null ) + { + try + { + jarFile.close(); + } + catch( IOException e ) + {} + } + } + } + + protected boolean isUpToDate( File jarFile, File signedjarFile ) + { + if( null == jarFile ) + { + return false; + } + + if( null != signedjarFile ) + { + + if( !jarFile.exists() ) + return false; + if( !signedjarFile.exists() ) + return false; + if( jarFile.equals( signedjarFile ) ) + return false; + if( signedjarFile.lastModified() > jarFile.lastModified() ) + return true; + } + else + { + if( lazy ) + { + return isSigned( jarFile ); + } + } + + return false; + } + + private void doOneJar( File jarSource, File jarTarget ) + throws BuildException + { + if( project.getJavaVersion().equals( Project.JAVA_1_1 ) ) + { + throw new BuildException( "The signjar task is only available on JDK versions 1.2 or greater" ); + } + + if( null == alias ) + { + throw new BuildException( "alias attribute must be set" ); + } + + if( null == storepass ) + { + throw new BuildException( "storepass attribute must be set" ); + } + + if( isUpToDate( jarSource, jarTarget ) ) + return; + + final StringBuffer sb = new StringBuffer(); + + final ExecTask cmd = ( ExecTask )project.createTask( "exec" ); + cmd.setExecutable( "jarsigner" ); + + if( null != keystore ) + { + cmd.createArg().setValue( "-keystore" ); + cmd.createArg().setValue( keystore.toString() ); + } + + if( null != storepass ) + { + cmd.createArg().setValue( "-storepass" ); + cmd.createArg().setValue( storepass ); + } + + if( null != storetype ) + { + cmd.createArg().setValue( "-storetype" ); + cmd.createArg().setValue( storetype ); + } + + if( null != keypass ) + { + cmd.createArg().setValue( "-keypass" ); + cmd.createArg().setValue( keypass ); + } + + if( null != sigfile ) + { + cmd.createArg().setValue( "-sigfile" ); + cmd.createArg().setValue( sigfile.toString() ); + } + + if( null != jarTarget ) + { + cmd.createArg().setValue( "-signedjar" ); + cmd.createArg().setValue( jarTarget.toString() ); + } + + if( verbose ) + { + cmd.createArg().setValue( "-verbose" ); + } + + if( internalsf ) + { + cmd.createArg().setValue( "-internalsf" ); + } + + if( sectionsonly ) + { + cmd.createArg().setValue( "-sectionsonly" ); + } + + cmd.createArg().setValue( jarSource.toString() ); + + cmd.createArg().setValue( alias ); + + log( "Signing Jar : " + jarSource.getAbsolutePath() ); + cmd.setFailonerror( true ); + cmd.setTaskName( getTaskName() ); + cmd.execute(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sleep.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sleep.java new file mode 100644 index 000000000..2c8d81c86 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sleep.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * A task to sleep for a period of time + * + * @author steve_l@iseran.com steve loughran + * @created 01 May 2001 + */ + +public class Sleep extends Task +{ + /** + * failure flag + */ + private boolean failOnError = true; + + /** + * Description of the Field + */ + private int seconds = 0; + /** + * Description of the Field + */ + private int hours = 0; + /** + * Description of the Field + */ + private int minutes = 0; + /** + * Description of the Field + */ + private int milliseconds = 0; + + + /** + * Creates new instance + */ + public Sleep() { } + + + /** + * Sets the FailOnError attribute of the MimeMail object + * + * @param failOnError The new FailOnError value + */ + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + + /** + * Sets the Hours attribute of the Sleep object + * + * @param hours The new Hours value + */ + public void setHours( int hours ) + { + this.hours = hours; + } + + + /** + * Sets the Milliseconds attribute of the Sleep object + * + * @param milliseconds The new Milliseconds value + */ + public void setMilliseconds( int milliseconds ) + { + this.milliseconds = milliseconds; + } + + + /** + * Sets the Minutes attribute of the Sleep object + * + * @param minutes The new Minutes value + */ + public void setMinutes( int minutes ) + { + this.minutes = minutes; + } + + + /** + * Sets the Seconds attribute of the Sleep object + * + * @param seconds The new Seconds value + */ + public void setSeconds( int seconds ) + { + this.seconds = seconds; + } + + + /** + * sleep for a period of time + * + * @param millis time to sleep + */ + public void doSleep( long millis ) + { + try + { + Thread.currentThread().sleep( millis ); + } + catch( InterruptedException ie ) + { + } + } + + + /** + * Executes this build task. throws org.apache.tools.ant.BuildException if + * there is an error during task execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + validate(); + long sleepTime = getSleepTime(); + log( "sleeping for " + sleepTime + " milliseconds", + Project.MSG_VERBOSE ); + doSleep( sleepTime ); + } + catch( Exception e ) + { + if( failOnError ) + { + throw new BuildException( e ); + } + else + { + String text = e.toString(); + log( text, Project.MSG_ERR ); + } + } + } + + + /** + * verify parameters + * + * @throws BuildException if something is invalid + */ + public void validate() + throws BuildException + { + long sleepTime = getSleepTime(); + if( getSleepTime() < 0 ) + { + throw new BuildException( "Negative sleep periods are not supported" ); + } + } + + + /** + * return time to sleep + * + * @return sleep time. if below 0 then there is an error + */ + + private long getSleepTime() + { + return ( ( ( ( long )hours * 60 ) + minutes ) * 60 + seconds ) * 1000 + milliseconds; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/StreamPumper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/StreamPumper.java new file mode 100644 index 000000000..8e8a8e9ef --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/StreamPumper.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copies all data from an input stream to an output stream. + * + * @author thomas.haas@softwired-inc.com + */ +public class StreamPumper implements Runnable +{ + + // TODO: make SIZE and SLEEP instance variables. + // TODO: add a status flag to note if an error occured in run. + + private final static int SLEEP = 5; + private final static int SIZE = 128; + private InputStream is; + private OutputStream os; + + + /** + * Create a new stream pumper. + * + * @param is input stream to read data from + * @param os output stream to write data to. + */ + public StreamPumper( InputStream is, OutputStream os ) + { + this.is = is; + this.os = os; + } + + + /** + * Copies data from the input stream to the output stream. Terminates as + * soon as the input stream is closed or an error occurs. + */ + public void run() + { + final byte[] buf = new byte[SIZE]; + + int length; + try + { + while( ( length = is.read( buf ) ) > 0 ) + { + os.write( buf, 0, length ); + try + { + Thread.sleep( SLEEP ); + } + catch( InterruptedException e ) + {} + } + } + catch( IOException e ) + {} + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tar.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tar.java new file mode 100644 index 000000000..2f3fe3cdc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tar.java @@ -0,0 +1,481 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; +import org.apache.tools.tar.TarConstants; +import org.apache.tools.tar.TarEntry; +import org.apache.tools.tar.TarOutputStream; + +/** + * Creates a TAR archive. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Stefan Bodewig + * @author Magesh Umasankar + */ + +public class Tar extends MatchingTask +{ + + /** + * @deprecated Tar.WARN is deprecated and is replaced with + * Tar.TarLongFileMode.WARN + */ + public final static String WARN = "warn"; + /** + * @deprecated Tar.FAIL is deprecated and is replaced with + * Tar.TarLongFileMode.FAIL + */ + public final static String FAIL = "fail"; + /** + * @deprecated Tar.TRUNCATE is deprecated and is replaced with + * Tar.TarLongFileMode.TRUNCATE + */ + public final static String TRUNCATE = "truncate"; + /** + * @deprecated Tar.GNU is deprecated and is replaced with + * Tar.TarLongFileMode.GNU + */ + public final static String GNU = "gnu"; + /** + * @deprecated Tar.OMIT is deprecated and is replaced with + * Tar.TarLongFileMode.OMIT + */ + public final static String OMIT = "omit"; + + private TarLongFileMode longFileMode = new TarLongFileMode(); + + Vector filesets = new Vector(); + Vector fileSetFiles = new Vector(); + + /** + * Indicates whether the user has been warned about long files already. + */ + private boolean longWarningGiven = false; + File baseDir; + + File tarFile; + + /** + * This is the base directory to look in for things to tar. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * Set how to handle long files. Allowable values are truncate - paths are + * truncated to the maximum length fail - paths greater than the maximim + * cause a build exception warn - paths greater than the maximum cause a + * warning and GNU is used gnu - GNU extensions are used for any paths + * greater than the maximum. omit - paths greater than the maximum are + * omitted from the archive + * + * @param mode The new Longfile value + * @deprecated setLongFile(String) is deprecated and is replaced with + * setLongFile(Tar.TarLongFileMode) to make Ant's Introspection + * mechanism do the work and also to encapsulate operations on the mode + * in its own class. + */ + public void setLongfile( String mode ) + { + log( "DEPRECATED - The setLongfile(String) method has been deprecated." + + " Use setLongfile(Tar.TarLongFileMode) instead." ); + this.longFileMode = new TarLongFileMode(); + longFileMode.setValue( mode ); + } + + /** + * Set how to handle long files. Allowable values are truncate - paths are + * truncated to the maximum length fail - paths greater than the maximim + * cause a build exception warn - paths greater than the maximum cause a + * warning and GNU is used gnu - GNU extensions are used for any paths + * greater than the maximum. omit - paths greater than the maximum are + * omitted from the archive + * + * @param mode The new Longfile value + */ + public void setLongfile( TarLongFileMode mode ) + { + this.longFileMode = mode; + } + + + /** + * This is the name/location of where to create the tar file. + * + * @param tarFile The new Tarfile value + */ + public void setTarfile( File tarFile ) + { + this.tarFile = tarFile; + } + + public TarFileSet createTarFileSet() + { + TarFileSet fileset = new TarFileSet(); + filesets.addElement( fileset ); + return fileset; + } + + public void execute() + throws BuildException + { + if( tarFile == null ) + { + throw new BuildException( "tarfile attribute must be set!", + location ); + } + + if( tarFile.exists() && tarFile.isDirectory() ) + { + throw new BuildException( "tarfile is a directory!", + location ); + } + + if( tarFile.exists() && !tarFile.canWrite() ) + { + throw new BuildException( "Can not write to the specified tarfile!", + location ); + } + + if( baseDir != null ) + { + if( !baseDir.exists() ) + { + throw new BuildException( "basedir does not exist!", location ); + } + + // add the main fileset to the list of filesets to process. + TarFileSet mainFileSet = new TarFileSet( fileset ); + mainFileSet.setDir( baseDir ); + filesets.addElement( mainFileSet ); + } + + if( filesets.size() == 0 ) + { + throw new BuildException( "You must supply either a basdir attribute or some nested filesets.", + location ); + } + + // check if tr is out of date with respect to each + // fileset + boolean upToDate = true; + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + TarFileSet fs = ( TarFileSet )e.nextElement(); + String[] files = fs.getFiles( project ); + + if( !archiveIsUpToDate( files ) ) + { + upToDate = false; + } + + for( int i = 0; i < files.length; ++i ) + { + if( tarFile.equals( new File( fs.getDir( project ), files[i] ) ) ) + { + throw new BuildException( "A tar file cannot include itself", location ); + } + } + } + + if( upToDate ) + { + log( "Nothing to do: " + tarFile.getAbsolutePath() + " is up to date.", + Project.MSG_INFO ); + return; + } + + log( "Building tar: " + tarFile.getAbsolutePath(), Project.MSG_INFO ); + + TarOutputStream tOut = null; + try + { + tOut = new TarOutputStream( new FileOutputStream( tarFile ) ); + tOut.setDebug( true ); + if( longFileMode.isTruncateMode() ) + { + tOut.setLongFileMode( TarOutputStream.LONGFILE_TRUNCATE ); + } + else if( longFileMode.isFailMode() || + longFileMode.isOmitMode() ) + { + tOut.setLongFileMode( TarOutputStream.LONGFILE_ERROR ); + } + else + { + // warn or GNU + tOut.setLongFileMode( TarOutputStream.LONGFILE_GNU ); + } + + longWarningGiven = false; + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + TarFileSet fs = ( TarFileSet )e.nextElement(); + String[] files = fs.getFiles( project ); + for( int i = 0; i < files.length; i++ ) + { + File f = new File( fs.getDir( project ), files[i] ); + String name = files[i].replace( File.separatorChar, '/' ); + tarFile( f, tOut, name, fs ); + } + } + } + catch( IOException ioe ) + { + String msg = "Problem creating TAR: " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( tOut != null ) + { + try + { + // close up + tOut.close(); + } + catch( IOException e ) + {} + } + } + } + + protected boolean archiveIsUpToDate( String[] files ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + MergingMapper mm = new MergingMapper(); + mm.setTo( tarFile.getAbsolutePath() ); + return sfs.restrict( files, baseDir, null, mm ).length == 0; + } + + protected void tarFile( File file, TarOutputStream tOut, String vPath, + TarFileSet tarFileSet ) + throws IOException + { + FileInputStream fIn = null; + + // don't add "" to the archive + if( vPath.length() <= 0 ) + { + return; + } + + if( file.isDirectory() && !vPath.endsWith( "/" ) ) + { + vPath += "/"; + } + + try + { + if( vPath.length() >= TarConstants.NAMELEN ) + { + if( longFileMode.isOmitMode() ) + { + log( "Omitting: " + vPath, Project.MSG_INFO ); + return; + } + else if( longFileMode.isWarnMode() ) + { + log( "Entry: " + vPath + " longer than " + + TarConstants.NAMELEN + " characters.", Project.MSG_WARN ); + if( !longWarningGiven ) + { + log( "Resulting tar file can only be processed successfully" + + " by GNU compatible tar commands", Project.MSG_WARN ); + longWarningGiven = true; + } + } + else if( longFileMode.isFailMode() ) + { + throw new BuildException( + "Entry: " + vPath + " longer than " + + TarConstants.NAMELEN + "characters.", location ); + } + } + + TarEntry te = new TarEntry( vPath ); + te.setModTime( file.lastModified() ); + if( !file.isDirectory() ) + { + te.setSize( file.length() ); + te.setMode( tarFileSet.getMode() ); + } + te.setUserName( tarFileSet.getUserName() ); + te.setGroupName( tarFileSet.getGroup() ); + + tOut.putNextEntry( te ); + + if( !file.isDirectory() ) + { + fIn = new FileInputStream( file ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + tOut.write( buffer, 0, count ); + count = fIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + + tOut.closeEntry(); + } + finally + { + if( fIn != null ) + fIn.close(); + } + } + + public static class TarFileSet extends FileSet + { + private String[] files = null; + + private int mode = 0100644; + + private String userName = ""; + private String groupName = ""; + + + public TarFileSet( FileSet fileset ) + { + super( fileset ); + } + + public TarFileSet() + { + super(); + } + + public void setGroup( String groupName ) + { + this.groupName = groupName; + } + + public void setMode( String octalString ) + { + this.mode = 0100000 | Integer.parseInt( octalString, 8 ); + } + + public void setUserName( String userName ) + { + this.userName = userName; + } + + /** + * Get a list of files and directories specified in the fileset. + * + * @param p Description of Parameter + * @return a list of file and directory names, relative to the baseDir + * for the project. + */ + public String[] getFiles( Project p ) + { + if( files == null ) + { + DirectoryScanner ds = getDirectoryScanner( p ); + String[] directories = ds.getIncludedDirectories(); + String[] filesPerSe = ds.getIncludedFiles(); + files = new String[directories.length + filesPerSe.length]; + System.arraycopy( directories, 0, files, 0, directories.length ); + System.arraycopy( filesPerSe, 0, files, directories.length, + filesPerSe.length ); + } + + return files; + } + + public String getGroup() + { + return groupName; + } + + public int getMode() + { + return mode; + } + + public String getUserName() + { + return userName; + } + + } + + /** + * Valid Modes for LongFile attribute to Tar Task + * + * @author Magesh Umasankar + */ + public static class TarLongFileMode extends EnumeratedAttribute + { + + // permissable values for longfile attribute + public final static String WARN = "warn"; + public final static String FAIL = "fail"; + public final static String TRUNCATE = "truncate"; + public final static String GNU = "gnu"; + public final static String OMIT = "omit"; + + private final String[] validModes = {WARN, FAIL, TRUNCATE, GNU, OMIT}; + + public TarLongFileMode() + { + super(); + setValue( WARN ); + } + + public String[] getValues() + { + return validModes; + } + + public boolean isFailMode() + { + return FAIL.equalsIgnoreCase( getValue() ); + } + + public boolean isGnuMode() + { + return GNU.equalsIgnoreCase( getValue() ); + } + + public boolean isOmitMode() + { + return OMIT.equalsIgnoreCase( getValue() ); + } + + public boolean isTruncateMode() + { + return TRUNCATE.equalsIgnoreCase( getValue() ); + } + + public boolean isWarnMode() + { + return WARN.equalsIgnoreCase( getValue() ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/TaskOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/TaskOutputStream.java new file mode 100644 index 000000000..5af5c9934 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/TaskOutputStream.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Task; + +/** + * Redirects text written to a stream thru the standard ant logging mechanism. + * This class is useful for integrating with tools that write to System.out and + * System.err. For example, the following will cause all text written to + * System.out to be logged with "info" priority:

      System.setOut(new PrintStream(new TaskOutputStream(project, Project.MSG_INFO)));
      + * + * @author James Duncan Davidson (duncan@x180.com) + * @deprecated use LogOutputStream instead. + */ + +public class TaskOutputStream extends OutputStream +{ + private StringBuffer line; + private int msgOutputLevel; + + private Task task; + + /** + * Constructs a new JavacOutputStream with the given project as the output + * source for messages. + * + * @param task Description of Parameter + * @param msgOutputLevel Description of Parameter + */ + + TaskOutputStream( Task task, int msgOutputLevel ) + { + this.task = task; + this.msgOutputLevel = msgOutputLevel; + + line = new StringBuffer(); + } + + /** + * Write a character to the output stream. This method looks to make sure + * that there isn't an error being reported and will flush each line of + * input out to the project's log stream. + * + * @param c Description of Parameter + * @exception IOException Description of Exception + */ + + public void write( int c ) + throws IOException + { + char cc = ( char )c; + if( cc == '\r' || cc == '\n' ) + { + // line feed + if( line.length() > 0 ) + { + processLine(); + } + } + else + { + line.append( cc ); + } + } + + /** + * Processes a line of input and determines if an error occured. + */ + + private void processLine() + { + String s = line.toString(); + task.log( s, msgOutputLevel ); + line = new StringBuffer(); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Taskdef.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Taskdef.java new file mode 100644 index 000000000..609a2771d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Taskdef.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Define a new task. + * + * @author Stefan Bodewig + */ +public class Taskdef extends Definer +{ + protected void addDefinition( String name, Class c ) + throws BuildException + { + project.addTaskDefinition( name, c ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Touch.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Touch.java new file mode 100644 index 000000000..358597118 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Touch.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Locale; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.FileUtils; + +/** + * Touch a file and/or fileset(s) -- corresponds to the Unix touch command.

      + * + * If the file to touch doesn't exist, an empty one is created.

      + * + * Note: Setting the modification time of files is not supported in JDK 1.1.

      + * + * @author Stefan Bodewig + * @author Michael J. Sikorsky + * @author Robert Shaw + */ +public class Touch extends Task +{// required + private long millis = -1; + private Vector filesets = new Vector(); + private String dateTime; + + private File file; + private FileUtils fileUtils; + + public Touch() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Date in the format MM/DD/YYYY HH:MM AM_PM. + * + * @param dateTime The new Datetime value + */ + public void setDatetime( String dateTime ) + { + this.dateTime = dateTime; + } + + /** + * Sets a single source file to touch. If the file does not exist an empty + * file will be created. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Milliseconds since 01/01/1970 00:00 am. + * + * @param millis The new Millis value + */ + public void setMillis( long millis ) + { + this.millis = millis; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Execute the touch operation. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( file == null && filesets.size() == 0 ) + { + throw + new BuildException( "Specify at least one source - a file or a fileset." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( "Use a fileset to touch directories." ); + } + + if( dateTime != null ) + { + DateFormat df = DateFormat.getDateTimeInstance( DateFormat.SHORT, + DateFormat.SHORT, + Locale.US ); + try + { + setMillis( df.parse( dateTime ).getTime() ); + if( millis < 0 ) + { + throw new BuildException( "Date of " + dateTime + + " results in negative milliseconds value relative to epoch (January 1, 1970, 00:00:00 GMT)." ); + } + } + catch( ParseException pe ) + { + throw new BuildException( pe.getMessage(), pe, location ); + } + } + + touch(); + } + + /** + * Does the actual work. Entry point for Untar and Expand as well. + * + * @exception BuildException Description of Exception + */ + protected void touch() + throws BuildException + { + if( file != null ) + { + if( !file.exists() ) + { + log( "Creating " + file, Project.MSG_INFO ); + try + { + FileOutputStream fos = new FileOutputStream( file ); + fos.write( new byte[0] ); + fos.close(); + } + catch( IOException ioe ) + { + throw new BuildException( "Could not create " + file, ioe, + location ); + } + } + } + + if( millis >= 0 && project.getJavaVersion() == Project.JAVA_1_1 ) + { + log( "modification time of files cannot be set in JDK 1.1", + Project.MSG_WARN ); + return; + } + + boolean resetMillis = false; + if( millis < 0 ) + { + resetMillis = true; + millis = System.currentTimeMillis(); + } + + if( file != null ) + { + touch( file ); + } + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + String[] srcDirs = ds.getIncludedDirectories(); + + for( int j = 0; j < srcFiles.length; j++ ) + { + touch( new File( fromDir, srcFiles[j] ) ); + } + + for( int j = 0; j < srcDirs.length; j++ ) + { + touch( new File( fromDir, srcDirs[j] ) ); + } + } + + if( resetMillis ) + { + millis = -1; + } + } + + protected void touch( File file ) + throws BuildException + { + if( !file.canWrite() ) + { + throw new BuildException( "Can not change modification date of read-only file " + file ); + } + + if( project.getJavaVersion() == Project.JAVA_1_1 ) + { + return; + } + + fileUtils.setFileLastModified( file, millis ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Transform.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Transform.java new file mode 100644 index 000000000..02ecfae96 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Transform.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +/** + * Has been merged into ExecuteOn, empty class for backwards compatibility. + * + * @author Stefan Bodewig + */ +public class Transform extends ExecuteOn +{ +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tstamp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tstamp.java new file mode 100644 index 000000000..276d964e0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tstamp.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; +import java.util.TimeZone; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Sets TSTAMP, DSTAMP and TODAY + * + * @author costin@dnt.ro + * @author stefano@apache.org + * @author roxspring@yahoo.com + * @author conor@cognet.com.au + * @author Magesh Umasankar + */ +public class Tstamp extends Task +{ + + private Vector customFormats = new Vector(); + private String prefix = ""; + + public void setPrefix( String prefix ) + { + this.prefix = prefix; + if( !this.prefix.endsWith( "." ) ) + { + this.prefix += "."; + } + } + + public CustomFormat createFormat() + { + CustomFormat cts = new CustomFormat( prefix ); + customFormats.addElement( cts ); + return cts; + } + + public void execute() + throws BuildException + { + try + { + Date d = new Date(); + + SimpleDateFormat dstamp = new SimpleDateFormat( "yyyyMMdd" ); + project.setNewProperty( prefix + "DSTAMP", dstamp.format( d ) ); + + SimpleDateFormat tstamp = new SimpleDateFormat( "HHmm" ); + project.setNewProperty( prefix + "TSTAMP", tstamp.format( d ) ); + + SimpleDateFormat today = new SimpleDateFormat( "MMMM d yyyy", Locale.US ); + project.setNewProperty( prefix + "TODAY", today.format( d ) ); + + Enumeration i = customFormats.elements(); + while( i.hasMoreElements() ) + { + CustomFormat cts = ( CustomFormat )i.nextElement(); + cts.execute( project, d, location ); + } + + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + public static class Unit extends EnumeratedAttribute + { + + private final static String MILLISECOND = "millisecond"; + private final static String SECOND = "second"; + private final static String MINUTE = "minute"; + private final static String HOUR = "hour"; + private final static String DAY = "day"; + private final static String WEEK = "week"; + private final static String MONTH = "month"; + private final static String YEAR = "year"; + + private final static String[] units = { + MILLISECOND, + SECOND, + MINUTE, + HOUR, + DAY, + WEEK, + MONTH, + YEAR + }; + + private Hashtable calendarFields = new Hashtable(); + + public Unit() + { + calendarFields.put( MILLISECOND, + new Integer( Calendar.MILLISECOND ) ); + calendarFields.put( SECOND, new Integer( Calendar.SECOND ) ); + calendarFields.put( MINUTE, new Integer( Calendar.MINUTE ) ); + calendarFields.put( HOUR, new Integer( Calendar.HOUR_OF_DAY ) ); + calendarFields.put( DAY, new Integer( Calendar.DATE ) ); + calendarFields.put( WEEK, new Integer( Calendar.WEEK_OF_YEAR ) ); + calendarFields.put( MONTH, new Integer( Calendar.MONTH ) ); + calendarFields.put( YEAR, new Integer( Calendar.YEAR ) ); + } + + public int getCalendarField() + { + String key = getValue().toLowerCase(); + Integer i = ( Integer )calendarFields.get( key ); + return i.intValue(); + } + + public String[] getValues() + { + return units; + } + } + + public class CustomFormat + { + private int offset = 0; + private int field = Calendar.DATE; + private String prefix = ""; + private String country; + private String language; + private String pattern; + private String propertyName; + private TimeZone timeZone; + private String variant; + + public CustomFormat( String prefix ) + { + this.prefix = prefix; + } + + public void setLocale( String locale ) + { + StringTokenizer st = new StringTokenizer( locale, " \t\n\r\f," ); + try + { + language = st.nextToken(); + if( st.hasMoreElements() ) + { + country = st.nextToken(); + if( st.hasMoreElements() ) + { + country = st.nextToken(); + if( st.hasMoreElements() ) + { + throw new BuildException( "bad locale format", getLocation() ); + } + } + } + else + { + country = ""; + } + } + catch( NoSuchElementException e ) + { + throw new BuildException( "bad locale format", e, getLocation() ); + } + } + + public void setOffset( int offset ) + { + this.offset = offset; + } + + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + public void setProperty( String propertyName ) + { + this.propertyName = prefix + propertyName; + } + + public void setTimezone( String id ) + { + timeZone = TimeZone.getTimeZone( id ); + } + + /** + * @param unit The new Unit value + * @deprecated setUnit(String) is deprecated and is replaced with + * setUnit(Tstamp.Unit) to make Ant's Introspection mechanism do + * the work and also to encapsulate operations on the unit in its + * own class. + */ + public void setUnit( String unit ) + { + log( "DEPRECATED - The setUnit(String) method has been deprecated." + + " Use setUnit(Tstamp.Unit) instead." ); + Unit u = new Unit(); + u.setValue( unit ); + field = u.getCalendarField(); + } + + public void setUnit( Unit unit ) + { + field = unit.getCalendarField(); + } + + public void execute( Project project, Date date, Location location ) + { + if( propertyName == null ) + { + throw new BuildException( "property attribute must be provided", location ); + } + + if( pattern == null ) + { + throw new BuildException( "pattern attribute must be provided", location ); + } + + SimpleDateFormat sdf; + if( language == null ) + { + sdf = new SimpleDateFormat( pattern ); + } + else if( variant == null ) + { + sdf = new SimpleDateFormat( pattern, new Locale( language, country ) ); + } + else + { + sdf = new SimpleDateFormat( pattern, new Locale( language, country, variant ) ); + } + if( offset != 0 ) + { + Calendar calendar = Calendar.getInstance(); + calendar.setTime( date ); + calendar.add( field, offset ); + date = calendar.getTime(); + } + if( timeZone != null ) + { + sdf.setTimeZone( timeZone ); + } + project.setNewProperty( propertyName, sdf.format( date ) ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Typedef.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Typedef.java new file mode 100644 index 000000000..2bb6ac880 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Typedef.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Define a new data type. + * + * @author Stefan Bodewig + */ +public class Typedef extends Definer +{ + protected void addDefinition( String name, Class c ) + throws BuildException + { + project.addDataTypeDefinition( name, c ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Unpack.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Unpack.java new file mode 100644 index 000000000..1e641b251 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Unpack.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Abstract Base class for unpack tasks. + * + * @author Magesh Umasankar + */ + +public abstract class Unpack extends Task +{ + protected File dest; + + protected File source; + + public void setDest( String dest ) + { + this.dest = project.resolveFile( dest ); + } + + public void setSrc( String src ) + { + source = project.resolveFile( src ); + } + + public void execute() + throws BuildException + { + validate(); + extract(); + } + + protected abstract String getDefaultExtension(); + + protected abstract void extract(); + + private void createDestFile( String defaultExtension ) + { + String sourceName = source.getName(); + int len = sourceName.length(); + if( defaultExtension != null + && len > defaultExtension.length() + && defaultExtension.equalsIgnoreCase( sourceName.substring( len - defaultExtension.length() ) ) ) + { + dest = new File( dest, sourceName.substring( 0, + len - defaultExtension.length() ) ); + } + else + { + dest = new File( dest, sourceName ); + } + } + + private void validate() + throws BuildException + { + if( source == null ) + { + throw new BuildException( "No Src for gunzip specified", location ); + } + + if( !source.exists() ) + { + throw new BuildException( "Src doesn't exist", location ); + } + + if( source.isDirectory() ) + { + throw new BuildException( "Cannot expand a directory", location ); + } + + if( dest == null ) + { + dest = new File( source.getParent() ); + } + + if( dest.isDirectory() ) + { + String defaultExtension = getDefaultExtension(); + createDestFile( defaultExtension ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Untar.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Untar.java new file mode 100644 index 000000000..99fba8759 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Untar.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Date; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.tar.TarEntry; +import org.apache.tools.tar.TarInputStream; + +/** + * Untar a file. Heavily based on the Expand task. + * + * @author Stefan Bodewig + * @author Magesh Umasankar + */ +public class Untar extends Expand +{ + + protected void expandFile( FileUtils fileUtils, File srcF, File dir ) + { + TarInputStream tis = null; + try + { + log( "Expanding: " + srcF + " into " + dir, Project.MSG_INFO ); + + tis = new TarInputStream( new FileInputStream( srcF ) ); + TarEntry te = null; + + while( ( te = tis.getNextEntry() ) != null ) + { + extractFile( fileUtils, srcF, dir, tis, + te.getName(), + te.getModTime(), te.isDirectory() ); + } + log( "expand complete", Project.MSG_VERBOSE ); + + } + catch( IOException ioe ) + { + throw new BuildException( "Error while expanding " + srcF.getPath(), + ioe, location ); + } + finally + { + if( tis != null ) + { + try + { + tis.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/UpToDate.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/UpToDate.java new file mode 100644 index 000000000..968131b0a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/UpToDate.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Will set the given property if the specified target has a timestamp greater + * than all of the source files. + * + * @author William Ferguson + * williamf@mincom.com + * @author Hiroaki Nakamura + * hnakamur@mc.neweb.ne.jp + * @author Stefan Bodewig + */ + +public class UpToDate extends MatchingTask implements Condition +{ + private Vector sourceFileSets = new Vector(); + + protected Mapper mapperElement = null; + + private String _property; + private File _targetFile; + private String _value; + + /** + * The property to set if the target file is more up to date than each of + * the source files. + * + * @param property the name of the property to set if Target is up to date. + */ + public void setProperty( String property ) + { + _property = property; + } + + /** + * The file which must be more up to date than each of the source files if + * the property is to be set. + * + * @param file the file which we are checking against. + */ + public void setTargetFile( File file ) + { + _targetFile = file; + } + + /** + * The value to set the named property to if the target file is more up to + * date than each of the source files. Defaults to 'true'. + * + * @param value the value to set the property to if Target is up to date + */ + public void setValue( String value ) + { + _value = value; + } + + /** + * Nested <srcfiles> element. + * + * @param fs The feature to be added to the Srcfiles attribute + */ + public void addSrcfiles( FileSet fs ) + { + sourceFileSets.addElement( fs ); + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Evaluate all target and source files, see if the targets are up-to-date. + * + * @return Description of the Returned Value + */ + public boolean eval() + { + if( sourceFileSets.size() == 0 ) + { + throw new BuildException( "At least one element must be set" ); + } + + if( _targetFile == null && mapperElement == null ) + { + throw new BuildException( "The targetfile attribute or a nested mapper element must be set" ); + } + + // if not there then it can't be up to date + if( _targetFile != null && !_targetFile.exists() ) + return false; + + Enumeration enum = sourceFileSets.elements(); + boolean upToDate = true; + while( upToDate && enum.hasMoreElements() ) + { + FileSet fs = ( FileSet )enum.nextElement(); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + upToDate = upToDate && scanDir( fs.getDir( project ), + ds.getIncludedFiles() ); + } + return upToDate; + } + + + /** + * Sets property to true if target files have a more recent timestamp than + * each of the corresponding source files. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + boolean upToDate = eval(); + if( upToDate ) + { + this.project.setProperty( _property, this.getValue() ); + if( mapperElement == null ) + { + log( "File \"" + _targetFile.getAbsolutePath() + "\" is up to date.", + Project.MSG_VERBOSE ); + } + else + { + log( "All target files have been up to date.", + Project.MSG_VERBOSE ); + } + } + } + + protected boolean scanDir( File srcDir, String files[] ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + FileNameMapper mapper = null; + File dir = srcDir; + if( mapperElement == null ) + { + MergingMapper mm = new MergingMapper(); + mm.setTo( _targetFile.getAbsolutePath() ); + mapper = mm; + dir = null; + } + else + { + mapper = mapperElement.getImplementation(); + } + return sfs.restrict( files, srcDir, dir, mapper ).length == 0; + } + + /** + * Returns the value, or "true" if a specific value wasn't provided. + * + * @return The Value value + */ + private String getValue() + { + return ( _value != null ) ? _value : "true"; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/WaitFor.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/WaitFor.java new file mode 100644 index 000000000..9f87abb1d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/WaitFor.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.taskdefs.condition.ConditionBase; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Wait for an external event to occur. Wait for an external process to start or + * to complete some task. This is useful with the parallel task to + * syncronize the execution of tests with server startup. The following + * attributes can be specified on a waitfor task: + *
        + *
      • maxwait - maximum length of time to wait before giving up
      • + *
      • maxwaitunit - The unit to be used to interpret maxwait attribute
      • + * + *
      • checkevery - amount of time to sleep between each check
      • + *
      • checkeveryunit - The unit to be used to interpret checkevery attribute + *
      • + *
      • timeoutproperty - name of a property to set if maxwait has been + * exceeded.
      • + *
      + * The maxwaitunit and checkeveryunit are allowed to have the following values: + * millesond, second, minute, hour, day and week. The default is millisecond. + * + * @author Denis Hennessy + * @author Magesh Umasankar + */ + +public class WaitFor extends ConditionBase +{ + private long maxWaitMillis = 1000l * 60l * 3l;// default max wait time + private long maxWaitMultiplier = 1l; + private long checkEveryMillis = 500l; + private long checkEveryMultiplier = 1l; + private String timeoutProperty; + + /** + * Set the time between each check + * + * @param time The new CheckEvery value + */ + public void setCheckEvery( long time ) + { + checkEveryMillis = time; + } + + /** + * Set the check every time unit + * + * @param unit The new CheckEveryUnit value + */ + public void setCheckEveryUnit( Unit unit ) + { + checkEveryMultiplier = unit.getMultiplier(); + } + + /** + * Set the maximum length of time to wait + * + * @param time The new MaxWait value + */ + public void setMaxWait( long time ) + { + maxWaitMillis = time; + } + + /** + * Set the max wait time unit + * + * @param unit The new MaxWaitUnit value + */ + public void setMaxWaitUnit( Unit unit ) + { + maxWaitMultiplier = unit.getMultiplier(); + } + + /** + * Set the timeout property. + * + * @param p The new TimeoutProperty value + */ + public void setTimeoutProperty( String p ) + { + timeoutProperty = p; + } + + /** + * Check repeatedly for the specified conditions until they become true or + * the timeout expires. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + Condition c = ( Condition )getConditions().nextElement(); + + maxWaitMillis *= maxWaitMultiplier; + checkEveryMillis *= checkEveryMultiplier; + long start = System.currentTimeMillis(); + long end = start + maxWaitMillis; + + while( System.currentTimeMillis() < end ) + { + if( c.eval() ) + { + return; + } + try + { + Thread.sleep( checkEveryMillis ); + } + catch( InterruptedException e ) + { + } + } + + if( timeoutProperty != null ) + { + project.setNewProperty( timeoutProperty, "true" ); + } + } + + public static class Unit extends EnumeratedAttribute + { + + private final static String MILLISECOND = "millisecond"; + private final static String SECOND = "second"; + private final static String MINUTE = "minute"; + private final static String HOUR = "hour"; + private final static String DAY = "day"; + private final static String WEEK = "week"; + + private final static String[] units = { + MILLISECOND, SECOND, MINUTE, HOUR, DAY, WEEK + }; + + private Hashtable timeTable = new Hashtable(); + + public Unit() + { + timeTable.put( MILLISECOND, new Long( 1l ) ); + timeTable.put( SECOND, new Long( 1000l ) ); + timeTable.put( MINUTE, new Long( 1000l * 60l ) ); + timeTable.put( HOUR, new Long( 1000l * 60l * 60l ) ); + timeTable.put( DAY, new Long( 1000l * 60l * 60l * 24l ) ); + timeTable.put( WEEK, new Long( 1000l * 60l * 60l * 24l * 7l ) ); + } + + public long getMultiplier() + { + String key = getValue().toLowerCase(); + Long l = ( Long )timeTable.get( key ); + return l.longValue(); + } + + public String[] getValues() + { + return units; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/War.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/War.java new file mode 100644 index 000000000..ff2bf9c48 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/War.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + + +/** + * Creates a WAR archive. + * + * @author Stefan Bodewig + */ +public class War extends Jar +{ + + private File deploymentDescriptor; + private boolean descriptorAdded; + + public War() + { + super(); + archiveType = "war"; + emptyBehavior = "create"; + } + + public void setWarfile( File warFile ) + { + log( "DEPRECATED - The warfile attribute is deprecated. Use file attribute instead." ); + setFile( warFile ); + } + + public void setWebxml( File descr ) + { + deploymentDescriptor = descr; + if( !deploymentDescriptor.exists() ) + throw new BuildException( "Deployment descriptor: " + deploymentDescriptor + " does not exist." ); + + // Create a ZipFileSet for this file, and pass it up. + ZipFileSet fs = new ZipFileSet(); + fs.setDir( new File( deploymentDescriptor.getParent() ) ); + fs.setIncludes( deploymentDescriptor.getName() ); + fs.setFullpath( "WEB-INF/web.xml" ); + super.addFileset( fs ); + } + + public void addClasses( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/classes/" ); + super.addFileset( fs ); + } + + public void addLib( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/lib/" ); + super.addFileset( fs ); + } + + public void addWebinf( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/" ); + super.addFileset( fs ); + } + + /** + * Make sure we don't think we already have a web.xml next time this task + * gets executed. + */ + protected void cleanUp() + { + descriptorAdded = false; + super.cleanUp(); + } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + // If no webxml file is specified, it's an error. + if( deploymentDescriptor == null && !isInUpdateMode() ) + { + throw new BuildException( "webxml attribute is required", location ); + } + + super.initZipOutputStream( zOut ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is WEB-INF/web.xml, we warn if it's not the + // one specified in the "webxml" attribute - or if it's being added twice, + // meaning the same file is specified by the "webxml" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "WEB-INF/web.xml" ) ) + { + if( deploymentDescriptor == null || !deploymentDescriptor.equals( file ) || descriptorAdded ) + { + log( "Warning: selected " + archiveType + " files include a WEB-INF/web.xml which will be ignored " + + "(please use webxml attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + descriptorAdded = true; + } + } + else + { + super.zipFile( file, zOut, vPath ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLiaison.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLiaison.java new file mode 100644 index 000000000..a4d01bd8e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLiaison.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; + +/** + * Proxy interface for XSLT processors. + * + * @author Sam Ruby + * @author Stephane Bailliez + * @see XSLTProcess + */ +public interface XSLTLiaison +{ + + /** + * the file protocol prefix for systemid. This file protocol must be + * appended to an absolute path. Typically: FILE_PROTOCOL_PREFIX + + * file.getAbsolutePath() This is not correct in specification terms + * since an absolute url in Unix is file:// + file.getAbsolutePath() while + * it is file:/// + file.getAbsolutePath() under Windows. Whatever, it + * should not be a problem to put file:/// in every case since most parsers + * for now incorrectly makes no difference between it.. and users also have + * problem with that :) + */ + String FILE_PROTOCOL_PREFIX = "file:///"; + + /** + * set the stylesheet to use for the transformation. + * + * @param stylesheet the stylesheet to be used for transformation. + * @exception Exception Description of Exception + */ + void setStylesheet( File stylesheet ) + throws Exception; + + /** + * Add a parameter to be set during the XSL transformation. + * + * @param name the parameter name. + * @param expression the parameter value as an expression string. + * @throws Exception thrown if any problems happens. + */ + void addParam( String name, String expression ) + throws Exception; + + /** + * set the output type to use for the transformation. Only "xml" (the + * default) is guaranteed to work for all parsers. Xalan2 also supports + * "html" and "text". + * + * @param type the output method to use + * @exception Exception Description of Exception + */ + void setOutputtype( String type ) + throws Exception; + + /** + * Perform the transformation of a file into another. + * + * @param infile the input file, probably an XML one. :-) + * @param outfile the output file resulting from the transformation + * @see #setStylesheet(File) + * @throws Exception thrown if any problems happens. + */ + void transform( File infile, File outfile ) + throws Exception; + +}//-- XSLTLiaison diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLogger.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLogger.java new file mode 100644 index 000000000..4579d4085 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLogger.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +public interface XSLTLogger +{ + /** + * Log a message. + * + * @param msg Description of Parameter + */ + void log( String msg ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java new file mode 100644 index 000000000..ae2051ae0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java @@ -0,0 +1,13 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +public interface XSLTLoggerAware +{ + void setLogger( XSLTLogger l ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java new file mode 100644 index 000000000..13a17d2b2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java @@ -0,0 +1,581 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + + +/** + * A Task to process via XSLT a set of XML documents. This is useful for + * building views of XML based documentation. arguments: + *
        + *
      • basedir + *
      • destdir + *
      • style + *
      • includes + *
      • excludes + *
      + * Of these arguments, the sourcedir and destdir are required.

      + * + * This task will recursively scan the sourcedir and destdir looking for XML + * documents to process via XSLT. Any other files, such as images, or html files + * in the source directory will be copied into the destination directory. + * + * @author Keith Visco + * @author Sam Ruby + * @author Russell Gold + * @author Stefan Bodewig + */ + +public class XSLTProcess extends MatchingTask implements XSLTLogger +{ + + private File destDir = null; + + private File baseDir = null; + + private String xslFile = null; + + private String targetExtension = ".html"; + private Vector params = new Vector(); + + private File inFile = null; + + private File outFile = null; + private Path classpath = null; + private boolean stylesheetLoaded = false; + + private boolean force = false; + + private String outputtype = null; + + private FileUtils fileUtils; + private XSLTLiaison liaison; + + private String processor; + + /** + * Creates a new XSLTProcess Task. + */ + public XSLTProcess() + { + fileUtils = FileUtils.newFileUtils(); + }//-- setForce + + /** + * Set the base directory. + * + * @param dir The new Basedir value + */ + public void setBasedir( File dir ) + { + baseDir = dir; + } + + /** + * Set the classpath to load the Processor through (attribute). + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + createClasspath().append( classpath ); + } + + /** + * Set the classpath to load the Processor through via reference + * (attribute). + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + }//-- setSourceDir + + /** + * Set the destination directory into which the XSL result files should be + * copied to + * + * @param dir The new Destdir value + */ + public void setDestdir( File dir ) + { + destDir = dir; + }//-- setDestDir + + /** + * Set the desired file extension to be used for the target + * + * @param name the extension to use + */ + public void setExtension( String name ) + { + targetExtension = name; + }//-- execute + + /** + * Set whether to check dependencies, or always generate. + * + * @param force The new Force value + */ + public void setForce( boolean force ) + { + this.force = force; + } + + /** + * Sets an input xml file to be styled + * + * @param inFile The new In value + */ + public void setIn( File inFile ) + { + this.inFile = inFile; + } + + /** + * Sets an out file + * + * @param outFile The new Out value + */ + public void setOut( File outFile ) + { + this.outFile = outFile; + } + + /** + * Set the output type to use for the transformation. Only "xml" (the + * default) is guaranteed to work for all parsers. Xalan2 also supports + * "html" and "text". + * + * @param type the output method to use + */ + public void setOutputtype( String type ) + { + this.outputtype = type; + } + + + public void setProcessor( String processor ) + { + this.processor = processor; + }//-- setDestDir + + /** + * Sets the file to use for styling relative to the base directory of this + * task. + * + * @param xslFile The new Style value + */ + public void setStyle( String xslFile ) + { + this.xslFile = xslFile; + } + + /** + * Set the classpath to load the Processor through (nested element). + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + public Param createParam() + { + Param p = new Param(); + params.addElement( p ); + return p; + }//-- XSLTProcess + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + + public void execute() + throws BuildException + { + DirectoryScanner scanner; + String[] list; + String[] dirs; + + if( xslFile == null ) + { + throw new BuildException( "no stylesheet specified", location ); + } + + if( baseDir == null ) + { + baseDir = project.resolveFile( "." ); + } + + liaison = getLiaison(); + + // check if liaison wants to log errors using us as logger + if( liaison instanceof XSLTLoggerAware ) + { + ( ( XSLTLoggerAware )liaison ).setLogger( this ); + } + + log( "Using " + liaison.getClass().toString(), Project.MSG_VERBOSE ); + + File stylesheet = project.resolveFile( xslFile ); + if( !stylesheet.exists() ) + { + stylesheet = fileUtils.resolveFile( baseDir, xslFile ); + /* + * shouldn't throw out deprecation warnings before we know, + * the wrong version has been used. + */ + if( stylesheet.exists() ) + { + log( "DEPRECATED - the style attribute should be relative to the project\'s" ); + log( " basedir, not the tasks\'s basedir." ); + } + } + + // if we have an in file and out then process them + if( inFile != null && outFile != null ) + { + process( inFile, outFile, stylesheet ); + return; + } + + /* + * if we get here, in and out have not been specified, we are + * in batch processing mode. + */ + //-- make sure Source directory exists... + if( destDir == null ) + { + String msg = "destdir attributes must be set!"; + throw new BuildException( msg ); + } + scanner = getDirectoryScanner( baseDir ); + log( "Transforming into " + destDir, Project.MSG_INFO ); + + // Process all the files marked for styling + list = scanner.getIncludedFiles(); + for( int i = 0; i < list.length; ++i ) + { + process( baseDir, list[i], destDir, stylesheet ); + } + + // Process all the directoried marked for styling + dirs = scanner.getIncludedDirectories(); + for( int j = 0; j < dirs.length; ++j ) + { + list = new File( baseDir, dirs[j] ).list(); + for( int i = 0; i < list.length; ++i ) + process( baseDir, list[i], destDir, stylesheet ); + } + } + + protected XSLTLiaison getLiaison() + { + // if processor wasn't specified, see if TraX is available. If not, + // default it to xslp or xalan, depending on which is in the classpath + if( liaison == null ) + { + if( processor != null ) + { + try + { + resolveProcessor( processor ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + else + { + try + { + resolveProcessor( "trax" ); + } + catch( Throwable e1 ) + { + try + { + resolveProcessor( "xalan" ); + } + catch( Throwable e2 ) + { + try + { + resolveProcessor( "adaptx" ); + } + catch( Throwable e3 ) + { + try + { + resolveProcessor( "xslp" ); + } + catch( Throwable e4 ) + { + e4.printStackTrace(); + e3.printStackTrace(); + e2.printStackTrace(); + throw new BuildException( e1 ); + } + } + } + } + } + } + return liaison; + } + + /** + * Loads the stylesheet and set xsl:param parameters. + * + * @param stylesheet Description of Parameter + * @exception BuildException Description of Exception + */ + protected void configureLiaison( File stylesheet ) + throws BuildException + { + if( stylesheetLoaded ) + { + return; + } + stylesheetLoaded = true; + + try + { + log( "Loading stylesheet " + stylesheet, Project.MSG_INFO ); + liaison.setStylesheet( stylesheet ); + for( Enumeration e = params.elements(); e.hasMoreElements(); ) + { + Param p = ( Param )e.nextElement(); + liaison.addParam( p.getName(), p.getExpression() ); + } + } + catch( Exception ex ) + { + log( "Failed to read stylesheet " + stylesheet, Project.MSG_INFO ); + throw new BuildException( ex ); + } + } + + private void ensureDirectoryFor( File targetFile ) + throws BuildException + { + File directory = new File( targetFile.getParent() ); + if( !directory.exists() ) + { + if( !directory.mkdirs() ) + { + throw new BuildException( "Unable to create directory: " + + directory.getAbsolutePath() ); + } + } + } + + /** + * Load named class either via the system classloader or a given custom + * classloader. + * + * @param classname Description of Parameter + * @return Description of the Returned Value + * @exception Exception Description of Exception + */ + private Class loadClass( String classname ) + throws Exception + { + if( classpath == null ) + { + return Class.forName( classname ); + } + else + { + AntClassLoader al = new AntClassLoader( project, classpath ); + Class c = al.loadClass( classname ); + AntClassLoader.initializeClass( c ); + return c; + } + } + + /** + * Processes the given input XML file and stores the result in the given + * resultFile. + * + * @param baseDir Description of Parameter + * @param xmlFile Description of Parameter + * @param destDir Description of Parameter + * @param stylesheet Description of Parameter + * @exception BuildException Description of Exception + */ + private void process( File baseDir, String xmlFile, File destDir, + File stylesheet ) + throws BuildException + { + + String fileExt = targetExtension; + File outFile = null; + File inFile = null; + + try + { + long styleSheetLastModified = stylesheet.lastModified(); + inFile = new File( baseDir, xmlFile ); + int dotPos = xmlFile.lastIndexOf( '.' ); + if( dotPos > 0 ) + { + outFile = new File( destDir, xmlFile.substring( 0, xmlFile.lastIndexOf( '.' ) ) + fileExt ); + } + else + { + outFile = new File( destDir, xmlFile + fileExt ); + } + if( force || + inFile.lastModified() > outFile.lastModified() || + styleSheetLastModified > outFile.lastModified() ) + { + ensureDirectoryFor( outFile ); + log( "Processing " + inFile + " to " + outFile ); + + configureLiaison( stylesheet ); + liaison.transform( inFile, outFile ); + } + } + catch( Exception ex ) + { + // If failed to process document, must delete target document, + // or it will not attempt to process it the second time + log( "Failed to process " + inFile, Project.MSG_INFO ); + if( outFile != null ) + { + outFile.delete(); + } + + throw new BuildException( ex ); + } + + }//-- processXML + + private void process( File inFile, File outFile, File stylesheet ) + throws BuildException + { + try + { + long styleSheetLastModified = stylesheet.lastModified(); + log( "In file " + inFile + " time: " + inFile.lastModified(), Project.MSG_DEBUG ); + log( "Out file " + outFile + " time: " + outFile.lastModified(), Project.MSG_DEBUG ); + log( "Style file " + xslFile + " time: " + styleSheetLastModified, Project.MSG_DEBUG ); + if( force || + inFile.lastModified() > outFile.lastModified() || + styleSheetLastModified > outFile.lastModified() ) + { + ensureDirectoryFor( outFile ); + log( "Processing " + inFile + " to " + outFile, Project.MSG_INFO ); + configureLiaison( stylesheet ); + liaison.transform( inFile, outFile ); + } + } + catch( Exception ex ) + { + log( "Failed to process " + inFile, Project.MSG_INFO ); + if( outFile != null ) + outFile.delete(); + throw new BuildException( ex ); + } + } + + /** + * Load processor here instead of in setProcessor - this will be called from + * within execute, so we have access to the latest classpath. + * + * @param proc Description of Parameter + * @exception Exception Description of Exception + */ + private void resolveProcessor( String proc ) + throws Exception + { + if( proc.equals( "trax" ) ) + { + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.TraXLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "xslp" ) ) + { + log( "DEPRECATED - xslp processor is deprecated. Use trax or xalan instead." ); + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.XslpLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "xalan" ) ) + { + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.XalanLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "adaptx" ) ) + { + log( "DEPRECATED - adaptx processor is deprecated. Use trax or xalan instead." ); + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.AdaptxLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else + { + liaison = ( XSLTLiaison )loadClass( proc ).newInstance(); + } + } + + public class Param + { + private String name = null; + private String expression = null; + + public void setExpression( String expression ) + { + this.expression = expression; + } + + public void setName( String name ) + { + this.name = name; + } + + public String getExpression() + throws BuildException + { + if( expression == null ) + throw new BuildException( "Expression attribute is missing." ); + return expression; + } + + public String getName() + throws BuildException + { + if( name == null ) + throw new BuildException( "Name attribute is missing." ); + return name; + } + } + +}//-- XSLTProcess diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Zip.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Zip.java new file mode 100644 index 000000000..802ec0476 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Zip.java @@ -0,0 +1,888 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.Stack; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.ZipInputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.FileScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.ant.types.ZipScanner; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; +import org.apache.tools.zip.ZipEntry; +import org.apache.tools.zip.ZipOutputStream; + +/** + * Create a ZIP archive. + * + * @author James Davidson duncan@x180.com + * @author Jon S. Stevens jon@clearink.com + * @author Stefan Bodewig + */ +public class Zip extends MatchingTask +{ + + // For directories: + private final static long EMPTY_CRC = new CRC32().getValue(); + private boolean doCompress = true; + private boolean doUpdate = false; + private boolean doFilesonly = false; + protected String archiveType = "zip"; + protected String emptyBehavior = "skip"; + private Vector filesets = new Vector(); + protected Hashtable addedDirs = new Hashtable(); + private Vector addedFiles = new Vector(); + + protected File zipFile; + + /** + * true when we are adding new files into the Zip file, as opposed to adding + * back the unchanged files + */ + private boolean addingNewFiles; + private File baseDir; + + /** + * Encoding to use for filenames, defaults to the platform's default + * encoding. + */ + private String encoding; + + protected static String[][] grabFileNames( FileScanner[] scanners ) + { + String[][] result = new String[scanners.length][]; + for( int i = 0; i < scanners.length; i++ ) + { + String[] files = scanners[i].getIncludedFiles(); + String[] dirs = scanners[i].getIncludedDirectories(); + result[i] = new String[files.length + dirs.length]; + System.arraycopy( files, 0, result[i], 0, files.length ); + System.arraycopy( dirs, 0, result[i], files.length, dirs.length ); + } + return result; + } + + protected static File[] grabFiles( FileScanner[] scanners ) + { + return grabFiles( scanners, grabFileNames( scanners ) ); + } + + protected static File[] grabFiles( FileScanner[] scanners, + String[][] fileNames ) + { + Vector files = new Vector(); + for( int i = 0; i < fileNames.length; i++ ) + { + File thisBaseDir = scanners[i].getBasedir(); + for( int j = 0; j < fileNames[i].length; j++ ) + files.addElement( new File( thisBaseDir, fileNames[i][j] ) ); + } + File[] toret = new File[files.size()]; + files.copyInto( toret ); + return toret; + } + + /** + * This is the base directory to look in for things to zip. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * Sets whether we want to compress the files or only store them. + * + * @param c The new Compress value + */ + public void setCompress( boolean c ) + { + doCompress = c; + } + + /** + * Encoding to use for filenames, defaults to the platform's default + * encoding.

      + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * .

      + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * This is the name/location of where to create the .zip file. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.zipFile = file; + } + + /** + * Emulate Sun's jar utility by not adding parent dirs + * + * @param f The new Filesonly value + */ + public void setFilesonly( boolean f ) + { + doFilesonly = f; + } + + /** + * Sets whether we want to update the file (if it exists) or create a new + * one. + * + * @param c The new Update value + */ + public void setUpdate( boolean c ) + { + doUpdate = c; + } + + /** + * Sets behavior of the task when no files match. Possible values are: + * fail (throw an exception and halt the build); skip + * (do not create any archive, but issue a warning); create + * (make an archive with no entries). Default for zip tasks is skip + * ; for jar tasks, create. + * + * @param we The new Whenempty value + */ + public void setWhenempty( WhenEmpty we ) + { + emptyBehavior = we.getValue(); + } + + /** + * This is the name/location of where to create the .zip file. + * + * @param zipFile The new Zipfile value + * @deprecated Use setFile() instead + */ + public void setZipfile( File zipFile ) + { + log( "DEPRECATED - The zipfile attribute is deprecated. Use file attribute instead." ); + setFile( zipFile ); + } + + /** + * Are we updating an existing archive? + * + * @return The InUpdateMode value + */ + public boolean isInUpdateMode() + { + return doUpdate; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Adds a set of files (nested zipfileset attribute) that can be read from + * an archive and be given a prefix/fullpath. + * + * @param set The feature to be added to the Zipfileset attribute + */ + public void addZipfileset( ZipFileSet set ) + { + filesets.addElement( set ); + } + + public void execute() + throws BuildException + { + if( baseDir == null && filesets.size() == 0 && "zip".equals( archiveType ) ) + { + throw new BuildException( "basedir attribute must be set, or at least " + + "one fileset must be given!" ); + } + + if( zipFile == null ) + { + throw new BuildException( "You must specify the " + archiveType + " file to create!" ); + } + + // Renamed version of original file, if it exists + File renamedFile = null; + // Whether or not an actual update is required - + // we don't need to update if the original file doesn't exist + + addingNewFiles = true; + doUpdate = doUpdate && zipFile.exists(); + if( doUpdate ) + { + FileUtils fileUtils = FileUtils.newFileUtils(); + renamedFile = fileUtils.createTempFile( "zip", ".tmp", + fileUtils.getParentFile( zipFile ) ); + + try + { + if( !zipFile.renameTo( renamedFile ) ) + { + throw new BuildException( "Unable to rename old file to temporary file" ); + } + } + catch( SecurityException e ) + { + throw new BuildException( "Not allowed to rename old file to temporary file" ); + } + } + + // Create the scanners to pass to isUpToDate(). + Vector dss = new Vector(); + if( baseDir != null ) + { + dss.addElement( getDirectoryScanner( baseDir ) ); + } + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + dss.addElement( fs.getDirectoryScanner( project ) ); + } + int dssSize = dss.size(); + FileScanner[] scanners = new FileScanner[dssSize]; + dss.copyInto( scanners ); + + // quick exit if the target is up to date + // can also handle empty archives + if( isUpToDate( scanners, zipFile ) ) + { + return; + } + + String action = doUpdate ? "Updating " : "Building "; + + log( action + archiveType + ": " + zipFile.getAbsolutePath() ); + + boolean success = false; + try + { + ZipOutputStream zOut = + new ZipOutputStream( new FileOutputStream( zipFile ) ); + zOut.setEncoding( encoding ); + try + { + if( doCompress ) + { + zOut.setMethod( ZipOutputStream.DEFLATED ); + } + else + { + zOut.setMethod( ZipOutputStream.STORED ); + } + initZipOutputStream( zOut ); + + // Add the implicit fileset to the archive. + if( baseDir != null ) + { + addFiles( getDirectoryScanner( baseDir ), zOut, "", "" ); + } + // Add the explicit filesets to the archive. + addFiles( filesets, zOut ); + if( doUpdate ) + { + addingNewFiles = false; + ZipFileSet oldFiles = new ZipFileSet(); + oldFiles.setSrc( renamedFile ); + + StringBuffer exclusionPattern = new StringBuffer(); + for( int i = 0; i < addedFiles.size(); i++ ) + { + if( i != 0 ) + { + exclusionPattern.append( "," ); + } + exclusionPattern.append( ( String )addedFiles.elementAt( i ) ); + } + oldFiles.setExcludes( exclusionPattern.toString() ); + Vector tmp = new Vector(); + tmp.addElement( oldFiles ); + addFiles( tmp, zOut ); + } + finalizeZipOutputStream( zOut ); + success = true; + } + finally + { + // Close the output stream. + try + { + if( zOut != null ) + { + zOut.close(); + } + } + catch( IOException ex ) + { + // If we're in this finally clause because of an exception, we don't + // really care if there's an exception when closing the stream. E.g. if it + // throws "ZIP file must have at least one entry", because an exception happened + // before we added any files, then we must swallow this exception. Otherwise, + // the error that's reported will be the close() error, which is not the real + // cause of the problem. + if( success ) + throw ex; + } + } + } + catch( IOException ioe ) + { + String msg = "Problem creating " + archiveType + ": " + ioe.getMessage(); + + // delete a bogus ZIP file + if( !zipFile.delete() ) + { + msg += " (and the archive is probably corrupt but I could not delete it)"; + } + + if( doUpdate ) + { + if( !renamedFile.renameTo( zipFile ) ) + { + msg += " (and I couldn't rename the temporary file " + + renamedFile.getName() + " back)"; + } + } + + throw new BuildException( msg, ioe, location ); + } + finally + { + cleanUp(); + } + + // If we've been successful on an update, delete the temporary file + if( success && doUpdate ) + { + if( !renamedFile.delete() ) + { + log( "Warning: unable to delete temporary file " + + renamedFile.getName(), Project.MSG_WARN ); + } + } + } + + /** + * Indicates if the task is adding new files into the archive as opposed to + * copying back unchanged files from the backup copy + * + * @return The AddingNewFiles value + */ + protected boolean isAddingNewFiles() + { + return addingNewFiles; + } + + + /** + * Check whether the archive is up-to-date; and handle behavior for empty + * archives. + * + * @param scanners list of prepared scanners containing files to archive + * @param zipFile intended archive file (may or may not exist) + * @return true if nothing need be done (may have done something already); + * false if archive creation should proceed + * @exception BuildException if it likes + */ + protected boolean isUpToDate( FileScanner[] scanners, File zipFile ) + throws BuildException + { + String[][] fileNames = grabFileNames( scanners ); + File[] files = grabFiles( scanners, fileNames ); + if( files.length == 0 ) + { + if( emptyBehavior.equals( "skip" ) ) + { + log( "Warning: skipping " + archiveType + " archive " + zipFile + + " because no files were included.", Project.MSG_WARN ); + return true; + } + else if( emptyBehavior.equals( "fail" ) ) + { + throw new BuildException( "Cannot create " + archiveType + " archive " + zipFile + + ": no files were included.", location ); + } + else + { + // Create. + return createEmptyZip( zipFile ); + } + } + else + { + for( int i = 0; i < files.length; ++i ) + { + if( files[i].equals( zipFile ) ) + { + throw new BuildException( "A zip file cannot include itself", location ); + } + } + + if( !zipFile.exists() ) + return false; + + SourceFileScanner sfs = new SourceFileScanner( this ); + MergingMapper mm = new MergingMapper(); + mm.setTo( zipFile.getAbsolutePath() ); + for( int i = 0; i < scanners.length; i++ ) + { + if( sfs.restrict( fileNames[i], scanners[i].getBasedir(), null, + mm ).length > 0 ) + { + return false; + } + } + return true; + } + } + + /** + * Add all files of the given FileScanner to the ZipOutputStream prependig + * the given prefix to each filename.

      + * + * Ensure parent directories have been added as well. + * + * @param scanner The feature to be added to the Files attribute + * @param zOut The feature to be added to the Files attribute + * @param prefix The feature to be added to the Files attribute + * @param fullpath The feature to be added to the Files attribute + * @exception IOException Description of Exception + */ + protected void addFiles( FileScanner scanner, ZipOutputStream zOut, + String prefix, String fullpath ) + throws IOException + { + if( prefix.length() > 0 && fullpath.length() > 0 ) + throw new BuildException( "Both prefix and fullpath attributes may not be set on the same fileset." ); + + File thisBaseDir = scanner.getBasedir(); + + // directories that matched include patterns + String[] dirs = scanner.getIncludedDirectories(); + if( dirs.length > 0 && fullpath.length() > 0 ) + throw new BuildException( "fullpath attribute may only be specified for filesets that specify a single file." ); + for( int i = 0; i < dirs.length; i++ ) + { + if( "".equals( dirs[i] ) ) + { + continue; + } + String name = dirs[i].replace( File.separatorChar, '/' ); + if( !name.endsWith( "/" ) ) + { + name += "/"; + } + addParentDirs( thisBaseDir, name, zOut, prefix ); + } + + // files that matched include patterns + String[] files = scanner.getIncludedFiles(); + if( files.length > 1 && fullpath.length() > 0 ) + throw new BuildException( "fullpath attribute may only be specified for filesets that specify a single file." ); + for( int i = 0; i < files.length; i++ ) + { + File f = new File( thisBaseDir, files[i] ); + if( fullpath.length() > 0 ) + { + // Add this file at the specified location. + addParentDirs( null, fullpath, zOut, "" ); + zipFile( f, zOut, fullpath ); + } + else + { + // Add this file with the specified prefix. + String name = files[i].replace( File.separatorChar, '/' ); + addParentDirs( thisBaseDir, name, zOut, prefix ); + zipFile( f, zOut, prefix + name ); + } + } + } + + /** + * Iterate over the given Vector of (zip)filesets and add all files to the + * ZipOutputStream using the given prefix or fullpath. + * + * @param filesets The feature to be added to the Files attribute + * @param zOut The feature to be added to the Files attribute + * @exception IOException Description of Exception + */ + protected void addFiles( Vector filesets, ZipOutputStream zOut ) + throws IOException + { + // Add each fileset in the Vector. + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + + String prefix = ""; + String fullpath = ""; + if( fs instanceof ZipFileSet ) + { + ZipFileSet zfs = ( ZipFileSet )fs; + prefix = zfs.getPrefix(); + fullpath = zfs.getFullpath(); + } + + if( prefix.length() > 0 + && !prefix.endsWith( "/" ) + && !prefix.endsWith( "\\" ) ) + { + prefix += "/"; + } + + // Need to manually add either fullpath's parent directory, or + // the prefix directory, to the archive. + if( prefix.length() > 0 ) + { + addParentDirs( null, prefix, zOut, "" ); + zipDir( null, zOut, prefix ); + } + else if( fullpath.length() > 0 ) + { + addParentDirs( null, fullpath, zOut, "" ); + } + + if( fs instanceof ZipFileSet + && ( ( ZipFileSet )fs ).getSrc() != null ) + { + addZipEntries( ( ZipFileSet )fs, ds, zOut, prefix, fullpath ); + } + else + { + // Add the fileset. + addFiles( ds, zOut, prefix, fullpath ); + } + } + } + + /** + * Ensure all parent dirs of a given entry have been added. + * + * @param baseDir The feature to be added to the ParentDirs attribute + * @param entry The feature to be added to the ParentDirs attribute + * @param zOut The feature to be added to the ParentDirs attribute + * @param prefix The feature to be added to the ParentDirs attribute + * @exception IOException Description of Exception + */ + protected void addParentDirs( File baseDir, String entry, + ZipOutputStream zOut, String prefix ) + throws IOException + { + if( !doFilesonly ) + { + Stack directories = new Stack(); + int slashPos = entry.length(); + + while( ( slashPos = entry.lastIndexOf( ( int )'/', slashPos - 1 ) ) != -1 ) + { + String dir = entry.substring( 0, slashPos + 1 ); + if( addedDirs.get( prefix + dir ) != null ) + { + break; + } + directories.push( dir ); + } + + while( !directories.isEmpty() ) + { + String dir = ( String )directories.pop(); + File f = null; + if( baseDir != null ) + { + f = new File( baseDir, dir ); + } + else + { + f = new File( dir ); + } + zipDir( f, zOut, prefix + dir ); + } + } + } + + protected void addZipEntries( ZipFileSet fs, DirectoryScanner ds, + ZipOutputStream zOut, String prefix, String fullpath ) + throws IOException + { + if( prefix.length() > 0 && fullpath.length() > 0 ) + throw new BuildException( "Both prefix and fullpath attributes may not be set on the same fileset." ); + + ZipScanner zipScanner = ( ZipScanner )ds; + File zipSrc = fs.getSrc(); + + ZipEntry entry; + java.util.zip.ZipEntry origEntry; + ZipInputStream in = null; + try + { + in = new ZipInputStream( new FileInputStream( zipSrc ) ); + + while( ( origEntry = in.getNextEntry() ) != null ) + { + entry = new ZipEntry( origEntry ); + String vPath = entry.getName(); + if( zipScanner.match( vPath ) ) + { + if( fullpath.length() > 0 ) + { + addParentDirs( null, fullpath, zOut, "" ); + zipFile( in, zOut, fullpath, entry.getTime() ); + } + else + { + addParentDirs( null, vPath, zOut, prefix ); + if( !entry.isDirectory() ) + { + zipFile( in, zOut, prefix + vPath, entry.getTime() ); + } + } + } + } + } + finally + { + if( in != null ) + { + in.close(); + } + } + } + + /** + * Do any clean up necessary to allow this instance to be used again.

      + * + * When we get here, the Zip file has been closed and all we need to do is + * to reset some globals.

      + */ + protected void cleanUp() + { + addedDirs = new Hashtable(); + addedFiles = new Vector(); + filesets = new Vector(); + zipFile = null; + baseDir = null; + doCompress = true; + doUpdate = false; + doFilesonly = false; + addingNewFiles = false; + encoding = null; + } + + /** + * Create an empty zip file + * + * @param zipFile Description of Parameter + * @return true if the file is then considered up to date. + */ + protected boolean createEmptyZip( File zipFile ) + { + // In this case using java.util.zip will not work + // because it does not permit a zero-entry archive. + // Must create it manually. + log( "Note: creating empty " + archiveType + " archive " + zipFile, Project.MSG_INFO ); + try + { + OutputStream os = new FileOutputStream( zipFile ); + try + { + // Cf. PKZIP specification. + byte[] empty = new byte[22]; + empty[0] = 80;// P + empty[1] = 75;// K + empty[2] = 5; + empty[3] = 6; + // remainder zeros + os.write( empty ); + } + finally + { + os.close(); + } + } + catch( IOException ioe ) + { + throw new BuildException( "Could not create empty ZIP archive", ioe, location ); + } + return true; + } + + protected void finalizeZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException { } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException { } + + protected void zipDir( File dir, ZipOutputStream zOut, String vPath ) + throws IOException + { + if( addedDirs.get( vPath ) != null ) + { + // don't add directories we've already added. + // no warning if we try, it is harmless in and of itself + return; + } + addedDirs.put( vPath, vPath ); + + ZipEntry ze = new ZipEntry( vPath ); + if( dir != null && dir.exists() ) + { + ze.setTime( dir.lastModified() ); + } + else + { + ze.setTime( System.currentTimeMillis() ); + } + ze.setSize( 0 ); + ze.setMethod( ZipEntry.STORED ); + // This is faintly ridiculous: + ze.setCrc( EMPTY_CRC ); + + // this is 040775 | MS-DOS directory flag in reverse byte order + ze.setExternalAttributes( 0x41FD0010L ); + + zOut.putNextEntry( ze ); + } + + protected void zipFile( InputStream in, ZipOutputStream zOut, String vPath, + long lastModified ) + throws IOException + { + ZipEntry ze = new ZipEntry( vPath ); + ze.setTime( lastModified ); + + /* + * XXX ZipOutputStream.putEntry expects the ZipEntry to know its + * size and the CRC sum before you start writing the data when using + * STORED mode. + * + * This forces us to process the data twice. + * + * I couldn't find any documentation on this, just found out by try + * and error. + */ + if( !doCompress ) + { + long size = 0; + CRC32 cal = new CRC32(); + if( !in.markSupported() ) + { + // Store data into a byte[] + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + size += count; + cal.update( buffer, 0, count ); + bos.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + in = new ByteArrayInputStream( bos.toByteArray() ); + + } + else + { + in.mark( Integer.MAX_VALUE ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + size += count; + cal.update( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + in.reset(); + } + ze.setSize( size ); + ze.setCrc( cal.getValue() ); + } + + zOut.putNextEntry( ze ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + if( count != 0 ) + { + zOut.write( buffer, 0, count ); + } + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + addedFiles.addElement( vPath ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + if( file.equals( zipFile ) ) + { + throw new BuildException( "A zip file cannot include itself", location ); + } + + FileInputStream fIn = new FileInputStream( file ); + try + { + zipFile( fIn, zOut, vPath, file.lastModified() ); + } + finally + { + fIn.close(); + } + } + + + /** + * Possible behaviors when there are no matching files for the task. + * + * @author RT + */ + public static class WhenEmpty extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"fail", "skip", "create"}; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java new file mode 100644 index 000000000..f82338b01 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Javac; + +/** + * The interface that all compiler adapters must adher to.

      + * + * A compiler adapter is an adapter that interprets the javac's parameters in + * preperation to be passed off to the compier this adapter represents. As all + * the necessary values are stored in the Javac task itself, the only thing all + * adapters need is the javac task, the execute command and a parameterless + * constructor (for reflection).

      + * + * @author Jay Dickon Glanville + * jayglanville@home.com + */ + +public interface CompilerAdapter +{ + + /** + * Sets the compiler attributes, which are stored in the Javac task. + * + * @param attributes The new Javac value + */ + void setJavac( Javac attributes ); + + /** + * Executes the task. + * + * @return has the compilation been successful + * @exception BuildException Description of Exception + */ + boolean execute() + throws BuildException; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java new file mode 100644 index 000000000..5e0e91536 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Creates the necessary compiler adapter, given basic criteria. + * + * @author J D Glanville + */ +public class CompilerAdapterFactory +{ + + /** + * This is a singlton -- can't create instances!! + */ + private CompilerAdapterFactory() { } + + /** + * Based on the parameter passed in, this method creates the necessary + * factory desired. The current mapping for compiler names are as follows: + * + *
        + *
      • jikes = jikes compiler + *
      • classic, javac1.1, javac1.2 = the standard compiler from JDK + * 1.1/1.2 + *
      • modern, javac1.3 = the new compiler of JDK 1.3 + *
      • jvc, microsoft = the command line compiler from Microsoft's SDK + * for Java / Visual J++ + *
      • kjc = the kopi compiler
      • + *
      • gcj = the gcj compiler from gcc
      • + *
      • a fully quallified classname = the name of a compiler + * adapter + *
      + * + * + * @param compilerType either the name of the desired compiler, or the full + * classname of the compiler's adapter. + * @param task a task to log through. + * @return The Compiler value + * @throws BuildException if the compiler type could not be resolved into a + * compiler adapter. + */ + public static CompilerAdapter getCompiler( String compilerType, Task task ) + throws BuildException + { + /* + * If I've done things right, this should be the extent of the + * conditional statements required. + */ + if( compilerType.equalsIgnoreCase( "jikes" ) ) + { + return new Jikes(); + } + if( compilerType.equalsIgnoreCase( "extJavac" ) ) + { + return new JavacExternal(); + } + if( compilerType.equalsIgnoreCase( "classic" ) || + compilerType.equalsIgnoreCase( "javac1.1" ) || + compilerType.equalsIgnoreCase( "javac1.2" ) ) + { + return new Javac12(); + } + if( compilerType.equalsIgnoreCase( "modern" ) || + compilerType.equalsIgnoreCase( "javac1.3" ) || + compilerType.equalsIgnoreCase( "javac1.4" ) ) + { + // does the modern compiler exist? + try + { + Class.forName( "com.sun.tools.javac.Main" ); + } + catch( ClassNotFoundException cnfe ) + { + task.log( "Modern compiler is not available - using " + + "classic compiler", Project.MSG_WARN ); + return new Javac12(); + } + return new Javac13(); + } + if( compilerType.equalsIgnoreCase( "jvc" ) || + compilerType.equalsIgnoreCase( "microsoft" ) ) + { + return new Jvc(); + } + if( compilerType.equalsIgnoreCase( "kjc" ) ) + { + return new Kjc(); + } + if( compilerType.equalsIgnoreCase( "gcj" ) ) + { + return new Gcj(); + } + if( compilerType.equalsIgnoreCase( "sj" ) || + compilerType.equalsIgnoreCase( "symantec" ) ) + { + return new Sj(); + } + return resolveClassName( compilerType ); + } + + /** + * Tries to resolve the given classname into a compiler adapter. Throws a + * fit if it can't. + * + * @param className The fully qualified classname to be created. + * @return Description of the Returned Value + * @throws BuildException This is the fit that is thrown if className isn't + * an instance of CompilerAdapter. + */ + private static CompilerAdapter resolveClassName( String className ) + throws BuildException + { + try + { + Class c = Class.forName( className ); + Object o = c.newInstance(); + return ( CompilerAdapter )o; + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( className + " can\'t be found.", cnfe ); + } + catch( ClassCastException cce ) + { + throw new BuildException( className + " isn\'t the classname of " + + "a compiler adapter.", cce ); + } + catch( Throwable t ) + { + // for all other possibilities + throw new BuildException( className + " caused an interesting " + + "exception.", t ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java new file mode 100644 index 000000000..a4675b48d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java @@ -0,0 +1,494 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.Javac; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.FileUtils; + +/** + * This is the default implementation for the CompilerAdapter interface. + * Currently, this is a cut-and-paste of the original javac task. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public abstract class DefaultCompilerAdapter implements CompilerAdapter +{ + protected static String lSep = System.getProperty( "line.separator" ); + protected boolean debug = false; + protected boolean optimize = false; + protected boolean deprecation = false; + protected boolean depend = false; + protected boolean verbose = false; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + protected Javac attributes; + protected Path bootclasspath; + protected Path compileClasspath; + + protected File[] compileList; + protected File destDir; + protected String encoding; + protected Path extdirs; + protected boolean includeAntRuntime; + protected boolean includeJavaRuntime; + protected Location location; + protected String memoryInitialSize; + protected String memoryMaximumSize; + protected Project project; + + /* + * jdg - TODO - all these attributes are currently protected, but they + * should probably be private in the near future. + */ + protected Path src; + protected String target; + + public void setJavac( Javac attributes ) + { + this.attributes = attributes; + src = attributes.getSrcdir(); + destDir = attributes.getDestdir(); + encoding = attributes.getEncoding(); + debug = attributes.getDebug(); + optimize = attributes.getOptimize(); + deprecation = attributes.getDeprecation(); + depend = attributes.getDepend(); + verbose = attributes.getVerbose(); + target = attributes.getTarget(); + bootclasspath = attributes.getBootclasspath(); + extdirs = attributes.getExtdirs(); + compileList = attributes.getFileList(); + compileClasspath = attributes.getClasspath(); + project = attributes.getProject(); + location = attributes.getLocation(); + includeAntRuntime = attributes.getIncludeantruntime(); + includeJavaRuntime = attributes.getIncludejavaruntime(); + memoryInitialSize = attributes.getMemoryInitialSize(); + memoryMaximumSize = attributes.getMemoryMaximumSize(); + } + + public Javac getJavac() + { + return attributes; + } + + protected Commandline setupJavacCommand() + { + return setupJavacCommand( false ); + } + + /** + * Does the command line argument processing for classic and adds the files + * to compile as well. + * + * @param debugLevelCheck Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupJavacCommand( boolean debugLevelCheck ) + { + Commandline cmd = new Commandline(); + setupJavacCommandlineSwitches( cmd, debugLevelCheck ); + logAndAddFilesToCompile( cmd ); + return cmd; + } + + protected Commandline setupJavacCommandlineSwitches( Commandline cmd ) + { + return setupJavacCommandlineSwitches( cmd, false ); + } + + /** + * Does the command line argument processing common to classic and modern. + * Doesn't add the files to compile. + * + * @param cmd Description of Parameter + * @param useDebugLevel Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupJavacCommandlineSwitches( Commandline cmd, + boolean useDebugLevel ) + { + Path classpath = getCompileClasspath(); + + // we cannot be using Java 1.0 when forking, so we only have to + // distinguish between Java 1.1, and Java 1.2 and higher, as Java 1.1 + // has its own parameter format + boolean usingJava1_1 = Project.getJavaVersion().equals( Project.JAVA_1_1 ); + String memoryParameterPrefix = usingJava1_1 ? "-J-" : "-J-X"; + if( memoryInitialSize != null ) + { + if( !attributes.isForkedJavac() ) + { + attributes.log( "Since fork is false, ignoring memoryInitialSize setting.", + Project.MSG_WARN ); + } + else + { + cmd.createArgument().setValue( memoryParameterPrefix + "ms" + memoryInitialSize ); + } + } + + if( memoryMaximumSize != null ) + { + if( !attributes.isForkedJavac() ) + { + attributes.log( "Since fork is false, ignoring memoryMaximumSize setting.", + Project.MSG_WARN ); + } + else + { + cmd.createArgument().setValue( memoryParameterPrefix + "mx" + memoryMaximumSize ); + } + } + + if( attributes.getNowarn() ) + { + cmd.createArgument().setValue( "-nowarn" ); + } + + if( deprecation == true ) + { + cmd.createArgument().setValue( "-deprecation" ); + } + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + cmd.createArgument().setValue( "-classpath" ); + + // Just add "sourcepath" to classpath ( for JDK1.1 ) + // as well as "bootclasspath" and "extdirs" + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + Path cp = new Path( project ); + /* + * XXX - This doesn't mix very well with build.systemclasspath, + */ + if( bootclasspath != null ) + { + cp.append( bootclasspath ); + } + if( extdirs != null ) + { + cp.addExtdirs( extdirs ); + } + cp.append( classpath ); + cp.append( src ); + cmd.createArgument().setPath( cp ); + } + else + { + cmd.createArgument().setPath( classpath ); + cmd.createArgument().setValue( "-sourcepath" ); + cmd.createArgument().setPath( src ); + if( target != null ) + { + cmd.createArgument().setValue( "-target" ); + cmd.createArgument().setValue( target ); + } + if( bootclasspath != null ) + { + cmd.createArgument().setValue( "-bootclasspath" ); + cmd.createArgument().setPath( bootclasspath ); + } + if( extdirs != null ) + { + cmd.createArgument().setValue( "-extdirs" ); + cmd.createArgument().setPath( extdirs ); + } + } + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + if( debug ) + { + if( useDebugLevel + && Project.getJavaVersion() != Project.JAVA_1_0 + && Project.getJavaVersion() != Project.JAVA_1_1 ) + { + + String debugLevel = attributes.getDebugLevel(); + if( debugLevel != null ) + { + cmd.createArgument().setValue( "-g:" + debugLevel ); + } + else + { + cmd.createArgument().setValue( "-g" ); + } + } + else + { + cmd.createArgument().setValue( "-g" ); + } + } + else if( Project.getJavaVersion() != Project.JAVA_1_0 && + Project.getJavaVersion() != Project.JAVA_1_1 ) + { + cmd.createArgument().setValue( "-g:none" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + + if( depend ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + cmd.createArgument().setValue( "-depend" ); + } + else if( Project.getJavaVersion().startsWith( "1.2" ) ) + { + cmd.createArgument().setValue( "-Xdepend" ); + } + else + { + attributes.log( "depend attribute is not supported by the modern compiler", + Project.MSG_WARN ); + } + } + + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + return cmd; + } + + /** + * Does the command line argument processing for modern and adds the files + * to compile as well. + * + * @return Description of the Returned Value + */ + protected Commandline setupModernJavacCommand() + { + Commandline cmd = new Commandline(); + setupModernJavacCommandlineSwitches( cmd ); + + logAndAddFilesToCompile( cmd ); + return cmd; + } + + /** + * Does the command line argument processing for modern. Doesn't add the + * files to compile. + * + * @param cmd Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupModernJavacCommandlineSwitches( Commandline cmd ) + { + setupJavacCommandlineSwitches( cmd, true ); + if( attributes.getSource() != null ) + { + cmd.createArgument().setValue( "-source" ); + cmd.createArgument().setValue( attributes.getSource() ); + } + return cmd; + } + + /** + * Builds the compilation classpath. + * + * @return The CompileClasspath value + */ + protected Path getCompileClasspath() + { + Path classpath = new Path( project ); + + // add dest dir to classpath so that previously compiled and + // untouched classes are on classpath + + if( destDir != null ) + { + classpath.setLocation( destDir ); + } + + // Combine the build classpath with the system classpath, in an + // order determined by the value of build.classpath + + if( compileClasspath == null ) + { + if( includeAntRuntime ) + { + classpath.addExisting( Path.systemClasspath ); + } + } + else + { + if( includeAntRuntime ) + { + classpath.addExisting( compileClasspath.concatSystemClasspath( "last" ) ); + } + else + { + classpath.addExisting( compileClasspath.concatSystemClasspath( "ignore" ) ); + } + } + + if( includeJavaRuntime ) + { + classpath.addJavaRuntime(); + } + + return classpath; + } + + /** + * Adds the command line arguments specifc to the current implementation. + * + * @param cmd The feature to be added to the CurrentCompilerArgs attribute + */ + protected void addCurrentCompilerArgs( Commandline cmd ) + { + cmd.addArguments( getJavac().getCurrentCompilerArgs() ); + } + + /** + * Do the compile with the specified arguments. + * + * @param args - arguments to pass to process on command line + * @param firstFileName - index of the first source file in args + * @return Description of the Returned Value + */ + protected int executeExternalCompile( String[] args, int firstFileName ) + { + String[] commandArray = null; + File tmpFile = null; + + try + { + /* + * Many system have been reported to get into trouble with + * long command lines - no, not only Windows ;-). + * + * POSIX seems to define a lower limit of 4k, so use a temporary + * file if the total length of the command line exceeds this limit. + */ + if( Commandline.toString( args ).length() > 4096 ) + { + PrintWriter out = null; + try + { + tmpFile = fileUtils.createTempFile( "jikes", "", null ); + out = new PrintWriter( new FileWriter( tmpFile ) ); + for( int i = firstFileName; i < args.length; i++ ) + { + out.println( args[i] ); + } + out.flush(); + commandArray = new String[firstFileName + 1]; + System.arraycopy( args, 0, commandArray, 0, firstFileName ); + commandArray[firstFileName] = "@" + tmpFile.getAbsolutePath(); + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", e, location ); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( Throwable t ) + {} + } + } + } + else + { + commandArray = args; + } + + try + { + Execute exe = new Execute( new LogStreamHandler( attributes, + Project.MSG_INFO, + Project.MSG_WARN ) ); + exe.setAntRun( project ); + exe.setWorkingDirectory( project.getBaseDir() ); + exe.setCommandline( commandArray ); + exe.execute(); + return exe.getExitValue(); + } + catch( IOException e ) + { + throw new BuildException( "Error running " + args[0] + + " compiler", e, location ); + } + } + finally + { + if( tmpFile != null ) + { + tmpFile.delete(); + } + } + } + + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( Commandline cmd ) + { + attributes.log( "Compilation args: " + cmd.toString(), + Project.MSG_VERBOSE ); + + StringBuffer niceSourceList = new StringBuffer( "File" ); + if( compileList.length != 1 ) + { + niceSourceList.append( "s" ); + } + niceSourceList.append( " to be compiled:" ); + + niceSourceList.append( lSep ); + + for( int i = 0; i < compileList.length; i++ ) + { + String arg = compileList[i].getAbsolutePath(); + cmd.createArgument().setValue( arg ); + niceSourceList.append( " " + arg + lSep ); + } + + attributes.log( niceSourceList.toString(), Project.MSG_VERBOSE ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Gcj.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Gcj.java new file mode 100644 index 000000000..ee0918e10 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Gcj.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the gcj compiler. This is primarily a cut-and-paste + * from the jikes. + * + * @author Takashi Okamoto + */ +public class Gcj extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the gcj compiler. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author tora@debian.org + */ + public boolean execute() + throws BuildException + { + Commandline cmd; + attributes.log( "Using gcj compiler", Project.MSG_VERBOSE ); + cmd = setupGCJCommand(); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + + protected Commandline setupGCJCommand() + { + Commandline cmd = new Commandline(); + Path classpath = new Path( project ); + + // gcj doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // gcj doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + classpath.append( getCompileClasspath() ); + + // Gcj has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + cmd.setExecutable( "gcj" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + + if( destDir.mkdirs() ) + { + throw new BuildException( "Can't make output directories. Maybe permission is wrong. " ); + } + ; + } + + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + + if( encoding != null ) + { + cmd.createArgument().setValue( "--encoding=" + encoding ); + } + if( debug ) + { + cmd.createArgument().setValue( "-g1" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + + /** + * gcj should be set for generate class. + */ + cmd.createArgument().setValue( "-C" ); + + addCurrentCompilerArgs( cmd ); + + return cmd; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac12.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac12.java new file mode 100644 index 000000000..9eee2523a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac12.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the javac compiler for JDK 1.2 This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Javac12 extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using classic compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupJavacCommand( true ); + + OutputStream logstr = new LogOutputStream( attributes, Project.MSG_WARN ); + try + { + // Create an instance of the compiler, redirecting output to + // the project log + Class c = Class.forName( "sun.tools.javac.Main" ); + Constructor cons = c.getConstructor( new Class[]{OutputStream.class, String.class} ); + Object compiler = cons.newInstance( new Object[]{logstr, "javac"} ); + + // Call the compile() method + Method compile = c.getMethod( "compile", new Class[]{String[].class} ); + Boolean ok = ( Boolean )compile.invoke( compiler, new Object[]{cmd.getArguments()} ); + return ok.booleanValue(); + } + catch( ClassNotFoundException ex ) + { + throw new BuildException( "Cannot use classic compiler, as it is not available" + + " A common solution is to set the environment variable" + + " JAVA_HOME to your jdk directory.", location ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting classic compiler: ", ex, location ); + } + } + finally + { + try + { + logstr.close(); + } + catch( IOException e ) + { + // plain impossible + throw new BuildException( e ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac13.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac13.java new file mode 100644 index 000000000..7516d43c9 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac13.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + +/** + * The implementation of the javac compiler for JDK 1.3 This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Javac13 extends DefaultCompilerAdapter +{ + + /** + * Integer returned by the "Modern" jdk1.3 compiler to indicate success. + */ + private final static int MODERN_COMPILER_SUCCESS = 0; + + public boolean execute() + throws BuildException + { + attributes.log( "Using modern compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupModernJavacCommand(); + + // Use reflection to be able to build on all JDKs >= 1.1: + try + { + Class c = Class.forName( "com.sun.tools.javac.Main" ); + Object compiler = c.newInstance(); + Method compile = c.getMethod( "compile", + new Class[]{( new String[]{} ).getClass()} ); + int result = ( ( Integer )compile.invoke + ( compiler, new Object[]{cmd.getArguments()} ) ).intValue(); + return ( result == MODERN_COMPILER_SUCCESS ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting modern compiler", ex, location ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java new file mode 100644 index 000000000..528b7cd43 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * Performs a compile using javac externally. + * + * @author Brian Deitte + */ +public class JavacExternal extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the Javac externally. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using external javac compiler", Project.MSG_VERBOSE ); + + Commandline cmd = new Commandline(); + cmd.setExecutable( getJavac().getJavacExecutable() ); + setupModernJavacCommandlineSwitches( cmd ); + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jikes.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jikes.java new file mode 100644 index 000000000..a45f64c3c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jikes.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the jikes compiler. This is primarily a cut-and-paste + * from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Jikes extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the Jikes compiler from IBM.. Mostly of this + * code is identical to doClassicCompile() However, it does not support all + * options like bootclasspath, extdirs, deprecation and so on, because there + * is no option in jikes and I don't understand what they should do. It has + * been successfully tested with jikes >1.10 + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author skanthak@muehlheim.de + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using jikes compiler", Project.MSG_VERBOSE ); + + Path classpath = new Path( project ); + + // Jikes doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // Jikes doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + else + { + // there is a bootclasspath stated. By default, the + // includeJavaRuntime is false. If the user has stated a + // bootclasspath and said to include the java runtime, it's on + // their head! + } + classpath.append( getCompileClasspath() ); + + // Jikes has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + // if the user has set JIKESPATH we should add the contents as well + String jikesPath = System.getProperty( "jikes.class.path" ); + if( jikesPath != null ) + { + classpath.append( new Path( project, jikesPath ) ); + } + + Commandline cmd = new Commandline(); + cmd.setExecutable( "jikes" ); + + if( deprecation == true ) + cmd.createArgument().setValue( "-deprecation" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + if( debug ) + { + cmd.createArgument().setValue( "-g" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + if( depend ) + { + cmd.createArgument().setValue( "-depend" ); + } + /** + * XXX Perhaps we shouldn't use properties for these three options + * (emacs mode, warnings and pedantic), but include it in the javac + * directive? + */ + + /** + * Jikes has the nice feature to print error messages in a form readable + * by emacs, so that emacs can directly set the cursor to the place, + * where the error occured. + */ + String emacsProperty = project.getProperty( "build.compiler.emacs" ); + if( emacsProperty != null && Project.toBoolean( emacsProperty ) ) + { + cmd.createArgument().setValue( "+E" ); + } + + /** + * Jikes issues more warnings that javac, for example, when you have + * files in your classpath that don't exist. As this is often the case, + * these warning can be pretty annoying. + */ + String warningsProperty = project.getProperty( "build.compiler.warnings" ); + if( warningsProperty != null ) + { + attributes.log( "!! the build.compiler.warnings property is deprecated. !!", + Project.MSG_WARN ); + attributes.log( "!! Use the nowarn attribute instead. !!", + Project.MSG_WARN ); + if( !Project.toBoolean( warningsProperty ) ) + { + cmd.createArgument().setValue( "-nowarn" ); + } + } + if( attributes.getNowarn() ) + { + /* + * FIXME later + * + * let the magic property win over the attribute for backwards + * compatibility + */ + cmd.createArgument().setValue( "-nowarn" ); + } + + /** + * Jikes can issue pedantic warnings. + */ + String pedanticProperty = project.getProperty( "build.compiler.pedantic" ); + if( pedanticProperty != null && Project.toBoolean( pedanticProperty ) ) + { + cmd.createArgument().setValue( "+P" ); + } + + /** + * Jikes supports something it calls "full dependency checking", see the + * jikes documentation for differences between -depend and +F. + */ + String fullDependProperty = project.getProperty( "build.compiler.fulldepend" ); + if( fullDependProperty != null && Project.toBoolean( fullDependProperty ) ) + { + cmd.createArgument().setValue( "+F" ); + } + + addCurrentCompilerArgs( cmd ); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jvc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jvc.java new file mode 100644 index 000000000..ccb0b0f2a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jvc.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the jvc compiler from microsoft. This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Jvc extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using jvc compiler", Project.MSG_VERBOSE ); + + Path classpath = new Path( project ); + + // jvc doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // jvc doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + else + { + // there is a bootclasspath stated. By default, the + // includeJavaRuntime is false. If the user has stated a + // bootclasspath and said to include the java runtime, it's on + // their head! + } + classpath.append( getCompileClasspath() ); + + // jvc has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + Commandline cmd = new Commandline(); + cmd.setExecutable( "jvc" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "/d" ); + cmd.createArgument().setFile( destDir ); + } + + // Add the Classpath before the "internal" one. + cmd.createArgument().setValue( "/cp:p" ); + cmd.createArgument().setPath( classpath ); + + // Enable MS-Extensions and ... + cmd.createArgument().setValue( "/x-" ); + // ... do not display a Message about this. + cmd.createArgument().setValue( "/nomessage" ); + // Do not display Logo + cmd.createArgument().setValue( "/nologo" ); + + if( debug ) + { + cmd.createArgument().setValue( "/g" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "/O" ); + } + if( verbose ) + { + cmd.createArgument().setValue( "/verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Kjc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Kjc.java new file mode 100644 index 000000000..2cb2bdc48 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Kjc.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the Java compiler for KJC. This is primarily a + * cut-and-paste from Jikes.java and DefaultCompilerAdapter. + * + * @author Takashi Okamoto + + */ +public class Kjc extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using kjc compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupKjcCommand(); + + try + { + Class c = Class.forName( "at.dms.kjc.Main" ); + + // Call the compile() method + Method compile = c.getMethod( "compile", + new Class[]{String[].class} ); + Boolean ok = ( Boolean )compile.invoke( null, + new Object[]{cmd.getArguments()} ); + return ok.booleanValue(); + } + catch( ClassNotFoundException ex ) + { + throw new BuildException( "Cannot use kjc compiler, as it is not available" + + " A common solution is to set the environment variable" + + " CLASSPATH to your kjc archive (kjc.jar).", location ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting kjc compiler: ", ex, location ); + } + } + } + + /** + * setup kjc command arguments. + * + * @return Description of the Returned Value + */ + protected Commandline setupKjcCommand() + { + Commandline cmd = new Commandline(); + + // generate classpath, because kjc does't support sourcepath. + Path classpath = getCompileClasspath(); + + if( deprecation == true ) + { + cmd.createArgument().setValue( "-deprecation" ); + } + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + // generate the clsspath + cmd.createArgument().setValue( "-classpath" ); + + Path cp = new Path( project ); + + // kjc don't have bootclasspath option. + if( bootclasspath != null ) + { + cp.append( bootclasspath ); + } + + if( extdirs != null ) + { + cp.addExtdirs( extdirs ); + } + + cp.append( classpath ); + cp.append( src ); + + cmd.createArgument().setPath( cp ); + + // kjc-1.5A doesn't support -encoding option now. + // but it will be supported near the feature. + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + + if( debug ) + { + cmd.createArgument().setValue( "-g" ); + } + + if( optimize ) + { + cmd.createArgument().setValue( "-O2" ); + } + + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + logAndAddFilesToCompile( cmd ); + return cmd; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Sj.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Sj.java new file mode 100644 index 000000000..3ab63f48b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Sj.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the sj compiler. Uses the defaults for + * DefaultCompilerAdapter + * + * @author Don Ferguson + */ +public class Sj extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the sj compiler from Symantec. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author don@bea.com + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using symantec java compiler", Project.MSG_VERBOSE ); + + Commandline cmd = setupJavacCommand(); + cmd.setExecutable( "sj" ); + + int firstFileName = cmd.size() - compileList.length; + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/And.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/And.java new file mode 100644 index 000000000..44b9810ac --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/And.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; + +/** + * <and> condition container.

      + * + * Iterates over all conditions and returns false as soon as one evaluates to + * false.

      + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class And extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + Enumeration enum = getConditions(); + while( enum.hasMoreElements() ) + { + Condition c = ( Condition )enum.nextElement(); + if( !c.eval() ) + { + return false; + } + } + return true; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Condition.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Condition.java new file mode 100644 index 000000000..fc589867c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Condition.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; + +import org.apache.myrmidon.api.TaskException; + +/** + * Interface for conditions to use inside the <condition> task. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface Condition +{ + /** + * Is this condition true? + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean eval() + throws TaskException; +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/ConditionBase.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/ConditionBase.java new file mode 100644 index 000000000..a8a13820d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/ConditionBase.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import java.util.NoSuchElementException; +import java.util.Vector; +import org.apache.tools.ant.ProjectComponent; +import org.apache.tools.ant.taskdefs.Available; +import org.apache.tools.ant.taskdefs.Checksum; +import org.apache.tools.ant.taskdefs.UpToDate; +import org.apache.myrmidon.framework.Os; + +/** + * Baseclass for the <condition> task as well as several conditions - + * ensures that the types of conditions inside the task and the "container" + * conditions are in sync. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public abstract class ConditionBase extends ProjectComponent +{ + private Vector conditions = new Vector(); + + /** + * Add an <and> condition "container". + * + * @param a The feature to be added to the And attribute + * @since 1.1 + */ + public void addAnd( And a ) + { + conditions.addElement( a ); + } + + /** + * Add an <available> condition. + * + * @param a The feature to be added to the Available attribute + * @since 1.1 + */ + public void addAvailable( Available a ) + { + conditions.addElement( a ); + } + + /** + * Add an <checksum> condition. + * + * @param c The feature to be added to the Checksum attribute + * @since 1.4 + */ + public void addChecksum( Checksum c ) + { + conditions.addElement( c ); + } + + /** + * Add an <equals> condition. + * + * @param e The feature to be added to the Equals attribute + * @since 1.1 + */ + public void addEquals( Equals e ) + { + conditions.addElement( e ); + } + + /** + * Add an <http> condition. + * + * @param h The feature to be added to the Http attribute + * @since 1.7 + */ + public void addHttp( Http h ) + { + conditions.addElement( h ); + } + + /** + * Add an <isset> condition. + * + * @param i The feature to be added to the IsSet attribute + * @since 1.1 + */ + public void addIsSet( IsSet i ) + { + conditions.addElement( i ); + } + + /** + * Add an <not> condition "container". + * + * @param n The feature to be added to the Not attribute + * @since 1.1 + */ + public void addNot( Not n ) + { + conditions.addElement( n ); + } + + /** + * Add an <or> condition "container". + * + * @param o The feature to be added to the Or attribute + * @since 1.1 + */ + public void addOr( Or o ) + { + conditions.addElement( o ); + } + + /** + * Add an <os> condition. + * + * @param o The feature to be added to the Os attribute + * @since 1.1 + */ + public void addOs( Os o ) + { + conditions.addElement( o ); + } + + /** + * Add a <socket> condition. + * + * @param s The feature to be added to the Socket attribute + * @since 1.7 + */ + public void addSocket( Socket s ) + { + conditions.addElement( s ); + } + + /** + * Add an <uptodate> condition. + * + * @param u The feature to be added to the Uptodate attribute + * @since 1.1 + */ + public void addUptodate( UpToDate u ) + { + conditions.addElement( u ); + } + + /** + * Iterate through all conditions. + * + * @return The Conditions value + * @since 1.1 + */ + protected final Enumeration getConditions() + { + return new ConditionEnumeration(); + } + + /** + * Count the conditions. + * + * @return Description of the Returned Value + * @since 1.1 + */ + protected int countConditions() + { + return conditions.size(); + } + + /** + * Inner class that configures those conditions with a project instance that + * need it. + * + * @author RT + * @since 1.1 + */ + private class ConditionEnumeration implements Enumeration + { + private int currentElement = 0; + + public boolean hasMoreElements() + { + return countConditions() > currentElement; + } + + public Object nextElement() + throws NoSuchElementException + { + Object o = null; + try + { + o = conditions.elementAt( currentElement++ ); + } + catch( ArrayIndexOutOfBoundsException e ) + { + throw new NoSuchElementException(); + } + + if( o instanceof ProjectComponent ) + { + ( ( ProjectComponent )o ).setProject( getProject() ); + } + return o; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Equals.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Equals.java new file mode 100644 index 000000000..306f7928f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Equals.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; + +/** + * Simple String comparison condition. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Equals implements Condition +{ + + private String arg1, arg2; + + public void setArg1( String a1 ) + { + arg1 = a1; + } + + public void setArg2( String a2 ) + { + arg2 = a2; + } + + public boolean eval() + throws BuildException + { + if( arg1 == null || arg2 == null ) + { + throw new BuildException( "both arg1 and arg2 are required in equals" ); + } + return arg1.equals( arg2 ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Http.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Http.java new file mode 100644 index 000000000..b70aa228a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Http.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition to wait for a HTTP request to succeed. Its attribute(s) are: url - + * the URL of the request. + * + * @author Denis Hennessy + */ +public class Http extends ProjectComponent implements Condition +{ + String spec = null; + + public void setUrl( String url ) + { + spec = url; + } + + public boolean eval() + throws BuildException + { + if( spec == null ) + { + throw new BuildException( "No url specified in HTTP task" ); + } + log( "Checking for " + spec, Project.MSG_VERBOSE ); + try + { + URL url = new URL( spec ); + try + { + URLConnection conn = url.openConnection(); + if( conn instanceof HttpURLConnection ) + { + HttpURLConnection http = ( HttpURLConnection )conn; + int code = http.getResponseCode(); + log( "Result code for " + spec + " was " + code, Project.MSG_VERBOSE ); + if( code > 0 && code < 500 ) + { + return true; + } + else + { + return false; + } + } + } + catch( java.io.IOException e ) + { + return false; + } + } + catch( MalformedURLException e ) + { + throw new BuildException( "Badly formed URL: " + spec, e ); + } + return true; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/IsSet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/IsSet.java new file mode 100644 index 000000000..e7a6a11be --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/IsSet.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition that tests whether a given property has been set. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class IsSet extends ProjectComponent implements Condition +{ + private String property; + + public void setProperty( String p ) + { + property = p; + } + + public boolean eval() + throws BuildException + { + if( property == null ) + { + throw new BuildException( "No property specified for isset condition" ); + } + + return getProject().getProperty( property ) != null; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Not.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Not.java new file mode 100644 index 000000000..1af3b0bdb --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Not.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; + +/** + * <not> condition. Evaluates to true if the single condition nested into + * it is false and vice versa. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Not extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + return !( ( Condition )getConditions().nextElement() ).eval(); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Or.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Or.java new file mode 100644 index 000000000..62ca4641c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Or.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; + +/** + * <or> condition container.

      + * + * Iterates over all conditions and returns true as soon as one evaluates to + * true.

      + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Or extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + Enumeration enum = getConditions(); + while( enum.hasMoreElements() ) + { + Condition c = ( Condition )enum.nextElement(); + if( c.eval() ) + { + return true; + } + } + return false; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Socket.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Socket.java new file mode 100644 index 000000000..b1e47526a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Socket.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition to wait for a TCP/IP socket to have a listener. Its attribute(s) + * are: server - the name of the server. port - the port number of the socket. + * + * @author Denis Hennessy + */ +public class Socket extends ProjectComponent implements Condition +{ + String server = null; + int port = 0; + + public void setPort( int port ) + { + this.port = port; + } + + public void setServer( String server ) + { + this.server = server; + } + + public boolean eval() + throws BuildException + { + if( server == null ) + { + throw new BuildException( "No server specified in Socket task" ); + } + if( port == 0 ) + { + throw new BuildException( "No port specified in Socket task" ); + } + log( "Checking for listener at " + server + ":" + port, Project.MSG_VERBOSE ); + try + { + java.net.Socket socket = new java.net.Socket( server, port ); + } + catch( IOException e ) + { + return false; + } + return true; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/defaults.properties b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/defaults.properties new file mode 100644 index 000000000..c680c8f92 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/defaults.properties @@ -0,0 +1,141 @@ +# standard ant tasks +mkdir=org.apache.tools.ant.taskdefs.Mkdir +javac=org.apache.tools.ant.taskdefs.Javac +chmod=org.apache.tools.ant.taskdefs.Chmod +delete=org.apache.tools.ant.taskdefs.Delete +copy=org.apache.tools.ant.taskdefs.Copy +move=org.apache.tools.ant.taskdefs.Move +jar=org.apache.tools.ant.taskdefs.Jar +rmic=org.apache.tools.ant.taskdefs.Rmic +cvs=org.apache.tools.ant.taskdefs.Cvs +get=org.apache.tools.ant.taskdefs.Get +unzip=org.apache.tools.ant.taskdefs.Expand +unjar=org.apache.tools.ant.taskdefs.Expand +unwar=org.apache.tools.ant.taskdefs.Expand +echo=org.apache.tools.ant.taskdefs.Echo +javadoc=org.apache.tools.ant.taskdefs.Javadoc +zip=org.apache.tools.ant.taskdefs.Zip +gzip=org.apache.tools.ant.taskdefs.GZip +gunzip=org.apache.tools.ant.taskdefs.GUnzip +replace=org.apache.tools.ant.taskdefs.Replace +java=org.apache.tools.ant.taskdefs.Java +tstamp=org.apache.tools.ant.taskdefs.Tstamp +property=org.apache.tools.ant.taskdefs.Property +taskdef=org.apache.tools.ant.taskdefs.Taskdef +ant=org.apache.tools.ant.taskdefs.Ant +exec=org.apache.tools.ant.taskdefs.ExecTask +tar=org.apache.tools.ant.taskdefs.Tar +untar=org.apache.tools.ant.taskdefs.Untar +available=org.apache.tools.ant.taskdefs.Available +filter=org.apache.tools.ant.taskdefs.Filter +fixcrlf=org.apache.tools.ant.taskdefs.FixCRLF +patch=org.apache.tools.ant.taskdefs.Patch +style=org.apache.tools.ant.taskdefs.XSLTProcess +touch=org.apache.tools.ant.taskdefs.Touch +signjar=org.apache.tools.ant.taskdefs.SignJar +genkey=org.apache.tools.ant.taskdefs.GenerateKey +antstructure=org.apache.tools.ant.taskdefs.AntStructure +execon=org.apache.tools.ant.taskdefs.ExecuteOn +antcall=org.apache.tools.ant.taskdefs.CallTarget +sql=org.apache.tools.ant.taskdefs.SQLExec +mail=org.apache.tools.ant.taskdefs.SendEmail +fail=org.apache.tools.ant.taskdefs.Exit +war=org.apache.tools.ant.taskdefs.War +uptodate=org.apache.tools.ant.taskdefs.UpToDate +apply=org.apache.tools.ant.taskdefs.Transform +record=org.apache.tools.ant.taskdefs.Recorder +cvspass=org.apache.tools.ant.taskdefs.CVSPass +typedef=org.apache.tools.ant.taskdefs.Typedef +sleep=org.apache.tools.ant.taskdefs.Sleep +pathconvert=org.apache.tools.ant.taskdefs.PathConvert +ear=org.apache.tools.ant.taskdefs.Ear +parallel=org.apache.tools.ant.taskdefs.Parallel +sequential=org.apache.tools.ant.taskdefs.Sequential +condition=org.apache.tools.ant.taskdefs.ConditionTask +dependset=org.apache.tools.ant.taskdefs.DependSet +bzip2=org.apache.tools.ant.taskdefs.BZip2 +bunzip2=org.apache.tools.ant.taskdefs.BUnzip2 +checksum=org.apache.tools.ant.taskdefs.Checksum +waitfor=org.apache.tools.ant.taskdefs.WaitFor +input=org.apache.tools.ant.taskdefs.Input +manifest=org.apache.tools.ant.taskdefs.Manifest + +# optional tasks +script=org.apache.tools.ant.taskdefs.optional.Script +netrexxc=org.apache.tools.ant.taskdefs.optional.NetRexxC +renameext=org.apache.tools.ant.taskdefs.optional.RenameExtensions +ejbc=org.apache.tools.ant.taskdefs.optional.ejb.Ejbc +ddcreator=org.apache.tools.ant.taskdefs.optional.ejb.DDCreator +wlrun=org.apache.tools.ant.taskdefs.optional.ejb.WLRun +wlstop=org.apache.tools.ant.taskdefs.optional.ejb.WLStop +vssget=org.apache.tools.ant.taskdefs.optional.vss.MSVSSGET +ejbjar=org.apache.tools.ant.taskdefs.optional.ejb.EjbJar +mparse=org.apache.tools.ant.taskdefs.optional.metamata.MParse +mmetrics=org.apache.tools.ant.taskdefs.optional.metamata.MMetrics +maudit=org.apache.tools.ant.taskdefs.optional.metamata.MAudit +junit=org.apache.tools.ant.taskdefs.optional.junit.JUnitTask +cab=org.apache.tools.ant.taskdefs.optional.Cab +ftp=org.apache.tools.ant.taskdefs.optional.net.FTP +icontract=org.apache.tools.ant.taskdefs.optional.IContract +javacc=org.apache.tools.ant.taskdefs.optional.javacc.JavaCC +jjtree=org.apache.tools.ant.taskdefs.optional.javacc.JJTree +starteam=org.apache.tools.ant.taskdefs.optional.scm.AntStarTeamCheckOut +wljspc=org.apache.tools.ant.taskdefs.optional.jsp.WLJspc +jlink=org.apache.tools.ant.taskdefs.optional.jlink.JlinkTask +native2ascii=org.apache.tools.ant.taskdefs.optional.Native2Ascii +propertyfile=org.apache.tools.ant.taskdefs.optional.PropertyFile +depend=org.apache.tools.ant.taskdefs.optional.depend.Depend +antlr=org.apache.tools.ant.taskdefs.optional.ANTLR +vajload=org.apache.tools.ant.taskdefs.optional.ide.VAJLoadProjects +vajexport=org.apache.tools.ant.taskdefs.optional.ide.VAJExport +vajimport=org.apache.tools.ant.taskdefs.optional.ide.VAJImport +telnet=org.apache.tools.ant.taskdefs.optional.net.TelnetTask +csc=org.apache.tools.ant.taskdefs.optional.dotnet.CSharp +ilasm=org.apache.tools.ant.taskdefs.optional.dotnet.Ilasm +stylebook=org.apache.tools.ant.taskdefs.optional.StyleBook +test=org.apache.tools.ant.taskdefs.optional.Test +pvcs=org.apache.tools.ant.taskdefs.optional.pvcs.Pvcs +p4change=org.apache.tools.ant.taskdefs.optional.perforce.P4Change +p4label=org.apache.tools.ant.taskdefs.optional.perforce.P4Label +p4have=org.apache.tools.ant.taskdefs.optional.perforce.P4Have +p4sync=org.apache.tools.ant.taskdefs.optional.perforce.P4Sync +p4edit=org.apache.tools.ant.taskdefs.optional.perforce.P4Edit +p4submit=org.apache.tools.ant.taskdefs.optional.perforce.P4Submit +p4counter=org.apache.tools.ant.taskdefs.optional.perforce.P4Counter +javah=org.apache.tools.ant.taskdefs.optional.Javah +ccupdate=org.apache.tools.ant.taskdefs.optional.clearcase.CCUpdate +cccheckout=org.apache.tools.ant.taskdefs.optional.clearcase.CCCheckout +cccheckin=org.apache.tools.ant.taskdefs.optional.clearcase.CCCheckin +ccuncheckout=org.apache.tools.ant.taskdefs.optional.clearcase.CCUnCheckout +sound=org.apache.tools.ant.taskdefs.optional.sound.SoundTask +junitreport=org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator +vsslabel=org.apache.tools.ant.taskdefs.optional.vss.MSVSSLABEL +vsshistory=org.apache.tools.ant.taskdefs.optional.vss.MSVSSHISTORY +blgenclient=org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient +rpm=org.apache.tools.ant.taskdefs.optional.Rpm +xmlvalidate=org.apache.tools.ant.taskdefs.optional.XMLValidateTask +vsscheckin=org.apache.tools.ant.taskdefs.optional.vss.MSVSSCHECKIN +vsscheckout=org.apache.tools.ant.taskdefs.optional.vss.MSVSSCHECKOUT +iplanet-ejbc=org.apache.tools.ant.taskdefs.optional.ejb.IPlanetEjbcTask +jdepend=org.apache.tools.ant.taskdefs.optional.jdepend.JDependTask +mimemail=org.apache.tools.ant.taskdefs.optional.net.MimeMail +ccmcheckin=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckin +ccmcheckout=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckout +ccmcheckintask=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckinDefault +ccmreconfigure=org.apache.tools.ant.taskdefs.optional.ccm.CCMReconfigure +ccmcreatetask=org.apache.tools.ant.taskdefs.optional.ccm.CCMCreateTask +jpcoverage=org.apache.tools.ant.taskdefs.optional.sitraka.Coverage +jpcovmerge=org.apache.tools.ant.taskdefs.optional.sitraka.CovMerge +jpcovreport=org.apache.tools.ant.taskdefs.optional.sitraka.CovReport +p4add=org.apache.tools.ant.taskdefs.optional.perforce.P4Add +jspc=org.apache.tools.ant.taskdefs.optional.jsp.JspC +replaceregexp=org.apache.tools.ant.taskdefs.optional.ReplaceRegExp +translate=org.apache.tools.ant.taskdefs.optional.i18n.Translate + +# deprecated ant tasks (kept for back compatibility) +javadoc2=org.apache.tools.ant.taskdefs.Javadoc +#compileTask=org.apache.tools.ant.taskdefs.CompileTask +copydir=org.apache.tools.ant.taskdefs.Copydir +copyfile=org.apache.tools.ant.taskdefs.Copyfile +deltree=org.apache.tools.ant.taskdefs.Deltree +rename=org.apache.tools.ant.taskdefs.Rename diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ANTLR.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ANTLR.java new file mode 100644 index 000000000..7269bf2b2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ANTLR.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.URL; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ExitException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteJava; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; + +/** + * ANTLR task. + * + * @author Erik Meade + * @author <classpath> allows classpath to be set because a + * directory might be given for Antlr debug... + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return commandline.createClasspath( project ).createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg() + { + return commandline.createVmArgument(); + } + + public void execute() + throws BuildException + { + validateAttributes(); + + //TODO: use ANTLR to parse the grammer file to do this. + if( target.lastModified() > getGeneratedFile().lastModified() ) + { + commandline.createArgument().setValue( "-o" ); + commandline.createArgument().setValue( outputDirectory.toString() ); + commandline.createArgument().setValue( target.toString() ); + + if( fork ) + { + log( "Forking " + commandline.toString(), Project.MSG_VERBOSE ); + int err = run( commandline.getCommandline() ); + if( err == 1 ) + { + throw new BuildException( "ANTLR returned: " + err, location ); + } + } + else + { + ExecuteJava exe = new ExecuteJava(); + exe.setJavaCommand( commandline.getJavaCommand() ); + exe.setClasspath( commandline.getClasspath() ); + try + { + exe.execute( project ); + } + catch( ExitException e ) + { + if( e.getStatus() != 0 ) + { + throw new BuildException( "ANTLR returned: " + e.getStatus(), location ); + } + } + } + } + } + + /** + * Adds the jars or directories containing Antlr this should make the forked + * JVM work without having to specify it directly. + * + * @exception BuildException Description of Exception + */ + public void init() + throws BuildException + { + addClasspathEntry( "/antlr/Tool.class" ); + } + + /** + * Search for the given resource and add the directory or archive that + * contains it to the classpath.

      + * + * Doesn't work for archives in JDK 1.1 as the URL returned by getResource + * doesn't contain the name of the archive.

      + * + * @param resource The feature to be added to the ClasspathEntry attribute + */ + protected void addClasspathEntry( String resource ) + { + URL url = getClass().getResource( resource ); + if( url != null ) + { + String u = url.toString(); + if( u.startsWith( "jar:file:" ) ) + { + int pling = u.indexOf( "!" ); + String jarName = u.substring( 9, pling ); + log( "Implicitly adding " + jarName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( jarName ) ).getAbsolutePath() ) ); + } + else if( u.startsWith( "file:" ) ) + { + int tail = u.indexOf( resource ); + String dirName = u.substring( 5, tail ); + log( "Implicitly adding " + dirName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( dirName ) ).getAbsolutePath() ) ); + } + else + { + log( "Don\'t know how to handle resource URL " + u, + Project.MSG_DEBUG ); + } + } + else + { + log( "Couldn\'t find " + resource, Project.MSG_DEBUG ); + } + } + + private File getGeneratedFile() + throws BuildException + { + String generatedFileName = null; + try + { + BufferedReader in = new BufferedReader( new FileReader( target ) ); + String line; + while( ( line = in.readLine() ) != null ) + { + int extendsIndex = line.indexOf( " extends " ); + if( line.startsWith( "class " ) && extendsIndex > -1 ) + { + generatedFileName = line.substring( 6, extendsIndex ).trim(); + break; + } + } + in.close(); + } + catch( Exception e ) + { + throw new BuildException( "Unable to determine generated class", e ); + } + if( generatedFileName == null ) + { + throw new BuildException( "Unable to determine generated class" ); + } + return new File( outputDirectory, generatedFileName + ".java" ); + } + + /** + * execute in a forked VM + * + * @param command Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int run( String[] command ) + throws BuildException + { + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), null ); + exe.setAntRun( project ); + if( workingdir != null ) + { + exe.setWorkingDirectory( workingdir ); + } + exe.setCommandline( command ); + try + { + return exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + private void validateAttributes() + throws BuildException + { + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // if no output directory is specified, used the target's directory + if( outputDirectory == null ) + { + String fileName = target.toString(); + setOutputdirectory( new File( target.getParent() ) ); + } + if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "Invalid output directory: " + outputDirectory ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java new file mode 100644 index 000000000..53d6d80ed --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.exolab.adaptx.xslt.XSLTProcessor; +import org.exolab.adaptx.xslt.XSLTReader; +import org.exolab.adaptx.xslt.XSLTStylesheet; + +/** + * @author
      Arnaud Blandin + * @version $Revision$ $Date$ + */ +public class AdaptxLiaison implements XSLTLiaison +{ + + protected XSLTProcessor processor; + protected XSLTStylesheet xslSheet; + + public AdaptxLiaison() + { + processor = new XSLTProcessor(); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File fileName ) + throws Exception + { + XSLTReader xslReader = new XSLTReader(); + xslSheet = xslReader.read( fileName.getAbsolutePath() ); + } + + public void addParam( String name, String expression ) + { + processor.setProperty( name, expression ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileOutputStream fos = new FileOutputStream( outfile ); + OutputStreamWriter out = new OutputStreamWriter( fos, "UTF8" ); + processor.process( infile.getAbsolutePath(), xslSheet, out ); + } + +}//-- AdaptxLiaison diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Cab.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Cab.java new file mode 100644 index 000000000..65510cf56 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Cab.java @@ -0,0 +1,349 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.FileUtils; + + +/** + * Create a CAB archive. + * + * @author Roger Vaughn + * rvaughn@seaconinc.com + */ + +public class Cab extends MatchingTask +{ + private Vector filesets = new Vector(); + private boolean doCompress = true; + private boolean doVerbose = false; + + protected String archiveType = "cab"; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + private File baseDir; + + private File cabFile; + private String cmdOptions; + + /** + * This is the base directory to look in for things to cab. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * This is the name/location of where to create the .cab file. + * + * @param cabFile The new Cabfile value + */ + public void setCabfile( File cabFile ) + { + this.cabFile = cabFile; + } + + /** + * Sets whether we want to compress the files or only store them. + * + * @param compress The new Compress value + */ + public void setCompress( boolean compress ) + { + doCompress = compress; + } + + /** + * Sets additional cabarc options that aren't supported directly. + * + * @param options The new Options value + */ + public void setOptions( String options ) + { + cmdOptions = options; + } + + /** + * Sets whether we want to see or suppress cabarc output. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + doVerbose = verbose; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + public void execute() + throws BuildException + { + + checkConfiguration(); + + Vector files = getFileList(); + + // quick exit if the target is up to date + if( isUpToDate( files ) ) + return; + + log( "Building " + archiveType + ": " + cabFile.getAbsolutePath() ); + + if( !Os.isFamily( "windows" ) ) + { + log( "Using listcab/libcabinet", Project.MSG_VERBOSE ); + + StringBuffer sb = new StringBuffer(); + + Enumeration fileEnum = files.elements(); + + while( fileEnum.hasMoreElements() ) + { + sb.append( fileEnum.nextElement() ).append( "\n" ); + } + sb.append( "\n" ).append( cabFile.getAbsolutePath() ).append( "\n" ); + + try + { + Process p = Runtime.getRuntime().exec( "listcab" ); + OutputStream out = p.getOutputStream(); + out.write( sb.toString().getBytes() ); + out.flush(); + out.close(); + } + catch( IOException ex ) + { + String msg = "Problem creating " + cabFile + " " + ex.getMessage(); + throw new BuildException( msg ); + } + } + else + { + try + { + File listFile = createListFile( files ); + ExecTask exec = createExec(); + File outFile = null; + + // die if cabarc fails + exec.setFailonerror( true ); + exec.setDir( baseDir ); + + if( !doVerbose ) + { + outFile = fileUtils.createTempFile( "ant", "", null ); + exec.setOutput( outFile ); + } + + exec.setCommand( createCommand( listFile ) ); + exec.execute(); + + if( outFile != null ) + { + outFile.delete(); + } + + listFile.delete(); + } + catch( IOException ioe ) + { + String msg = "Problem creating " + cabFile + " " + ioe.getMessage(); + throw new BuildException( msg ); + } + } + } + + /** + * Get the complete list of files to be included in the cab. Filenames are + * gathered from filesets if any have been added, otherwise from the + * traditional include parameters. + * + * @return The FileList value + * @exception BuildException Description of Exception + */ + protected Vector getFileList() + throws BuildException + { + Vector files = new Vector(); + + if( filesets.size() == 0 ) + { + // get files from old methods - includes and nested include + appendFiles( files, super.getDirectoryScanner( baseDir ) ); + } + else + { + // get files from filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + if( fs != null ) + { + appendFiles( files, fs.getDirectoryScanner( project ) ); + } + } + } + + return files; + } + + /** + * Check to see if the target is up to date with respect to input files. + * + * @param files Description of Parameter + * @return true if the cab file is newer than its dependents. + */ + protected boolean isUpToDate( Vector files ) + { + boolean upToDate = true; + for( int i = 0; i < files.size() && upToDate; i++ ) + { + String file = files.elementAt( i ).toString(); + if( new File( baseDir, file ).lastModified() > + cabFile.lastModified() ) + upToDate = false; + } + return upToDate; + } + + /** + * Append all files found by a directory scanner to a vector. + * + * @param files Description of Parameter + * @param ds Description of Parameter + */ + protected void appendFiles( Vector files, DirectoryScanner ds ) + { + String[] dsfiles = ds.getIncludedFiles(); + + for( int i = 0; i < dsfiles.length; i++ ) + { + files.addElement( dsfiles[i] ); + } + } + + /* + * I'm not fond of this pattern: "sub-method expected to throw + * task-cancelling exceptions". It feels too much like programming + * for side-effects to me... + */ + protected void checkConfiguration() + throws BuildException + { + if( baseDir == null ) + { + throw new BuildException( "basedir attribute must be set!" ); + } + if( !baseDir.exists() ) + { + throw new BuildException( "basedir does not exist!" ); + } + if( cabFile == null ) + { + throw new BuildException( "cabfile attribute must be set!" ); + } + } + + /** + * Create the cabarc command line to use. + * + * @param listFile Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline createCommand( File listFile ) + { + Commandline command = new Commandline(); + command.setExecutable( "cabarc" ); + command.createArgument().setValue( "-r" ); + command.createArgument().setValue( "-p" ); + + if( !doCompress ) + { + command.createArgument().setValue( "-m" ); + command.createArgument().setValue( "none" ); + } + + if( cmdOptions != null ) + { + command.createArgument().setLine( cmdOptions ); + } + + command.createArgument().setValue( "n" ); + command.createArgument().setFile( cabFile ); + command.createArgument().setValue( "@" + listFile.getAbsolutePath() ); + + return command; + } + + /** + * Create a new exec delegate. The delegate task is populated so that it + * appears in the logs to be the same task as this one. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecTask createExec() + throws BuildException + { + ExecTask exec = ( ExecTask )project.createTask( "exec" ); + exec.setOwningTarget( this.getOwningTarget() ); + exec.setTaskName( this.getTaskName() ); + exec.setDescription( this.getDescription() ); + + return exec; + } + + /** + * Creates a list file. This temporary file contains a list of all files to + * be included in the cab, one file per line. + * + * @param files Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + protected File createListFile( Vector files ) + throws IOException + { + File listFile = fileUtils.createTempFile( "ant", "", null ); + + PrintWriter writer = new PrintWriter( new FileOutputStream( listFile ) ); + + for( int i = 0; i < files.size(); i++ ) + { + writer.println( files.elementAt( i ).toString() ); + } + writer.close(); + + return listFile; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/IContract.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/IContract.java new file mode 100644 index 000000000..cc52a4708 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/IContract.java @@ -0,0 +1,1105 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Date; +import java.util.Properties; +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.taskdefs.Java; +import org.apache.tools.ant.taskdefs.Javac; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.Mkdir; +import org.apache.tools.ant.taskdefs.compilers.DefaultCompilerAdapter; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Instruments Java classes with + * iContract DBC preprocessor.
      + * The task can generate a properties file for iControl , a graphical + * user interface that lets you turn on/off assertions. iControl generates a + * control file that you can refer to from this task using the controlfile + * attribute.

      + * + * Thanks to Rainer Schmitz for enhancements and comments. + * + * @author Aslak Hellesøy

      + * + * + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * srcdir + * + * + * + * Location of the java files. + * + * + * + * Yes + * + * + * + * + * + * + * + * instrumentdir + * + * + * + * Indicates where the instrumented source files should go. + * + * + * + * Yes + * + * + * + * + * + * + * + * repositorydir + * + * + * + * Indicates where the repository source files should go. + * + * + * + * Yes + * + * + * + * + * + * + * + * builddir + * + * + * + * Indicates where the compiled instrumented classes should go. + * Defaults to the value of instrumentdir.

      NOTE: Don't + * use the same directory for compiled instrumented classes and + * uninstrumented classes. It will break the dependency checking. + * (Classes will not be reinstrumented if you change them). + * + * + * + * No + * + * + * + * + * + * + * + * repositorybuilddir + * + * + * + * Indicates where the compiled repository classes should go. + * Defaults to the value of repositorydir. + * + * + * + * No + * + * + * + * + * + * + * + * pre + * + * + * + * Indicates whether or not to instrument for preconditions. Defaults + * to true unless controlfile is specified, in which + * case it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * post + * + * + * + * Indicates whether or not to instrument for postconditions. + * Defaults to true unless controlfile is specified, in + * which case it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * invariant + * + * + * + * Indicates whether or not to instrument for invariants. Defaults to + * true unless controlfile is specified, in which case + * it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * failthrowable + * + * + * + * The full name of the Throwable (Exception) that should be thrown + * when an assertion is violated. Defaults to java.lang.Error + * + * + * + * + * No + * + * + * + * + * + * + * + * verbosity + * + * + * + * Indicates the verbosity level of iContract. Any combination of + * error*,warning*,note*,info*,progress*,debug* (comma + * separated) can be used. Defaults to error* + * + * + * + * No + * + * + * + * + * + * + * + * quiet + * + * + * + * Indicates if iContract should be quiet. Turn it off if many your + * classes extend uninstrumented classes and you don't want warnings + * about this. Defaults to false + * + * + * + * No + * + * + * + * + * + * + * + * updateicontrol + * + * + * + * If set to true, it indicates that the properties file for iControl + * in the current directory should be updated (or created if it + * doesn't exist). Defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * controlfile + * + * + * + * The name of the control file to pass to iContract. Consider using + * iControl to generate the file. Default is not to pass a file. + * + * + * + * + * Only if updateicontrol=true + * + * + * + * + * + * + * + * classdir + * + * + * + * Indicates where compiled (unistrumented) classes are located. This + * is required in order to properly update the icontrol.properties + * file, not for instrumentation. + * + * + * + * Only if updateicontrol=true + * + * + * + * + * + * + * + * targets + * + * + * + * Name of the file that will be generated by this task, which lists + * all the classes that iContract will instrument. If specified, the + * file will not be deleted after execution. If not specified, a file + * will still be created, but it will be deleted after execution. + * + * + * + * + * No + * + * + * + * + * + *

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

      + * + * Nested includes and excludes are also supported.

      + * + * Example:

      + * <icontract
      + *    srcdir="${build.src}"
      + *    instrumentdir="${build.instrument}"
      + *    repositorydir="${build.repository}"
      + *    builddir="${build.instrclasses}"
      + *    updateicontrol="true"
      + *    classdir="${build.classes}"
      + *    controlfile="control"
      + *    targets="targets"
      + *    verbosity="error*,warning*"
      + *    quiet="true"
      + * >
      + *    <classpath refid="compile-classpath"/>
      + * </icontract>
      + * 
      + */ +public class IContract extends MatchingTask +{ + + private final static String ICONTROL_PROPERTIES_HEADER = + " You might want to set classRoot to point to your normal compilation class root directory."; + + private final static String ICONTROL_PROPERTIES_MESSAGE = + "You should probably modify icontrol.properties' classRoot to where comiled (uninstrumented) classes go."; + + /** + * \ on windows, / on linux/unix + */ + private final static 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 src root + */ + private File instrumentDir = null; + + /** + * instrumentation build root + */ + private File buildDir = null; + + /** + * repository src root + */ + private File repositoryDir = null; + + /** + * repository build root + */ + private File repBuildDir = 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*"; + + /** + * The -q option + */ + private boolean quiet = false; + + /** + * 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; + private boolean preModified = false; + + /** + * Indicates whether or not to instrument for postconditions + */ + private boolean post = true; + private boolean postModified = false; + + /** + * Indicates whether or not to instrument for invariants + */ + private boolean invariant = true; + private boolean invariantModified = false; + + /** + * 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 = false; + + /** + * Indicates the name of a properties file (intentionally for iControl) + * where the classpath property should be updated. + */ + private boolean updateIcontrol = false; + + /** + * Regular compilation class root + */ + private File classDir = null; + + /** + * Sets the build directory for instrumented classes + * + * @param buildDir the build directory + */ + public void setBuilddir( File buildDir ) + { + this.buildDir = buildDir; + } + + /** + * Sets the class directory (uninstrumented classes) + * + * @param classDir The new Classdir value + */ + public void setClassdir( File classDir ) + { + this.classDir = classDir; + } + + /** + * Sets the classpath to be used for invocation of iContract. + * + * @param path The new Classpath value + * @path the classpath + */ + public void setClasspath( Path path ) + { + createClasspath().append( path ); + } + + /** + * Adds a reference to a classpath defined elsewhere. + * + * @param reference referenced classpath + */ + public void setClasspathRef( Reference reference ) + { + createClasspath().setRefid( reference ); + } + + /** + * Sets the control file to pass to iContract. + * + * @param controlFile the control file + */ + public void setControlfile( File controlFile ) + { + if( !controlFile.exists() ) + { + log( "WARNING: Control file " + controlFile.getAbsolutePath() + " doesn't exist. iContract will be run without control file." ); + } + this.controlFile = controlFile; + } + + /** + * Sets the Throwable (Exception) to be thrown on assertion violation + * + * @param clazz the fully qualified Throwable class name + */ + public void setFailthrowable( String clazz ) + { + this.failThrowable = clazz; + } + + /** + * Sets the instrumentation directory + * + * @param instrumentDir the source directory + */ + public void setInstrumentdir( File instrumentDir ) + { + this.instrumentDir = instrumentDir; + if( this.buildDir == null ) + { + setBuilddir( instrumentDir ); + } + } + + /** + * Turns on/off invariant instrumentation + * + * @param invariant true turns it on + */ + public void setInvariant( boolean invariant ) + { + this.invariant = invariant; + invariantModified = true; + } + + /** + * Turns on/off postcondition instrumentation + * + * @param post true turns it on + */ + public void setPost( boolean post ) + { + this.post = post; + postModified = true; + } + + /** + * Turns on/off precondition instrumentation + * + * @param pre true turns it on + */ + public void setPre( boolean pre ) + { + this.pre = pre; + preModified = true; + } + + /** + * Tells iContract to be quiet. + * + * @param quiet true if iContract should be quiet. + */ + public void setQuiet( boolean quiet ) + { + this.quiet = quiet; + } + + /** + * Sets the build directory for instrumented classes + * + * @param repBuildDir The new Repbuilddir value + */ + public void setRepbuilddir( File repBuildDir ) + { + this.repBuildDir = repBuildDir; + } + + /** + * Sets the build directory for repository classes + * + * @param repositoryDir the source directory + */ + public void setRepositorydir( File repositoryDir ) + { + this.repositoryDir = repositoryDir; + if( this.repBuildDir == null ) + { + setRepbuilddir( repositoryDir ); + } + } + + /** + * Sets the source directory + * + * @param srcDir the source directory + */ + public void setSrcdir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Sets the name of the file where targets will be written. That is the file + * that tells iContract what files to process. + * + * @param targets the targets file name + */ + public void setTargets( File targets ) + { + this.targets = targets; + } + + /** + * Decides whether or not to update iControl properties file + * + * @param updateIcontrol true if iControl properties file should be updated + */ + public void setUpdateicontrol( boolean updateIcontrol ) + { + this.updateIcontrol = updateIcontrol; + } + + /** + * 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 verbosity verbosity level + */ + public void setVerbosity( String verbosity ) + { + this.verbosity = verbosity; + } + + /** + * Creates a nested classpath element + * + * @return the nested classpath element + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( getProject() ); + } + return classpath; + } + + /** + * Executes the task + * + * @exception BuildException if the instrumentation fails + */ + public void execute() + throws BuildException + { + preconditions(); + scan(); + if( dirty ) + { + + // turn off assertions if we're using controlfile, unless they are not explicitly set. + boolean useControlFile = ( controlFile != null ) && controlFile.exists(); + if( useControlFile && !preModified ) + { + pre = false; + } + if( useControlFile && !postModified ) + { + post = false; + } + if( useControlFile && !invariantModified ) + { + invariant = false; + } + // issue warning if pre,post or invariant is used together with controlfile + if( ( pre || post || invariant ) && controlFile != null ) + { + log( "WARNING: specifying pre,post or invariant will override control file settings" ); + } + + + // 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( buildDir ); + mkdir.execute(); + mkdir.setDir( repositoryDir ); + mkdir.execute(); + + // Set the classpath that is needed for regular Javac compilation + Path baseClasspath = createClasspath(); + + // Might need to add the core classes if we're not using Sun's Javac (like Jikes) + String compiler = project.getProperty( "build.compiler" ); + ClasspathHelper classpathHelper = new ClasspathHelper( compiler ); + classpathHelper.modify( baseClasspath ); + + // 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() ) ); + afterInstrumentationClasspath.append( new Path( getProject(), buildDir.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() ) ); + repositoryClasspath.append( new Path( getProject(), buildDir.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() ) ); + iContractClasspath.append( new Path( getProject(), buildDir.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( "\"" ).append( icCompiler ).append( " -classpath " ).append( beforeInstrumentationClasspath ).append( "\" " ); + args.append( "-c" ).append( "\"" ).append( icCompiler ).append( " -classpath " ).append( afterInstrumentationClasspath ).append( " -d " ).append( buildDir ).append( "\" " ); + args.append( "-n" ).append( "\"" ).append( icCompiler ).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( quiet ? "-q " : "" ); + args.append( instrumentall ? "-a " : "" );// reinstrument everything if controlFile exists and is newer than any class + args.append( "@" ).append( targets.getAbsolutePath() ); + iContract.createArg().setLine( args.toString() ); + +//System.out.println( "JAVA -classpath " + iContractClasspath + " com.reliablesystems.iContract.Tool " + args.toString() ); + + // update iControlProperties if it's set. + if( updateIcontrol ) + { + Properties iControlProps = new Properties(); + try + {// to read existing propertiesfile + iControlProps.load( new FileInputStream( "icontrol.properties" ) ); + } + catch( IOException e ) + { + log( "File icontrol.properties not found. That's ok. Writing a default one." ); + } + iControlProps.setProperty( "sourceRoot", srcDir.getAbsolutePath() ); + iControlProps.setProperty( "classRoot", classDir.getAbsolutePath() ); + iControlProps.setProperty( "classpath", afterInstrumentationClasspath.toString() ); + iControlProps.setProperty( "controlFile", controlFile.getAbsolutePath() ); + iControlProps.setProperty( "targetsFile", targets.getAbsolutePath() ); + + try + {// to read existing propertiesfile + iControlProps.store( new FileOutputStream( "icontrol.properties" ), ICONTROL_PROPERTIES_HEADER ); + log( "Updated icontrol.properties" ); + } + catch( IOException e ) + { + log( "Couldn't write icontrol.properties." ); + } + } + + // do it! + 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 + {// not dirty + //log( "Nothing to do. Everything up to date." ); + } + } + + + /** + * Creates the -m option based on the values of controlFile, pre, post and + * invariant. + * + * @return Description of the Returned Value + */ + private final String directiveString() + { + StringBuffer sb = new StringBuffer(); + boolean comma = false; + + boolean useControlFile = ( controlFile != null ) && controlFile.exists(); + if( useControlFile || pre || post || invariant ) + { + sb.append( "-m" ); + } + if( useControlFile ) + { + 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" ); + } + sb.append( " " ); + return sb.toString(); + } + + /** + * Checks that the required attributes are set. + * + * @exception BuildException Description of Exception + */ + 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 ); + } + if( updateIcontrol == true && classDir == null ) + { + throw new BuildException( "classdir attribute must be specified when updateicontrol=true!", location ); + } + if( updateIcontrol == true && controlFile == null ) + { + throw new BuildException( "controlfile attribute must be specified when updateicontrol=true!", 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 or the control 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. + * + * @exception BuildException Description of Exception + */ + private void scan() + throws BuildException + { + long now = ( new Date() ).getTime(); + + DirectoryScanner ds = null; + + ds = getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + + FileOutputStream targetOutputStream = null; + PrintStream targetPrinter = null; + boolean writeTargets = false; + try + { + if( targets == null ) + { + targets = new File( "targets" ); + log( "Warning: targets file not specified. generating file: " + targets.getName() ); + writeTargets = true; + } + else if( !targets.exists() ) + { + log( "Specified targets file doesn't exist. generating file: " + targets.getName() ); + writeTargets = true; + } + if( writeTargets ) + { + log( "You should consider using iControl to create a target file." ); + targetOutputStream = new FileOutputStream( targets ); + targetPrinter = new PrintStream( targetOutputStream ); + } + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".java" ) ) + { + // print the target, while we're at here. (Only if generatetarget=true). + if( targetPrinter != null ) + { + targetPrinter.println( srcFile.getAbsolutePath() ); + } + File classFile = new File( buildDir, 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; + } + } + } + if( targetPrinter != null ) + { + targetPrinter.flush(); + targetPrinter.close(); + } + } + catch( IOException e ) + { + throw new BuildException( "Could not create target file:" + e.getMessage() ); + } + + // also, check controlFile timestamp + long controlFileTime = -1; + try + { + if( controlFile != null ) + { + if( controlFile.exists() && buildDir.exists() ) + { + controlFileTime = controlFile.lastModified(); + ds = getDirectoryScanner( buildDir ); + 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; + } + } + } + } + } + } + catch( Throwable t ) + { + throw new BuildException( "Got an interesting exception:" + t.getMessage() ); + } + } + + /** + * This class is a helper to set correct classpath for other compilers, like + * Jikes. It reuses the logic from DefaultCompilerAdapter, which is + * protected, so we have to subclass it. + * + * @author RT + */ + private class ClasspathHelper extends DefaultCompilerAdapter + { + private final String compiler; + + public ClasspathHelper( String compiler ) + { + super(); + this.compiler = compiler; + } + + // dummy implementation. Never called + public void setJavac( Javac javac ) { } + + public boolean execute() + { + return true; + } + + // make it public + public void modify( Path path ) + { + // depending on what compiler to use, set the includeJavaRuntime flag + if( "jikes".equals( compiler ) ) + { + icCompiler = compiler; + includeJavaRuntime = true; + path.append( getCompileClasspath() ); + } + } + } + + /** + * 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 + * + * @author RT + */ + 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 ) { } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Javah.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Javah.java new file mode 100644 index 000000000..76d48b24c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Javah.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Task to generate JNI header files using javah. This task can take the + * following arguments: + *
        + *
      • classname - the fully-qualified name of a class
      • + *
      • outputFile - Concatenates the resulting header or source files for all + * the classes listed into this file
      • + *
      • destdir - Sets the directory where javah saves the header files or the + * stub files
      • + *
      • classpath
      • + *
      • bootclasspath
      • + *
      • force - Specifies that output files should always be written (JDK1.2 + * only)
      • + *
      • old - Specifies that old JDK1.0-style header files should be generated + * (otherwise output file contain JNI-style native method function prototypes) + * (JDK1.2 only)
      • + *
      • stubs - generate C declarations from the Java object file (used with + * old)
      • + *
      • verbose - causes javah to print a message to stdout concerning the + * status of the generated files
      • + *
      • extdirs - Override location of installed extensions
      • + *
      + * Of these arguments, either outputFile or destdir is required, + * but not both. More than one classname may be specified, using a + * comma-separated list or by using <class name="xxx"> + * elements within the task.

      + * + * When this task executes, it will generate C header and source files that are + * needed to implement native methods. + * + * @author Rick Beton + * richard.beton@physics.org + */ + +public class Javah extends Task +{ + + private final static String FAIL_MSG = "Compile failed, messages should have been provided."; + //private Path extdirs; + private static String lSep = System.getProperty( "line.separator" ); + + private Vector classes = new Vector( 2 ); + private Path classpath = null; + private File outputFile = null; + private boolean verbose = false; + private boolean force = false; + private boolean old = false; + private boolean stubs = false; + private Path bootclasspath; + private String cls; + private File destDir; + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + public void setBootclasspath( Path src ) + { + if( bootclasspath == null ) + { + bootclasspath = src; + } + else + { + bootclasspath.append( src ); + } + } + + public void setClass( String cls ) + { + this.cls = cls; + } + + public void setClasspath( Path src ) + { + if( classpath == null ) + { + classpath = src; + } + else + { + classpath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the destination directory into which the Java source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the force-write flag. + * + * @param force The new Force value + */ + public void setForce( boolean force ) + { + this.force = force; + } + + /** + * Set the old flag. + * + * @param old The new Old value + */ + public void setOld( boolean old ) + { + this.old = old; + } + + ///** + // * Sets the extension directories that will be used during the + // * compilation. + // */ + //public void setExtdirs(Path extdirs) { + // if (this.extdirs == null) { + // this.extdirs = extdirs; + // } else { + // this.extdirs.append(extdirs); + // } + //} + + ///** + // * Maybe creates a nested classpath element. + // */ + //public Path createExtdirs() { + // if (extdirs == null) { + // extdirs = new Path(project); + // } + // return extdirs.createPath(); + //} + + /** + * Set the output file name. + * + * @param outputFile The new OutputFile value + */ + public void setOutputFile( File outputFile ) + { + this.outputFile = outputFile; + } + + /** + * Set the stubs flag. + * + * @param stubs The new Stubs value + */ + public void setStubs( boolean stubs ) + { + this.stubs = stubs; + } + + /** + * Set the verbose flag. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + this.verbose = verbose; + } + + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + public ClassArgument createClass() + { + ClassArgument ga = new ClassArgument(); + classes.addElement( ga ); + return ga; + } + + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + + if( ( cls == null ) && ( classes.size() == 0 ) ) + { + throw new BuildException( "class attribute must be set!", location ); + } + + if( ( cls != null ) && ( classes.size() > 0 ) ) + { + throw new BuildException( "set class attribute or class element, not both.", location ); + } + + if( destDir != null ) + { + if( !destDir.isDirectory() ) + { + throw new BuildException( "destination directory \"" + destDir + "\" does not exist or is not a directory", location ); + } + if( outputFile != null ) + { + throw new BuildException( "destdir and outputFile are mutually exclusive", location ); + } + } + + if( classpath == null ) + { + classpath = Path.systemClasspath; + } + + String compiler = project.getProperty( "build.compiler" ); + if( compiler == null ) + { + if( Project.getJavaVersion() != Project.JAVA_1_1 && + Project.getJavaVersion() != Project.JAVA_1_2 ) + { + compiler = "modern"; + } + else + { + compiler = "classic"; + } + } + + doClassicCompile(); + } + + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( Commandline cmd ) + { + int n = 0; + log( "Compilation args: " + cmd.toString(), + Project.MSG_VERBOSE ); + + StringBuffer niceClassList = new StringBuffer(); + if( cls != null ) + { + StringTokenizer tok = new StringTokenizer( cls, ",", false ); + while( tok.hasMoreTokens() ) + { + String aClass = tok.nextToken().trim(); + cmd.createArgument().setValue( aClass ); + niceClassList.append( " " + aClass + lSep ); + n++; + } + } + + Enumeration enum = classes.elements(); + while( enum.hasMoreElements() ) + { + ClassArgument arg = ( ClassArgument )enum.nextElement(); + String aClass = arg.getName(); + cmd.createArgument().setValue( aClass ); + niceClassList.append( " " + aClass + lSep ); + n++; + } + + StringBuffer prefix = new StringBuffer( "Class" ); + if( n > 1 ) + { + prefix.append( "es" ); + } + prefix.append( " to be compiled:" ); + prefix.append( lSep ); + + log( prefix.toString() + niceClassList.toString(), Project.MSG_VERBOSE ); + } + + /** + * Does the command line argument processing common to classic and modern. + * + * @return Description of the Returned Value + */ + private Commandline setupJavahCommand() + { + Commandline cmd = new Commandline(); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + if( outputFile != null ) + { + cmd.createArgument().setValue( "-o" ); + cmd.createArgument().setFile( outputFile ); + } + + if( classpath != null ) + { + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + } + + // JDK1.1 is rather simpler than JDK1.2 + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + if( verbose ) + { + cmd.createArgument().setValue( "-v" ); + } + } + else + { + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + if( old ) + { + cmd.createArgument().setValue( "-old" ); + } + if( force ) + { + cmd.createArgument().setValue( "-force" ); + } + } + + if( stubs ) + { + if( !old ) + { + throw new BuildException( "stubs only available in old mode.", location ); + } + cmd.createArgument().setValue( "-stubs" ); + } + if( bootclasspath != null ) + { + cmd.createArgument().setValue( "-bootclasspath" ); + cmd.createArgument().setPath( bootclasspath ); + } + + logAndAddFilesToCompile( cmd ); + return cmd; + } + + // XXX + // we need a way to not use the current classpath. + + /** + * Peforms a compile using the classic compiler that shipped with JDK 1.1 + * and 1.2. + * + * @exception BuildException Description of Exception + */ + + private void doClassicCompile() + throws BuildException + { + Commandline cmd = setupJavahCommand(); + + // Use reflection to be able to build on all JDKs + /* + * / provide the compiler a different message sink - namely our own + * sun.tools.javac.Main compiler = + * new sun.tools.javac.Main(new LogOutputStream(this, Project.MSG_WARN), "javac"); + * if (!compiler.compile(cmd.getArguments())) { + * throw new BuildException("Compile failed"); + * } + */ + try + { + // Javac uses logstr to change the output stream and calls + // the constructor's invoke method to create a compiler instance + // dynamically. However, javah has a different interface and this + // makes it harder, so here's a simple alternative. + //------------------------------------------------------------------ + com.sun.tools.javah.Main main = new com.sun.tools.javah.Main( cmd.getArguments() ); + main.run(); + } + //catch (ClassNotFoundException ex) { + // throw new BuildException("Cannot use javah because it is not available"+ + // " A common solution is to set the environment variable"+ + // " JAVA_HOME to your jdk directory.", location); + //} + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting javah: ", ex, location ); + } + } + } + + public class ClassArgument + { + private String name; + + public ClassArgument() { } + + public void setName( String name ) + { + this.name = name; + log( "ClassArgument.name=" + name ); + } + + public String getName() + { + return name; + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ManifestFile.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ManifestFile.java new file mode 100644 index 000000000..4ef6878db --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ManifestFile.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.ListIterator; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Task for creating a manifest file for a jar archiv. use:

      + *   
      + *   
      + *     
      + *         
      + *       
      + * + * @author Thomas Kerle + * @version 1.0 2001-10-11 + */ +public class ManifestFile extends Task +{ + + private final static String newLine = System.getProperty( "line.separator" ); + private final static String keyValueSeparator = ":"; + private final static String UPDATE_ = "update"; + private final static String REPLACEALL_ = "replaceAll"; + private EntryContainer container; + private String currentMethod; + private Vector entries; + + private File manifestFile; + + public ManifestFile() + { + entries = new Vector(); + container = new EntryContainer(); + } + + /** + * Setter for the file attribute + * + * @param f The new File value + */ + public void setFile( File f ) + { + manifestFile = f; + } + + /** + * Setter for the method attribute (update/replaceAll) + * + * @param method Method to set task + */ + public void setMethod( String method ) + { + currentMethod = method.toUpperCase(); + } + + /** + * creating entries by Ant + * + * @return Description of the Returned Value + */ + public Entry createEntry() + { + Entry entry = new Entry(); + entries.addElement( entry ); + return entry; + } + + /** + * execute task + * + * @exception BuildException : Failure in building + */ + public void execute() + throws BuildException + { + checkParameters(); + if( isUpdate( currentMethod ) ) + readFile(); + + executeOperation(); + writeFile(); + } + + private StringTokenizer getLineTokens( StringBuffer buffer ) + { + String manifests = buffer.toString(); + StringTokenizer strTokens = new StringTokenizer( manifests, newLine ); + return strTokens; + } + + private boolean isReplaceAll( String method ) + { + return method.equals( REPLACEALL_.toUpperCase() ); + } + + + private boolean isUpdate( String method ) + { + return method.equals( UPDATE_.toUpperCase() ); + } + + private void addLine( String line ) + { + Entry entry = new Entry(); + + entry.setValue( line ); + entry.addTo( container ); + } + + + private StringBuffer buildBuffer() + { + StringBuffer buffer = new StringBuffer(); + + ListIterator iterator = container.elements(); + + while( iterator.hasNext() ) + { + Entry entry = ( Entry )iterator.next(); + + String key = ( String )entry.getKey(); + String value = ( String )entry.getValue(); + String entry_string = key + keyValueSeparator + value; + + buffer.append( entry_string + this.newLine ); + } + + return buffer; + } + + private boolean checkParam( String param ) + { + return !( ( param == null ) || ( param.equals( "null" ) ) ); + } + + private boolean checkParam( File param ) + { + return !( param == null ); + } + + private void checkParameters() + throws BuildException + { + if( !checkParam( manifestFile ) ) + { + throw new BuildException( "file token must not be null.", location ); + } + } + + /** + * adding entries to a container + * + * @exception BuildException + */ + private void executeOperation() + throws BuildException + { + Enumeration enum = entries.elements(); + + while( enum.hasMoreElements() ) + { + Entry entry = ( Entry )enum.nextElement(); + entry.addTo( container ); + } + } + + private void readFile() + throws BuildException + { + + if( manifestFile.exists() ) + { + this.log( "update existing manifest file " + manifestFile.getAbsolutePath() ); + + if( container != null ) + { + try + { + FileInputStream fis = new FileInputStream( manifestFile ); + + int c; + StringBuffer buffer = new StringBuffer( "" ); + boolean stop = false; + while( !stop ) + { + c = fis.read(); + if( c == -1 ) + { + stop = true; + } + else + buffer.append( ( char )c ); + } + fis.close(); + StringTokenizer lineTokens = getLineTokens( buffer ); + while( lineTokens.hasMoreElements() ) + { + String currentLine = ( String )lineTokens.nextElement(); + addLine( currentLine ); + } + } + catch( FileNotFoundException fnfe ) + { + throw new BuildException( "File not found exception " + fnfe.toString() ); + } + catch( IOException ioe ) + { + throw new BuildException( "Unknown input/output exception " + ioe.toString() ); + } + } + } + + } + + + private void writeFile() + throws BuildException + { + try + { + manifestFile.delete(); + log( "Replacing or creating new manifest file " + manifestFile.getAbsolutePath() ); + if( manifestFile.createNewFile() ) + { + FileOutputStream fos = new FileOutputStream( manifestFile ); + + StringBuffer buffer = buildBuffer(); + + int size = buffer.length(); + + for( int i = 0; i < size; i++ ) + { + fos.write( ( char )buffer.charAt( i ) ); + } + + fos.flush(); + fos.close(); + } + else + { + throw new BuildException( "Can't create manifest file" ); + } + + } + catch( IOException ioe ) + { + throw new BuildException( "An input/ouput error occured" + ioe.toString() ); + } + } + + public class Entry implements Comparator + { + //extern format + private String value = null; + + //intern representation + private String val = null; + private String key = null; + + public Entry() { } + + public void setValue( String value ) + { + this.value = new String( value ); + } + + public String getKey() + { + return key; + } + + public String getValue() + { + return val; + } + + public int compare( Object o1, Object o2 ) + { + int result = -1; + + try + { + Entry e1 = ( Entry )o1; + Entry e2 = ( Entry )o2; + + String key_1 = e1.getKey(); + String key_2 = e2.getKey(); + + result = key_1.compareTo( key_2 ); + } + catch( Exception e ) + { + + } + return result; + } + + + public boolean equals( Object obj ) + { + Entry ent = new Entry(); + boolean result = false; + int res = ent.compare( this, ( Entry )obj ); + if( res == 0 ) + result = true; + + return result; + } + + + protected void addTo( EntryContainer container ) + throws BuildException + { + checkFormat(); + split(); + container.set( this ); + } + + private void checkFormat() + throws BuildException + { + + if( value == null ) + { + throw new BuildException( "no argument for value" ); + } + + StringTokenizer st = new StringTokenizer( value, ManifestFile.keyValueSeparator ); + int size = st.countTokens(); + + if( size < 2 ) + { + throw new BuildException( "value has not the format of a manifest entry" ); + } + } + + private void split() + { + StringTokenizer st = new StringTokenizer( value, ManifestFile.keyValueSeparator ); + key = ( String )st.nextElement(); + val = ( String )st.nextElement(); + } + + } + + public class EntryContainer + { + + private ArrayList list = null; + + public EntryContainer() + { + list = new ArrayList(); + } + + public void set( Entry entry ) + { + + if( list.contains( entry ) ) + { + int index = list.indexOf( entry ); + + list.remove( index ); + list.add( index, entry ); + } + else + { + list.add( entry ); + } + } + + public ListIterator elements() + { + ListIterator iterator = list.listIterator(); + return iterator; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java new file mode 100644 index 000000000..882335c60 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.IdentityMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Convert files from native encodings to ascii. + * + * @author Drew Sudell + * @author Stefan Bodewig + */ +public class Native2Ascii extends MatchingTask +{ + + private boolean reverse = false;// convert from ascii back to native + private String encoding = null;// encoding to convert to/from + private File srcDir = null;// Where to find input files + private File destDir = null;// Where to put output files + private String extension = null;// Extension of output files if different + + private Mapper mapper; + + + /** + * Set the destination dirctory to place converted files into. + * + * @param destDir directory to place output file into. + */ + public void setDest( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the encoding to translate to/from. If unset, the default encoding for + * the JVM is used. + * + * @param encoding String containing the name of the Native encoding to + * convert from or to. + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Set the extension which converted files should have. If unset, files will + * not be renamed. + * + * @param ext File extension to use for converted files. + */ + public void setExt( String ext ) + { + this.extension = ext; + } + + /** + * Flag the conversion to run in the reverse sense, that is Ascii to Native + * encoding. + * + * @param reverse True if the conversion is to be reversed, otherwise false; + */ + public void setReverse( boolean reverse ) + { + this.reverse = reverse; + } + + /** + * Set the source directory in which to find files to convert. + * + * @param srcDir Direcrory to find input file in. + */ + public void setSrc( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapper != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapper = new Mapper( project ); + return mapper; + } + + public void execute() + throws BuildException + { + + Commandline baseCmd = null;// the common portion of our cmd line + DirectoryScanner scanner = null;// Scanner to find our inputs + String[] files;// list of files to process + + // default srcDir to basedir + if( srcDir == null ) + { + srcDir = project.resolveFile( "." ); + } + + // Require destDir + if( destDir == null ) + { + throw new BuildException( "The dest attribute must be set." ); + } + + // if src and dest dirs are the same, require the extension + // to be set, so we don't stomp every file. One could still + // include a file with the same extension, but .... + if( srcDir.equals( destDir ) && extension == null && mapper == null ) + { + throw new BuildException( "The ext attribute or a mapper must be set if" + + " src and dest dirs are the same." ); + } + + FileNameMapper m = null; + if( mapper == null ) + { + if( extension == null ) + { + m = new IdentityMapper(); + } + else + { + m = new ExtMapper(); + } + } + else + { + m = mapper.getImplementation(); + } + + scanner = getDirectoryScanner( srcDir ); + files = scanner.getIncludedFiles(); + SourceFileScanner sfs = new SourceFileScanner( this ); + files = sfs.restrict( files, srcDir, destDir, m ); + int count = files.length; + if( count == 0 ) + { + return; + } + String message = "Converting " + count + " file" + + ( count != 1 ? "s" : "" ) + " from "; + log( message + srcDir + " to " + destDir ); + for( int i = 0; i < files.length; i++ ) + { + convert( files[i], m.mapFileName( files[i] )[0] ); + } + } + + /** + * Convert a single file. + * + * @param srcName Description of Parameter + * @param destName Description of Parameter + * @exception BuildException Description of Exception + */ + private void convert( String srcName, String destName ) + throws BuildException + { + + Commandline cmd = new Commandline();// Command line to run + File srcFile;// File to convert + File destFile;// where to put the results + + // Set up the basic args (this could be done once, but + // it's cleaner here) + if( reverse ) + { + cmd.createArgument().setValue( "-reverse" ); + } + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + + // Build the full file names + srcFile = new File( srcDir, srcName ); + destFile = new File( destDir, destName ); + + cmd.createArgument().setFile( srcFile ); + cmd.createArgument().setFile( destFile ); + // Make sure we're not about to clobber something + if( srcFile.equals( destFile ) ) + { + throw new BuildException( "file " + srcFile + + " would overwrite its self" ); + } + + // Make intermediate directories if needed + // XXX JDK 1.1 dosen't have File.getParentFile, + String parentName = destFile.getParent(); + if( parentName != null ) + { + File parentFile = new File( parentName ); + + if( ( !parentFile.exists() ) && ( !parentFile.mkdirs() ) ) + { + throw new BuildException( "cannot create parent directory " + + parentName ); + } + } + + log( "converting " + srcName, Project.MSG_VERBOSE ); + sun.tools.native2ascii.Main n2a + = new sun.tools.native2ascii.Main(); + if( !n2a.convert( cmd.getArguments() ) ) + { + throw new BuildException( "conversion failed" ); + } + } + + private class ExtMapper implements FileNameMapper + { + + public void setFrom( String s ) { } + + public void setTo( String s ) { } + + public String[] mapFileName( String fileName ) + { + int lastDot = fileName.lastIndexOf( '.' ); + if( lastDot >= 0 ) + { + return new String[]{fileName.substring( 0, lastDot ) + extension}; + } + else + { + return new String[]{fileName + extension}; + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/NetRexxC.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/NetRexxC.java new file mode 100644 index 000000000..3b05253a1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/NetRexxC.java @@ -0,0 +1,766 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; +import netrexx.lang.Rexx; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; + +/** + * Task to compile NetRexx source files. This task can take the following + * arguments: + *
        + *
      • binary
      • + *
      • classpath
      • + *
      • comments
      • + *
      • compile
      • + *
      • console
      • + *
      • crossref
      • + *
      • decimal
      • + *
      • destdir
      • + *
      • diag
      • + *
      • explicit
      • + *
      • format
      • + *
      • keep
      • + *
      • logo
      • + *
      • replace
      • + *
      • savelog
      • + *
      • srcdir
      • + *
      • sourcedir
      • + *
      • strictargs
      • + *
      • strictassign
      • + *
      • strictcase
      • + *
      • strictimport
      • + *
      • symbols
      • + *
      • time
      • + *
      • trace
      • + *
      • utf8
      • + *
      • verbose
      • + *
      + * Of these arguments, the srcdir argument is required.

      + * + * When this task executes, it will recursively scan the srcdir looking for + * NetRexx source files to compile. This task makes its compile decision based + * on timestamp.

      + * + * Before files are compiled they and any other file in the srcdir will be + * copied to the destdir allowing support files to be located properly in the + * classpath. The reason for copying the source files before the compile is that + * NetRexxC has only two destinations for classfiles: + *

        + *
      1. The current directory, and,
      2. + *
      3. The directory the source is in (see sourcedir option) + *
      + * + * + * @author dIon Gillard + * dion@multitask.com.au + */ + +public class NetRexxC extends MatchingTask +{ + private boolean compile = true; + private boolean decimal = true; + private boolean logo = true; + private boolean sourcedir = true; + private String trace = "trace2"; + private String verbose = "verbose3"; + + // other implementation variables + private Vector compileList = new Vector(); + private Hashtable filecopyList = new Hashtable(); + private String oldClasspath = System.getProperty( "java.class.path" ); + + // variables to hold arguments + private boolean binary; + private String classpath; + private boolean comments; + private boolean compact; + private boolean console; + private boolean crossref; + private File destDir; + private boolean diag; + private boolean explicit; + private boolean format; + private boolean java; + private boolean keep; + private boolean replace; + private boolean savelog; + private File srcDir;// ?? Should this be the default for ant? + private boolean strictargs; + private boolean strictassign; + private boolean strictcase; + private boolean strictimport; + private boolean strictprops; + private boolean strictsignal; + private boolean symbols; + private boolean time; + private boolean utf8; + + + /** + * Set whether literals are treated as binary, rather than NetRexx types + * + * @param binary The new Binary value + */ + public void setBinary( boolean binary ) + { + this.binary = binary; + } + + /** + * Set the classpath used for NetRexx compilation + * + * @param classpath The new Classpath value + */ + public void setClasspath( String classpath ) + { + this.classpath = classpath; + } + + /** + * Set whether comments are passed through to the generated java source. + * Valid true values are "on" or "true". Anything else sets the flag to + * false. The default value is false + * + * @param comments The new Comments value + */ + public void setComments( boolean comments ) + { + this.comments = comments; + } + + /** + * Set whether error messages come out in compact or verbose format. Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false + * + * @param compact The new Compact value + */ + public void setCompact( boolean compact ) + { + this.compact = compact; + } + + /** + * Set whether the NetRexx compiler should compile the generated java code + * Valid true values are "on" or "true". Anything else sets the flag to + * false. The default value is true. Setting this flag to false, will + * automatically set the keep flag to true. + * + * @param compile The new Compile value + */ + public void setCompile( boolean compile ) + { + this.compile = compile; + if( !this.compile && !this.keep ) + this.keep = true; + } + + /** + * Set whether or not messages should be displayed on the 'console' Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is true. + * + * @param console The new Console value + */ + public void setConsole( boolean console ) + { + this.console = console; + } + + /** + * Whether variable cross references are generated + * + * @param crossref The new Crossref value + */ + public void setCrossref( boolean crossref ) + { + this.crossref = crossref; + } + + /** + * Set whether decimal arithmetic should be used for the netrexx code. + * Binary arithmetic is used when this flag is turned off. Valid true values + * are "on" or "true". Anything else sets the flag to false. The default + * value is true. + * + * @param decimal The new Decimal value + */ + public void setDecimal( boolean decimal ) + { + this.decimal = decimal; + } + + /** + * Set the destination directory into which the NetRexx source files should + * be copied and then compiled. + * + * @param destDirName The new DestDir value + */ + public void setDestDir( File destDirName ) + { + destDir = destDirName; + } + + /** + * Whether diagnostic information about the compile is generated + * + * @param diag The new Diag value + */ + public void setDiag( boolean diag ) + { + this.diag = diag; + } + + /** + * Sets whether variables must be declared explicitly before use. Valid true + * values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param explicit The new Explicit value + */ + public void setExplicit( boolean explicit ) + { + this.explicit = explicit; + } + + /** + * Whether the generated java code is formatted nicely or left to match + * NetRexx line numbers for call stack debugging + * + * @param format The new Format value + */ + public void setFormat( boolean format ) + { + this.format = format; + } + + /** + * Whether the generated java code is produced Valid true values are "on" or + * "true". Anything else sets the flag to false. The default value is false. + * + * @param java The new Java value + */ + public void setJava( boolean java ) + { + this.java = java; + } + + + /** + * Sets whether the generated java source file should be kept after + * compilation. The generated files will have an extension of .java.keep, + * not .java Valid true values are "on" or "true". Anything else sets + * the flag to false. The default value is false. + * + * @param keep The new Keep value + */ + public void setKeep( boolean keep ) + { + this.keep = keep; + } + + /** + * Whether the compiler text logo is displayed when compiling + * + * @param logo The new Logo value + */ + public void setLogo( boolean logo ) + { + this.logo = logo; + } + + /** + * Whether the generated .java file should be replaced when compiling Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param replace The new Replace value + */ + public void setReplace( boolean replace ) + { + this.replace = replace; + } + + /** + * Sets whether the compiler messages will be written to NetRexxC.log as + * well as to the console Valid true values are "on" or "true". Anything + * else sets the flag to false. The default value is false. + * + * @param savelog The new Savelog value + */ + public void setSavelog( boolean savelog ) + { + this.savelog = savelog; + } + + /** + * Tells the NetRexx compiler to store the class files in the same directory + * as the source files. The alternative is the working directory Valid true + * values are "on" or "true". Anything else sets the flag to false. The + * default value is true. + * + * @param sourcedir The new Sourcedir value + */ + public void setSourcedir( boolean sourcedir ) + { + this.sourcedir = sourcedir; + } + + /** + * Set the source dir to find the source Java files. + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + srcDir = srcDirName; + } + + /** + * Tells the NetRexx compiler that method calls always need parentheses, + * even if no arguments are needed, e.g. aStringVar.getBytes + * vs. aStringVar.getBytes() Valid true values are "on" or + * "true". Anything else sets the flag to false. The default value is false. + * + * @param strictargs The new Strictargs value + */ + public void setStrictargs( boolean strictargs ) + { + this.strictargs = strictargs; + } + + /** + * Tells the NetRexx compile that assignments must match exactly on type + * + * @param strictassign The new Strictassign value + */ + public void setStrictassign( boolean strictassign ) + { + this.strictassign = strictassign; + } + + /** + * Specifies whether the NetRexx compiler should be case sensitive or not + * + * @param strictcase The new Strictcase value + */ + public void setStrictcase( boolean strictcase ) + { + this.strictcase = strictcase; + } + + /** + * Sets whether classes need to be imported explicitly using an import + * statement. By default the NetRexx compiler will import certain packages + * automatically Valid true values are "on" or "true". Anything else sets + * the flag to false. The default value is false. + * + * @param strictimport The new Strictimport value + */ + public void setStrictimport( boolean strictimport ) + { + this.strictimport = strictimport; + } + + /** + * Sets whether local properties need to be qualified explicitly using + * this Valid true values are "on" or "true". Anything else + * sets the flag to false. The default value is false. + * + * @param strictprops The new Strictprops value + */ + public void setStrictprops( boolean strictprops ) + { + this.strictprops = strictprops; + } + + + /** + * Whether the compiler should force catching of exceptions by explicitly + * named types + * + * @param strictsignal The new Strictsignal value + */ + public void setStrictsignal( boolean strictsignal ) + { + this.strictsignal = strictsignal; + } + + /** + * Sets whether debug symbols should be generated into the class file Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param symbols The new Symbols value + */ + public void setSymbols( boolean symbols ) + { + this.symbols = symbols; + } + + /** + * Asks the NetRexx compiler to print compilation times to the console Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param time The new Time value + */ + public void setTime( boolean time ) + { + this.time = time; + } + + /** + * Turns on or off tracing and directs the resultant trace output Valid + * values are: "trace", "trace1", "trace2" and "notrace". "trace" and + * "trace2" + * + * @param trace The new Trace value + */ + public void setTrace( String trace ) + { + if( trace.equalsIgnoreCase( "trace" ) + || trace.equalsIgnoreCase( "trace1" ) + || trace.equalsIgnoreCase( "trace2" ) + || trace.equalsIgnoreCase( "notrace" ) ) + { + this.trace = trace; + } + else + { + throw new BuildException( "Unknown trace value specified: '" + trace + "'" ); + } + } + + /** + * Tells the NetRexx compiler that the source is in UTF8 Valid true values + * are "on" or "true". Anything else sets the flag to false. The default + * value is false. + * + * @param utf8 The new Utf8 value + */ + public void setUtf8( boolean utf8 ) + { + this.utf8 = utf8; + } + + /** + * Whether lots of warnings and error messages should be generated + * + * @param verbose The new Verbose value + */ + public void setVerbose( String verbose ) + { + this.verbose = verbose; + } + + /** + * Executes the task, i.e. does the actual compiler call + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // first off, make sure that we've got a srcdir and destdir + if( srcDir == null || destDir == null ) + { + throw new BuildException( "srcDir and destDir attributes must be set!" ); + } + + // scan source and dest dirs to build up both copy lists and + // compile lists + // scanDir(srcDir, destDir); + DirectoryScanner ds = getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + + scanDir( srcDir, destDir, files ); + + // copy the source and support files + copyFilesToDestination(); + + // compile the source files + if( compileList.size() > 0 ) + { + log( "Compiling " + compileList.size() + " source file" + + ( compileList.size() == 1 ? "" : "s" ) + + " to " + destDir ); + doNetRexxCompile(); + } + } + + /** + * Builds the compilation classpath. + * + * @return The CompileClasspath value + */ + private String getCompileClasspath() + { + StringBuffer classpath = new StringBuffer(); + + // add dest dir to classpath so that previously compiled and + // untouched classes are on classpath + classpath.append( destDir.getAbsolutePath() ); + + // add our classpath to the mix + if( this.classpath != null ) + { + addExistingToClasspath( classpath, this.classpath ); + } + + // add the system classpath + // addExistingToClasspath(classpath,System.getProperty("java.class.path")); + return classpath.toString(); + } + + /** + * This + * + * @return The CompileOptionsAsArray value + */ + private String[] getCompileOptionsAsArray() + { + Vector options = new Vector(); + options.addElement( binary ? "-binary" : "-nobinary" ); + options.addElement( comments ? "-comments" : "-nocomments" ); + options.addElement( compile ? "-compile" : "-nocompile" ); + options.addElement( compact ? "-compact" : "-nocompact" ); + options.addElement( console ? "-console" : "-noconsole" ); + options.addElement( crossref ? "-crossref" : "-nocrossref" ); + options.addElement( decimal ? "-decimal" : "-nodecimal" ); + options.addElement( diag ? "-diag" : "-nodiag" ); + options.addElement( explicit ? "-explicit" : "-noexplicit" ); + options.addElement( format ? "-format" : "-noformat" ); + options.addElement( keep ? "-keep" : "-nokeep" ); + options.addElement( logo ? "-logo" : "-nologo" ); + options.addElement( replace ? "-replace" : "-noreplace" ); + options.addElement( savelog ? "-savelog" : "-nosavelog" ); + options.addElement( sourcedir ? "-sourcedir" : "-nosourcedir" ); + options.addElement( strictargs ? "-strictargs" : "-nostrictargs" ); + options.addElement( strictassign ? "-strictassign" : "-nostrictassign" ); + options.addElement( strictcase ? "-strictcase" : "-nostrictcase" ); + options.addElement( strictimport ? "-strictimport" : "-nostrictimport" ); + options.addElement( strictprops ? "-strictprops" : "-nostrictprops" ); + options.addElement( strictsignal ? "-strictsignal" : "-nostrictsignal" ); + options.addElement( symbols ? "-symbols" : "-nosymbols" ); + options.addElement( time ? "-time" : "-notime" ); + options.addElement( "-" + trace ); + options.addElement( utf8 ? "-utf8" : "-noutf8" ); + options.addElement( "-" + verbose ); + String[] results = new String[options.size()]; + options.copyInto( results ); + return results; + } + + /** + * Takes a classpath-like string, and adds each element of this string to a + * new classpath, if the components exist. Components that don't exist, + * aren't added. We do this, because jikes issues warnings for non-existant + * files/dirs in his classpath, and these warnings are pretty annoying. + * + * @param target - target classpath + * @param source - source classpath to get file objects. + */ + private void addExistingToClasspath( StringBuffer target, String source ) + { + StringTokenizer tok = new StringTokenizer( source, + System.getProperty( "path.separator" ), false ); + while( tok.hasMoreTokens() ) + { + File f = project.resolveFile( tok.nextToken() ); + + if( f.exists() ) + { + target.append( File.pathSeparator ); + target.append( f.getAbsolutePath() ); + } + else + { + log( "Dropping from classpath: " + + f.getAbsolutePath(), Project.MSG_VERBOSE ); + } + } + + } + + /** + * Copy eligible files from the srcDir to destDir + */ + private void copyFilesToDestination() + { + if( filecopyList.size() > 0 ) + { + log( "Copying " + filecopyList.size() + " file" + + ( filecopyList.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + Enumeration enum = filecopyList.keys(); + while( enum.hasMoreElements() ) + { + String fromFile = ( String )enum.nextElement(); + String toFile = ( String )filecopyList.get( fromFile ); + try + { + project.copyFile( fromFile, toFile ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + } + } + } + + /** + * Peforms a copmile using the NetRexx 1.1.x compiler + * + * @exception BuildException Description of Exception + */ + private void doNetRexxCompile() + throws BuildException + { + log( "Using NetRexx compiler", Project.MSG_VERBOSE ); + String classpath = getCompileClasspath(); + StringBuffer compileOptions = new StringBuffer(); + StringBuffer fileList = new StringBuffer(); + + // create an array of strings for input to the compiler: one array + // comes from the compile options, the other from the compileList + String[] compileOptionsArray = getCompileOptionsAsArray(); + String[] fileListArray = new String[compileList.size()]; + Enumeration e = compileList.elements(); + int j = 0; + while( e.hasMoreElements() ) + { + fileListArray[j] = ( String )e.nextElement(); + j++; + } + // create a single array of arguments for the compiler + String compileArgs[] = new String[compileOptionsArray.length + fileListArray.length]; + for( int i = 0; i < compileOptionsArray.length; i++ ) + { + compileArgs[i] = compileOptionsArray[i]; + } + for( int i = 0; i < fileListArray.length; i++ ) + { + compileArgs[i + compileOptionsArray.length] = fileListArray[i]; + } + + // print nice output about what we are doing for the log + compileOptions.append( "Compilation args: " ); + for( int i = 0; i < compileOptionsArray.length; i++ ) + { + compileOptions.append( compileOptionsArray[i] ); + compileOptions.append( " " ); + } + log( compileOptions.toString(), Project.MSG_VERBOSE ); + + String eol = System.getProperty( "line.separator" ); + StringBuffer niceSourceList = new StringBuffer( "Files to be compiled:" + eol ); + + for( int i = 0; i < compileList.size(); i++ ) + { + niceSourceList.append( " " ); + niceSourceList.append( compileList.elementAt( i ).toString() ); + niceSourceList.append( eol ); + } + + log( niceSourceList.toString(), Project.MSG_VERBOSE ); + + // need to set java.class.path property and restore it later + // since the NetRexx compiler has no option for the classpath + String currentClassPath = System.getProperty( "java.class.path" ); + Properties currentProperties = System.getProperties(); + currentProperties.put( "java.class.path", classpath ); + + try + { + StringWriter out = new StringWriter(); + int rc = + COM.ibm.netrexx.process.NetRexxC.main( new Rexx( compileArgs ), new PrintWriter( out ) ); + + if( rc > 1 ) + {// 1 is warnings from real NetRexxC + log( out.toString(), Project.MSG_ERR ); + String msg = "Compile failed, messages should have been provided."; + throw new BuildException( msg ); + } + else if( rc == 1 ) + { + log( out.toString(), Project.MSG_WARN ); + } + else + { + log( out.toString(), Project.MSG_INFO ); + } + } + finally + { + // need to reset java.class.path property + // since the NetRexx compiler has no option for the classpath + currentProperties = System.getProperties(); + currentProperties.put( "java.class.path", currentClassPath ); + } + } + + /** + * Scans the directory looking for source files to be compiled and support + * files to be copied. + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + private void scanDir( File srcDir, File destDir, String[] files ) + { + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + File destFile = new File( destDir, files[i] ); + String filename = files[i]; + // if it's a non source file, copy it if a later date than the + // dest + // if it's a source file, see if the destination class file + // needs to be recreated via compilation + if( filename.toLowerCase().endsWith( ".nrx" ) ) + { + File classFile = + new File( destDir, + filename.substring( 0, filename.lastIndexOf( '.' ) ) + ".class" ); + + if( !compile || srcFile.lastModified() > classFile.lastModified() ) + { + filecopyList.put( srcFile.getAbsolutePath(), destFile.getAbsolutePath() ); + compileList.addElement( destFile.getAbsolutePath() ); + } + } + else + { + if( srcFile.lastModified() > destFile.lastModified() ) + { + filecopyList.put( srcFile.getAbsolutePath(), destFile.getAbsolutePath() ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java new file mode 100644 index 000000000..ab7bfd7b1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java @@ -0,0 +1,791 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * PropertyFile task uses java.util.Properties to modify integer, String and + * Date settings in a property file.

      + * + * The following is an example of its usage: + *

        <target name="setState">
        + * + *
          <property
          + * + *
            name="header"
            + * value="##Generated file - do not modify!"/>
            + * <propertyfile file="apropfile.properties" comment="${header}"> + *
            + * <entry key="product.version.major" type="int" value="5"/>
            + * <entry key="product.version.minor" type="int" value="0"/>
            + * <entry key="product.build.major" type="int" value="0" />
            + * <entry key="product.build.minor" type="int" operation="+" />
            + * <entry key="product.build.date" type="date" operation="now" /> + *
            + * <entry key="intSet" type="int" operation="=" value="681"/>
            + * <entry key="intDec" type="int" operation="-"/>
            + * <entry key="NeverDate" type="date" operation="never"/>
            + * <entry key="StringEquals" type="string" value="testValue"/>
            + * <entry key="NowDate" type="date" operation="now"/>
            + * + *
          + * </propertyfile>
          + * + *
        + * </target> + *
      + *

      + * + * The <propertyfile> task must have:
      + * + *

        + *
      • file
      • + *
      + * Other parameters are:
      + * + *
        + *
      • comment, key, operation, type and value (the final four being + * eliminated shortly)
      • + *
      + * The <entry> task must have:
      + * + *
        + *
      • key
      • + *
      + * Other parameters are:
      + * + *
        + *
      • operation
      • + *
      • type
      • + *
      • value
      • + *
      • offset
      • + *
      + * If type is unspecified, it defaults to string Parameter values:
      + * + *
        + *
      • operation:
      • + *
          + *
        • "=" (set -- default)
        • + *
        • "-" (dec)
        • + *
        • "+" (inc)
        • + *
        • type:
        • + *
            + *
          • "int"
          • + *
          • "date"
          • + *
          • "string"
          • + *
          + * + *
        + * + *
      • value:
      • + *
          + *
        • holds the default value, if the property was not found in property + * file
        • + *
        • "now" In case of type "date", the value "now" will be replaced by + * the current date/time and used even if a valid date was found in the + * property file.
        • + *
        + * + *
      • offset:
        + * valid for "-" or "+", the offset (default set to 1) will be added or + * subtracted from "int" or "date" type value.
      • + *
      + * String property types can only use the "=" operation. Date property types can + * only use the "never" or "now" operations. Int property types can only use the + * "=", "-" or "+" operations.

      + * + * The message property is used for the property file header, with "\\" being a + * newline delimiter charater. + * + * @author Thomas Christen chr@active.ch + * @author Jeremy Mawson + * jem@loftinspace.com.au + */ +public class PropertyFile extends Task +{ + + /* + * ======================================================================== + * + * Static variables. + */ + private final static String NEWLINE = System.getProperty( "line.separator" ); + + private Vector entries = new Vector(); + + /* + * ======================================================================== + * + * Instance variables. + */ + // Use this to prepend a message to the properties file + private String m_comment; + + private Properties m_properties; + private File m_propertyfile; + + public void setComment( String hdr ) + { + m_comment = hdr; + } + + public void setFile( File file ) + { + m_propertyfile = file; + } + + public Entry createEntry() + { + Entry e = new Entry(); + entries.addElement( e ); + return e; + } + + /* + * ======================================================================== + * + * Constructors + */ + /* + * ======================================================================== + * + * Methods + */ + public void execute() + throws BuildException + { + checkParameters(); + readFile(); + executeOperation(); + writeFile(); + } + + /* + * Returns whether the given parameter has been defined. + */ + private boolean checkParam( String param ) + { + return !( ( param == null ) || ( param.equals( "null" ) ) ); + } + + private boolean checkParam( File param ) + { + return !( param == null ); + } + + private void checkParameters() + throws BuildException + { + if( !checkParam( m_propertyfile ) ) + { + throw new BuildException( "file token must not be null.", location ); + } + } + + private void executeOperation() + throws BuildException + { + for( Enumeration e = entries.elements(); e.hasMoreElements(); ) + { + Entry entry = ( Entry )e.nextElement(); + entry.executeOn( m_properties ); + } + } + + private void readFile() + throws BuildException + { + // Create the PropertyFile + m_properties = new Properties(); + try + { + if( m_propertyfile.exists() ) + { + log( "Updating property file: " + m_propertyfile.getAbsolutePath() ); + FileInputStream fis = null; + try + { + fis = new FileInputStream( m_propertyfile ); + BufferedInputStream bis = new BufferedInputStream( fis ); + m_properties.load( bis ); + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + } + else + { + log( "Creating new property file: " + + m_propertyfile.getAbsolutePath() ); + FileOutputStream out = null; + try + { + out = new FileOutputStream( m_propertyfile.getAbsolutePath() ); + out.flush(); + } + finally + { + if( out != null ) + { + out.close(); + } + } + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.toString() ); + } + } + + private void writeFile() + throws BuildException + { + BufferedOutputStream bos = null; + try + { + bos = new BufferedOutputStream( new FileOutputStream( m_propertyfile ) ); + + // Properties.store is not available in JDK 1.1 + Method m = + Properties.class.getMethod( "store", + new Class[]{ + OutputStream.class, + String.class} + ); + m.invoke( m_properties, new Object[]{bos, m_comment} ); + + } + catch( NoSuchMethodException nsme ) + { + m_properties.save( bos, m_comment ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t ); + } + catch( IllegalAccessException iae ) + { + // impossible + throw new BuildException( iae ); + } + catch( IOException ioe ) + { + throw new BuildException( ioe ); + } + finally + { + if( bos != null ) + { + try + { + bos.close(); + } + catch( IOException ioex ) + {} + } + } + } + + /** + * Instance of this class represents nested elements of a task propertyfile. + * + * @author RT + */ + public static class Entry + { + + final static String NOW_VALUE_ = "now"; + final static String NULL_VALUE_ = "never"; + + private final static int DEFAULT_INT_VALUE = 1; + private final static GregorianCalendar + DEFAULT_DATE_VALUE = new GregorianCalendar(); + + private String m_key = null; + private int m_type = Type.STRING_TYPE; + private int m_operation = Operation.EQUALS_OPER; + private String m_value = ""; + private String m_default = null; + private String m_pattern = null; + + public void setDefault( String value ) + { + this.m_default = value; + } + + public void setKey( String value ) + { + this.m_key = value; + } + + public void setOperation( Operation value ) + { + int newOperation = Operation.toOperation( value.getValue() ); + if( newOperation == Operation.NOW_VALUE ) + { + this.m_operation = Operation.EQUALS_OPER; + this.setValue( this.NOW_VALUE_ ); + } + else if( newOperation == Operation.NULL_VALUE ) + { + this.m_operation = Operation.EQUALS_OPER; + this.setValue( this.NULL_VALUE_ ); + } + else + { + this.m_operation = newOperation; + } + } + + public void setPattern( String value ) + { + this.m_pattern = value; + } + + public void setType( Type value ) + { + this.m_type = Type.toType( value.getValue() ); + } + + public void setValue( String value ) + { + this.m_value = value; + } + + protected void executeOn( Properties props ) + throws BuildException + { + checkParameters(); + + // m_type may be null because it wasn't set + try + { + if( m_type == Type.INTEGER_TYPE ) + { + executeInteger( ( String )props.get( m_key ) ); + } + else if( m_type == Type.DATE_TYPE ) + { + executeDate( ( String )props.get( m_key ) ); + } + else if( m_type == Type.STRING_TYPE ) + { + executeString( ( String )props.get( m_key ) ); + } + else + { + throw new BuildException( "Unknown operation type: " + m_type + "" ); + } + } + catch( NullPointerException npe ) + { + // Default to string type + // which means do nothing + npe.printStackTrace(); + } + // Insert as a string by default + props.put( m_key, m_value ); + + } + + /** + * Check if parameter combinations can be supported + * + * @exception BuildException Description of Exception + */ + private void checkParameters() + throws BuildException + { + if( m_type == Type.STRING_TYPE && + m_operation == Operation.DECREMENT_OPER ) + { + throw new BuildException( "- is not suported for string properties (key:" + m_key + ")" ); + } + if( m_value == null && m_default == null ) + { + throw new BuildException( "value and/or default must be specified (key:" + m_key + ")" ); + } + if( m_key == null ) + { + throw new BuildException( "key is mandatory" ); + } + if( m_type == Type.STRING_TYPE && + m_pattern != null ) + { + throw new BuildException( "pattern is not suported for string properties (key:" + m_key + ")" ); + } + } + + /** + * Handle operations for type date. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeDate( String oldValue ) + throws BuildException + { + GregorianCalendar value = new GregorianCalendar(); + GregorianCalendar newValue = new GregorianCalendar(); + + if( m_pattern == null ) + m_pattern = "yyyy/MM/dd HH:mm"; + DateFormat fmt = new SimpleDateFormat( m_pattern ); + + // special case + if( m_default != null && + NOW_VALUE_.equals( m_default.toLowerCase() ) && + ( m_operation == Operation.INCREMENT_OPER || + m_operation == Operation.DECREMENT_OPER ) ) + { + oldValue = null; + } + + if( oldValue != null ) + { + try + { + value.setTime( fmt.parse( oldValue ) ); + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + + if( m_value != null ) + { + if( NOW_VALUE_.equals( m_value.toLowerCase() ) ) + { + value.setTime( new Date() ); + } + else if( NULL_VALUE_.equals( m_value.toLowerCase() ) ) + { + value = null; + } + else + { + try + { + value.setTime( fmt.parse( m_value ) ); + } + catch( Exception ex ) + { + // obviously not a date, try a simple int + try + { + int offset = Integer.parseInt( m_value ); + value.clear(); + value.set( Calendar.DAY_OF_YEAR, offset ); + } + catch( Exception ex_ ) + { + value.clear(); + value.set( Calendar.DAY_OF_YEAR, 1 ); + } + } + + } + } + + if( m_default != null && oldValue == null ) + { + if( NOW_VALUE_.equals( m_default.toLowerCase() ) ) + { + value.setTime( new Date() ); + } + else if( NULL_VALUE_.equals( m_default.toLowerCase() ) ) + { + value = null; + } + else + { + try + { + value.setTime( fmt.parse( m_default ) ); + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue.add( Calendar.SECOND, value.get( Calendar.SECOND ) ); + newValue.add( Calendar.MINUTE, value.get( Calendar.MINUTE ) ); + newValue.add( Calendar.HOUR_OF_DAY, value.get( Calendar.HOUR_OF_DAY ) ); + newValue.add( Calendar.DAY_OF_YEAR, value.get( Calendar.DAY_OF_YEAR ) ); + } + else if( m_operation == Operation.DECREMENT_OPER ) + { + newValue.add( Calendar.SECOND, -1 * value.get( Calendar.SECOND ) ); + newValue.add( Calendar.MINUTE, -1 * value.get( Calendar.MINUTE ) ); + newValue.add( Calendar.HOUR_OF_DAY, -1 * value.get( Calendar.HOUR_OF_DAY ) ); + newValue.add( Calendar.DAY_OF_YEAR, -1 * value.get( Calendar.DAY_OF_YEAR ) ); + } + if( newValue != null ) + { + m_value = fmt.format( newValue.getTime() ); + } + else + { + m_value = ""; + } + } + + + /** + * Handle operations for type int. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeInteger( String oldValue ) + throws BuildException + { + int value = 0; + int newValue = 0; + + DecimalFormat fmt = ( m_pattern != null ) ? new DecimalFormat( m_pattern ) + : new DecimalFormat(); + + if( oldValue != null ) + { + try + { + value = fmt.parse( oldValue ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + if( m_value != null ) + { + try + { + value = fmt.parse( m_value ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + if( m_default != null && oldValue == null ) + { + try + { + value = fmt.parse( m_default ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue = ++value; + } + else if( m_operation == Operation.DECREMENT_OPER ) + { + newValue = --value; + } + m_value = fmt.format( newValue ); + } + + /** + * Handle operations for type string. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeString( String oldValue ) + throws BuildException + { + String value = ""; + String newValue = ""; + + // the order of events is, of course, very important here + // default initially to the old value + if( oldValue != null ) + { + value = oldValue; + } + // but if a value is specified, use it + if( m_value != null ) + { + value = m_value; + } + // even if value is specified, ignore it and set to the default + // value if it is specified and there is no previous value + if( m_default != null && oldValue == null ) + { + value = m_default; + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue += value; + } + m_value = newValue; + } + + /** + * Enumerated attribute with the values "+", "-", "=", "now" and + * "never". + * + * @author RT + */ + public static class Operation extends EnumeratedAttribute + { + + // Property type operations + public final static int INCREMENT_OPER = 0; + public final static int DECREMENT_OPER = 1; + public final static int EQUALS_OPER = 2; + + // Special values + public final static int NOW_VALUE = 3; + public final static int NULL_VALUE = 4; + + public static int toOperation( String oper ) + { + if( "+".equals( oper ) ) + { + return INCREMENT_OPER; + } + else if( "-".equals( oper ) ) + { + return DECREMENT_OPER; + } + else if( NOW_VALUE_.equals( oper ) ) + { + return NOW_VALUE; + } + else if( NULL_VALUE_.equals( oper ) ) + { + return NULL_VALUE; + } + return EQUALS_OPER; + } + + public String[] getValues() + { + return new String[]{"+", "-", "=", NOW_VALUE_, NULL_VALUE_}; + } + } + + /** + * Enumerated attribute with the values "int", "date" and "string". + * + * @author RT + */ + public static class Type extends EnumeratedAttribute + { + + // Property types + public final static int INTEGER_TYPE = 0; + public final static int DATE_TYPE = 1; + public final static int STRING_TYPE = 2; + + public static int toType( String type ) + { + if( "int".equals( type ) ) + { + return INTEGER_TYPE; + } + else if( "date".equals( type ) ) + { + return DATE_TYPE; + } + return STRING_TYPE; + } + + public String[] getValues() + { + return new String[]{"int", "date", "string"}; + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java new file mode 100644 index 000000000..1aa5ab674 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.Move; +import org.apache.tools.ant.types.Mapper; + +/** + * @author dIon Gillard + * dion@multitask.com.au + * @author Stefan Bodewig + * @version 1.2 + */ +public class RenameExtensions extends MatchingTask +{ + + private String fromExtension = ""; + private String toExtension = ""; + private boolean replace = false; + + private Mapper.MapperType globType; + private File srcDir; + + + /** + * Creates new RenameExtensions + */ + public RenameExtensions() + { + super(); + globType = new Mapper.MapperType(); + globType.setValue( "glob" ); + } + + /** + * store fromExtension * + * + * @param from The new FromExtension value + */ + public void setFromExtension( String from ) + { + fromExtension = from; + } + + /** + * store replace attribute - this determines whether the target file should + * be overwritten if present + * + * @param replace The new Replace value + */ + public void setReplace( boolean replace ) + { + this.replace = replace; + } + + /** + * Set the source dir to find the files to be renamed. + * + * @param srcDir The new SrcDir value + */ + public void setSrcDir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * store toExtension * + * + * @param to The new ToExtension value + */ + public void setToExtension( String to ) + { + toExtension = to; + } + + /** + * Executes the task, i.e. does the actual compiler call + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // first off, make sure that we've got a from and to extension + if( fromExtension == null || toExtension == null || srcDir == null ) + { + throw new BuildException( "srcDir, fromExtension and toExtension " + + "attributes must be set!" ); + } + + log( "DEPRECATED - The renameext task is deprecated. Use move instead.", + Project.MSG_WARN ); + log( "Replace this with:", Project.MSG_INFO ); + log( "", + Project.MSG_INFO ); + log( " ", Project.MSG_INFO ); + log( " ", Project.MSG_INFO ); + log( "", Project.MSG_INFO ); + log( "using the same patterns on as you\'ve used here", + Project.MSG_INFO ); + + Move move = ( Move )project.createTask( "move" ); + move.setOwningTarget( target ); + move.setTaskName( getTaskName() ); + move.setLocation( getLocation() ); + move.setTodir( srcDir ); + move.setOverwrite( replace ); + + fileset.setDir( srcDir ); + move.addFileset( fileset ); + + Mapper me = move.createMapper(); + me.setType( globType ); + me.setFrom( "*" + fromExtension ); + me.setTo( "*" + toExtension ); + + move.execute(); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java new file mode 100644 index 000000000..ece6ed56e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.RegularExpression; +import org.apache.tools.ant.types.Substitution; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.regexp.Regexp; + +/** + *

      + * Task to do regular expression string replacements in a text
      + * file.  The input file(s) must be able to be properly processed by
      + * a Reader instance.  That is, they must be text only, no binary.
      + *
      + * The syntax of the regular expression depends on the implemtation that
      + * you choose to use. The system property ant.regexp.regexpimpl
      + * will be the classname of the implementation that will be used (the default is
      + * org.apache.tools.ant.util.regexp.JakartaOroRegexp and requires
      + * the Jakarta Oro Package). 
      + * For jdk  <= 1.3, there are two available implementations:
      + *   org.apache.tools.ant.util.regexp.JakartaOroRegexp (the default)
      + *        Requires  the jakarta-oro package
      + *
      + *   org.apache.tools.ant.util.regexp.JakartaRegexpRegexp
      + *        Requires the jakarta-regexp package
      + *
      + * For jdk >= 1.4 an additional implementation is available:
      + *   org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp
      + *        Requires the jdk 1.4 built in regular expression package.
      + * 
      Usage: Call Syntax: <replaceregexp file="file" match="pattern" + * replace="pattern" flags="options"? byline="true|false"? > + * regularexpression? substitution? fileset* </replaceregexp> NOTE: You + * must have either the file attribute specified, or at least one fileset + * subelement to operation on. You may not have the file attribute specified if + * you nest fileset elements inside this task. Also, you cannot specify both + * match and a regular expression subelement at the same time, nor can you + * specify the replace attribute and the substitution subelement at the same + * time. Attributes: file --> A single file to operation on (mutually + * exclusive with the fileset subelements) match --> The Regular expression + * to match replace --> The Expression replacement string flags --> The + * options to give to the replacement g = Substitute all occurrences. default is + * to replace only the first one i = Case insensitive match byline --> Should + * this file be processed a single line at a time (default is false) "true" + * indicates to perform replacement on a line by line basis "false" indicates to + * perform replacement on the whole file at once. Example: The following call + * could be used to replace an old property name in a ".properties" file with a + * new name. In the replace attribute, you can refer to any part of the match + * expression in parenthesis using backslash followed by a number like '\1'. + * <replaceregexp file="test.properties" match="MyProperty=(.*)" + * replace="NewProperty=\1" byline="true" />
      + * + * @author Matthew Inger + */ +public class ReplaceRegExp extends Task +{ + + private FileUtils fileUtils = FileUtils.newFileUtils(); + private boolean byline; + + private File file; + private Vector filesets; + private String flags;// Keep jdk 1.1 compliant so others can use this + private RegularExpression regex; + private Substitution subs; + + /** + * Default Constructor + */ + public ReplaceRegExp() + { + super(); + this.file = null; + this.filesets = new Vector(); + this.flags = ""; + this.byline = false; + + this.regex = null; + this.subs = null; + } + + public void setByLine( String byline ) + { + Boolean res = Boolean.valueOf( byline ); + if( res == null ) + res = Boolean.FALSE; + this.byline = res.booleanValue(); + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setFlags( String flags ) + { + this.flags = flags; + } + + public void setMatch( String match ) + { + if( regex != null ) + throw new BuildException( "Only one regular expression is allowed" ); + + regex = new RegularExpression(); + regex.setPattern( match ); + } + + public void setReplace( String replace ) + { + if( subs != null ) + throw new BuildException( "Only one substitution expression is allowed" ); + + subs = new Substitution(); + subs.setExpression( replace ); + } + + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + public RegularExpression createRegularExpression() + { + if( regex != null ) + throw new BuildException( "Only one regular expression is allowed." ); + + regex = new RegularExpression(); + return regex; + } + + public Substitution createSubstitution() + { + if( subs != null ) + throw new BuildException( "Only one substitution expression is allowed" ); + + subs = new Substitution(); + return subs; + } + + public void execute() + throws BuildException + { + if( regex == null ) + throw new BuildException( "No expression to match." ); + if( subs == null ) + throw new BuildException( "Nothing to replace expression with." ); + + if( file != null && filesets.size() > 0 ) + throw new BuildException( "You cannot supply the 'file' attribute and filesets at the same time." ); + + int options = 0; + + if( flags.indexOf( 'g' ) != -1 ) + options |= Regexp.REPLACE_ALL; + + if( flags.indexOf( 'i' ) != -1 ) + options |= Regexp.MATCH_CASE_INSENSITIVE; + + if( flags.indexOf( 'm' ) != -1 ) + options |= Regexp.MATCH_MULTILINE; + + if( flags.indexOf( 's' ) != -1 ) + options |= Regexp.MATCH_SINGLELINE; + + if( file != null && file.exists() ) + { + try + { + doReplace( file, options ); + } + catch( IOException e ) + { + log( "An error occurred processing file: '" + file.getAbsolutePath() + "': " + e.toString(), + Project.MSG_ERR ); + } + } + else if( file != null ) + { + log( "The following file is missing: '" + file.getAbsolutePath() + "'", + Project.MSG_ERR ); + } + + int sz = filesets.size(); + for( int i = 0; i < sz; i++ ) + { + FileSet fs = ( FileSet )( filesets.elementAt( i ) ); + DirectoryScanner ds = fs.getDirectoryScanner( getProject() ); + + String files[] = ds.getIncludedFiles(); + for( int j = 0; j < files.length; j++ ) + { + File f = new File( files[j] ); + if( f.exists() ) + { + try + { + doReplace( f, options ); + } + catch( Exception e ) + { + log( "An error occurred processing file: '" + f.getAbsolutePath() + "': " + e.toString(), + Project.MSG_ERR ); + } + } + else + { + log( "The following file is missing: '" + file.getAbsolutePath() + "'", + Project.MSG_ERR ); + } + } + } + } + + + protected String doReplace( RegularExpression r, + Substitution s, + String input, + int options ) + { + String res = input; + Regexp regexp = r.getRegexp( project ); + + if( regexp.matches( input, options ) ) + { + res = regexp.substitute( input, s.getExpression( project ), options ); + } + + return res; + } + + /** + * Perform the replace on the entire file + * + * @param f Description of Parameter + * @param options Description of Parameter + * @exception IOException Description of Exception + */ + protected void doReplace( File f, int options ) + throws IOException + { + File parentDir = new File( new File( f.getAbsolutePath() ).getParent() ); + File temp = fileUtils.createTempFile( "replace", ".txt", parentDir ); + + FileReader r = null; + FileWriter w = null; + + try + { + r = new FileReader( f ); + w = new FileWriter( temp ); + + BufferedReader br = new BufferedReader( r ); + BufferedWriter bw = new BufferedWriter( w ); + PrintWriter pw = new PrintWriter( bw ); + + boolean changes = false; + + log( "Replacing pattern '" + regex.getPattern( project ) + "' with '" + subs.getExpression( project ) + + "' in '" + f.getPath() + "'" + + ( byline ? " by line" : "" ) + + ( flags.length() > 0 ? " with flags: '" + flags + "'" : "" ) + + ".", + Project.MSG_WARN ); + + if( byline ) + { + LineNumberReader lnr = new LineNumberReader( br ); + String line = null; + + while( ( line = lnr.readLine() ) != null ) + { + String res = doReplace( regex, subs, line, options ); + if( !res.equals( line ) ) + changes = true; + + pw.println( res ); + } + pw.flush(); + } + else + { + int flen = ( int )( f.length() ); + char tmpBuf[] = new char[flen]; + int numread = 0; + int totread = 0; + while( numread != -1 && totread < flen ) + { + numread = br.read( tmpBuf, totread, flen ); + totread += numread; + } + + String buf = new String( tmpBuf ); + + String res = doReplace( regex, subs, buf, options ); + if( !res.equals( buf ) ) + changes = true; + + pw.println( res ); + pw.flush(); + } + + r.close(); + r = null; + w.close(); + w = null; + + if( changes ) + { + f.delete(); + temp.renameTo( f ); + } + else + { + temp.delete(); + } + } + finally + { + try + { + if( r != null ) + r.close(); + } + catch( Exception e ) + {} + ; + + try + { + if( w != null ) + r.close(); + } + catch( Exception e ) + {} + ; + } + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Rpm.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Rpm.java new file mode 100644 index 000000000..3811e701b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Rpm.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.taskdefs.PumpStreamHandler; +import org.apache.tools.ant.types.Commandline; + +/** + * @author lucas@collab.net + */ +public class Rpm extends Task +{ + + /** + * the rpm command to use + */ + private String command = "-bb"; + + /** + * clean BUILD directory + */ + private boolean cleanBuildDir = false; + + /** + * remove spec file + */ + private boolean removeSpec = false; + + /** + * remove sources + */ + private boolean removeSource = false; + + /** + * the file to direct standard error from the command + */ + private File error; + + /** + * the file to direct standard output from the command + */ + private File output; + + /** + * the spec file + */ + private String specFile; + + /** + * the rpm top dir + */ + private File topDir; + + public void setCleanBuildDir( boolean cbd ) + { + cleanBuildDir = cbd; + } + + public void setCommand( String c ) + { + this.command = c; + } + + public void setError( File error ) + { + this.error = error; + } + + public void setOutput( File output ) + { + this.output = output; + } + + public void setRemoveSource( boolean rs ) + { + removeSource = rs; + } + + public void setRemoveSpec( boolean rs ) + { + removeSpec = rs; + } + + public void setSpecFile( String sf ) + { + if( ( sf == null ) || ( sf.trim().equals( "" ) ) ) + { + throw new BuildException( "You must specify a spec file", location ); + } + this.specFile = sf; + } + + public void setTopDir( File td ) + { + this.topDir = td; + } + + public void execute() + throws BuildException + { + + Commandline toExecute = new Commandline(); + + toExecute.setExecutable( "rpm" ); + if( topDir != null ) + { + toExecute.createArgument().setValue( "--define" ); + toExecute.createArgument().setValue( "_topdir" + topDir ); + } + + toExecute.createArgument().setLine( command ); + + if( cleanBuildDir ) + { + toExecute.createArgument().setValue( "--clean" ); + } + if( removeSpec ) + { + toExecute.createArgument().setValue( "--rmspec" ); + } + if( removeSource ) + { + toExecute.createArgument().setValue( "--rmsource" ); + } + + toExecute.createArgument().setValue( "SPECS/" + specFile ); + + ExecuteStreamHandler streamhandler = null; + OutputStream outputstream = null; + OutputStream errorstream = null; + if( error == null && output == null ) + { + streamhandler = new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ); + } + else + { + if( output != null ) + { + try + { + outputstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + outputstream = new LogOutputStream( this, Project.MSG_INFO ); + } + if( error != null ) + { + try + { + errorstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( error ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + errorstream = new LogOutputStream( this, Project.MSG_WARN ); + } + streamhandler = new PumpStreamHandler( outputstream, errorstream ); + } + + Execute exe = new Execute( streamhandler, null ); + + exe.setAntRun( project ); + if( topDir == null ) + topDir = project.getBaseDir(); + exe.setWorkingDirectory( topDir ); + + exe.setCommandline( toExecute.getCommandline() ); + try + { + exe.execute(); + log( "Building the RPM based on the " + specFile + " file" ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + if( output != null ) + { + try + { + outputstream.close(); + } + catch( IOException e ) + {} + } + if( error != null ) + { + try + { + errorstream.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Script.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Script.java new file mode 100644 index 000000000..43a545ec1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Script.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import com.ibm.bsf.BSFException; +import com.ibm.bsf.BSFManager; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Execute a script + * + * @author Sam Ruby rubys@us.ibm.com + */ +public class Script extends Task +{ + private String script = ""; + private Hashtable beans = new Hashtable(); + private String language; + + /** + * Defines the language (required). + * + * @param language The new Language value + */ + public void setLanguage( String language ) + { + this.language = language; + } + + /** + * Load the script from an external file + * + * @param fileName The new Src value + */ + public void setSrc( String fileName ) + { + File file = new File( fileName ); + if( !file.exists() ) + throw new BuildException( "file " + fileName + " not found." ); + + int count = ( int )file.length(); + byte data[] = new byte[count]; + + try + { + FileInputStream inStream = new FileInputStream( file ); + inStream.read( data ); + inStream.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + script += new String( data ); + } + + /** + * Defines the script. + * + * @param text The feature to be added to the Text attribute + */ + public void addText( String text ) + { + this.script += text; + } + + /** + * Do the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + try + { + addBeans( project.getProperties() ); + addBeans( project.getUserProperties() ); + addBeans( project.getTargets() ); + addBeans( project.getReferences() ); + + beans.put( "project", getProject() ); + + beans.put( "self", this ); + + BSFManager manager = new BSFManager(); + + for( Enumeration e = beans.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + Object value = beans.get( key ); + manager.declareBean( key, value, value.getClass() ); + } + + // execute the script + manager.exec( language, "", 0, 0, script ); + } + catch( BSFException be ) + { + Throwable t = be; + Throwable te = be.getTargetException(); + if( te != null ) + { + if( te instanceof BuildException ) + { + throw ( BuildException )te; + } + else + { + t = te; + } + } + throw new BuildException( t ); + } + } + + /** + * Add a list of named objects to the list to be exported to the script + * + * @param dictionary The feature to be added to the Beans attribute + */ + private void addBeans( Hashtable dictionary ) + { + for( Enumeration e = dictionary.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + + boolean isValid = key.length() > 0 && + Character.isJavaIdentifierStart( key.charAt( 0 ) ); + + for( int i = 1; isValid && i < key.length(); i++ ) + isValid = Character.isJavaIdentifierPart( key.charAt( i ) ); + + if( isValid ) + beans.put( key, dictionary.get( key ) ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/StyleBook.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/StyleBook.java new file mode 100644 index 000000000..0f37fc687 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/StyleBook.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Java; + +/** + * Basic task for apache stylebook. + * + * @author Peter Donald + * @author Marcus + * Börger + */ +public class StyleBook + extends Java +{ + protected File m_book; + protected String m_loaderConfig; + protected File m_skinDirectory; + protected File m_targetDirectory; + + public StyleBook() + { + setClassname( "org.apache.stylebook.StyleBook" ); + setFork( true ); + setFailonerror( true ); + } + + public void setBook( final File book ) + { + m_book = book; + } + + public void setLoaderConfig( final String loaderConfig ) + { + m_loaderConfig = loaderConfig; + } + + public void setSkinDirectory( final File skinDirectory ) + { + m_skinDirectory = skinDirectory; + } + + public void setTargetDirectory( final File targetDirectory ) + { + m_targetDirectory = targetDirectory; + } + + public void execute() + throws BuildException + { + + if( null == m_targetDirectory ) + { + throw new BuildException( "TargetDirectory attribute not set." ); + } + + if( null == m_skinDirectory ) + { + throw new BuildException( "SkinDirectory attribute not set." ); + } + + if( null == m_book ) + { + throw new BuildException( "book attribute not set." ); + } + + createArg().setValue( "targetDirectory=" + m_targetDirectory ); + createArg().setValue( m_book.toString() ); + createArg().setValue( m_skinDirectory.toString() ); + if( null != m_loaderConfig ) + { + createArg().setValue( "loaderConfig=" + m_loaderConfig ); + } + + super.execute(); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Test.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Test.java new file mode 100644 index 000000000..3f248fdc4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Test.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Java; + +/** + * @author Peter Donald + */ +public class Test + extends Java +{ + + protected Vector m_tests = new Vector(); + + public Test() + { + setClassname( "org.apache.testlet.engine.TextTestEngine" ); + } + + public void setForceShowTrace( final boolean forceShowTrace ) + { + createArg().setValue( "-f=" + forceShowTrace ); + } + + public void setShowBanner( final String showBanner ) + { + createArg().setValue( "-b=" + showBanner ); + } + + public void setShowSuccess( final boolean showSuccess ) + { + createArg().setValue( "-s=" + showSuccess ); + } + + public void setShowTrace( final boolean showTrace ) + { + createArg().setValue( "-t=" + showTrace ); + } + + public TestletEntry createTestlet() + { + final TestletEntry entry = new TestletEntry(); + m_tests.addElement( entry ); + return entry; + } + + public void execute() + throws BuildException + { + + final int size = m_tests.size(); + + for( int i = 0; i < size; i++ ) + { + createArg().setValue( m_tests.elementAt( i ).toString() ); + } + + super.execute(); + } + + protected final static class TestletEntry + { + + protected String m_testname = ""; + + public void addText( final String testname ) + { + m_testname += testname; + } + + public String toString() + { + return m_testname; + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java new file mode 100644 index 000000000..e608bfcd2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.apache.tools.ant.taskdefs.XSLTLogger; +import org.apache.tools.ant.taskdefs.XSLTLoggerAware; + +/** + * Concrete liaison for XSLT processor implementing TraX. (ie JAXP 1.1) + * + * @author Sam Ruby + * @author Davanum Srinivas + * @author Stephane Bailliez + */ +public class TraXLiaison implements XSLTLiaison, ErrorListener, XSLTLoggerAware +{ + + /** + * The trax TransformerFactory + */ + private TransformerFactory tfactory = null; + + /** + * stylesheet stream, close it asap + */ + private FileInputStream xslStream = null; + + /** + * Stylesheet template + */ + private Templates templates = null; + + /** + * transformer + */ + private Transformer transformer = null; + + private XSLTLogger logger; + + public TraXLiaison() + throws Exception + { + tfactory = TransformerFactory.newInstance(); + tfactory.setErrorListener( this ); + } + + public void setLogger( XSLTLogger l ) + { + logger = l; + } + + public void setOutputtype( String type ) + throws Exception + { + transformer.setOutputProperty( OutputKeys.METHOD, type ); + } + +//------------------- IMPORTANT + // 1) Don't use the StreamSource(File) ctor. It won't work with + // xalan prior to 2.2 because of systemid bugs. + + // 2) Use a stream so that you can close it yourself quickly + // and avoid keeping the handle until the object is garbaged. + // (always keep control), otherwise you won't be able to delete + // the file quickly on windows. + + // 3) Always set the systemid to the source for imports, includes... + // in xsl and xml... + + public void setStylesheet( File stylesheet ) + throws Exception + { + xslStream = new FileInputStream( stylesheet ); + StreamSource src = new StreamSource( xslStream ); + src.setSystemId( getSystemId( stylesheet ) ); + templates = tfactory.newTemplates( src ); + transformer = templates.newTransformer(); + transformer.setErrorListener( this ); + } + + public void addParam( String name, String value ) + { + transformer.setParameter( name, value ); + } + + public void error( TransformerException e ) + { + logError( e, "Error" ); + } + + public void fatalError( TransformerException e ) + { + logError( e, "Fatal Error" ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileInputStream fis = null; + FileOutputStream fos = null; + try + { + fis = new FileInputStream( infile ); + fos = new FileOutputStream( outfile ); + StreamSource src = new StreamSource( fis ); + src.setSystemId( getSystemId( infile ) ); + StreamResult res = new StreamResult( fos ); + // not sure what could be the need of this... + res.setSystemId( getSystemId( outfile ) ); + + transformer.transform( src, res ); + } + finally + { + // make sure to close all handles, otherwise the garbage + // collector will close them...whenever possible and + // Windows may complain about not being able to delete files. + try + { + if( xslStream != null ) + { + xslStream.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fis != null ) + { + fis.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fos != null ) + { + fos.close(); + } + } + catch( IOException ignored ) + {} + } + } + + public void warning( TransformerException e ) + { + logError( e, "Warning" ); + } + + // make sure that the systemid is made of '/' and not '\' otherwise + // crimson will complain that it cannot resolve relative entities + // because it grabs the base uri via lastIndexOf('/') without + // making sure it is really a /'ed path + protected String getSystemId( File file ) + { + String path = file.getAbsolutePath(); + path = path.replace( '\\', '/' ); + return FILE_PROTOCOL_PREFIX + path; + } + + private void logError( TransformerException e, String type ) + { + StringBuffer msg = new StringBuffer(); + if( e.getLocator() != null ) + { + if( e.getLocator().getSystemId() != null ) + { + String url = e.getLocator().getSystemId(); + if( url.startsWith( "file:///" ) ) + url = url.substring( 8 ); + msg.append( url ); + } + else + { + msg.append( "Unknown file" ); + } + if( e.getLocator().getLineNumber() != -1 ) + { + msg.append( ":" + e.getLocator().getLineNumber() ); + if( e.getLocator().getColumnNumber() != -1 ) + { + msg.append( ":" + e.getLocator().getColumnNumber() ); + } + } + } + msg.append( ": " + type + "! " ); + msg.append( e.getMessage() ); + if( e.getCause() != null ) + { + msg.append( " Cause: " + e.getCause() ); + } + + logger.log( msg.toString() ); + } + +}//-- TraXLiaison diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java new file mode 100644 index 000000000..02abefc30 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java @@ -0,0 +1,648 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Parser; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.ParserAdapter; + +/** + * The XMLValidateTask checks that an XML document is valid, with a + * SAX validating parser. + * + * @author Raphael Pierquin + * raphael.pierquin@agisphere.com + */ +public class XMLValidateTask extends Task +{ + + /** + * The default implementation parser classname used by the task to process + * validation. + */ + // The crimson implementation is shipped with ant. + public static String DEFAULT_XML_READER_CLASSNAME = "org.apache.crimson.parser.XMLReaderImpl"; + + protected static String INIT_FAILED_MSG = "Could not start xml validation: "; + + // ant task properties + // defaults + protected boolean failOnError = true; + protected boolean warn = true; + protected boolean lenient = false; + protected String readerClassName = DEFAULT_XML_READER_CLASSNAME; + + protected File file = null;// file to be validated + protected Vector filesets = new Vector(); + + /** + * the parser is viewed as a SAX2 XMLReader. If a SAX1 parser is specified, + * it's wrapped in an adapter that make it behave as a XMLReader. a more + * 'standard' way of doing this would be to use the JAXP1.1 SAXParser + * interface. + */ + protected XMLReader xmlReader = null;// XMLReader used to validation process + protected ValidatorErrorHandler errorHandler + = new ValidatorErrorHandler();// to report sax parsing errors + protected Hashtable features = new Hashtable(); + + /** + * The list of configured DTD locations + */ + public Vector dtdLocations = new Vector();// sets of file to be validated + protected Path classpath; + + /** + * Specify the class name of the SAX parser to be used. (optional) + * + * @param className should be an implementation of SAX2 org.xml.sax.XMLReader + * or SAX2 org.xml.sax.Parser.

      + * + * if className is an implementation of org.xml.sax.Parser + * , {@link #setLenient(boolean)}, will be ignored.

      + * + * if not set, the default {@link #DEFAULT_XML_READER_CLASSNAME} will + * be used. + * @see org.xml.sax.XMLReader + * @see org.xml.sax.Parser + */ + public void setClassName( String className ) + { + + readerClassName = className; + } + + + /** + * Specify the classpath to be searched to load the parser (optional) + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * @param r The new ClasspathRef value + * @see #setClasspath + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Specify how parser error are to be handled.

      + * + * If set to true (default), throw a buildException if the + * parser yields an error. + * + * @param fail The new FailOnError value + */ + public void setFailOnError( boolean fail ) + { + + failOnError = fail; + } + + /** + * specifify the file to be checked + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Specify whether the parser should be validating. Default is true + * .

      + * + * If set to false, the validation will fail only if the parsed document is + * not well formed XML.

      + * + * this option is ignored if the specified class with {@link + * #setClassName(String)} is not a SAX2 XMLReader. + * + * @param bool The new Lenient value + */ + public void setLenient( boolean bool ) + { + + lenient = bool; + } + + /** + * Specify how parser error are to be handled.

      + * + * If set to true + * + *(default), log a warn message for each SAX warn event. + * + * @param bool The new Warn value + */ + public void setWarn( boolean bool ) + { + + warn = bool; + } + + /** + * specifify a set of file to be checked + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * @return Description of the Returned Value + * @see #setClasspath + */ + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + /** + * Create a DTD location record. This stores the location of a DTD. The DTD + * is identified by its public Id. The location may either be a file + * location or a resource location. + * + * @return Description of the Returned Value + */ + public DTDLocation createDTD() + { + DTDLocation dtdLocation = new DTDLocation(); + dtdLocations.addElement( dtdLocation ); + + return dtdLocation; + } + + public void execute() + throws BuildException + { + + int fileProcessed = 0; + if( file == null && ( filesets.size() == 0 ) ) + { + throw new BuildException( "Specify at least one source - a file or a fileset." ); + } + + initValidator(); + + if( file != null ) + { + if( file.exists() && file.canRead() && file.isFile() ) + { + doValidate( file ); + fileProcessed++; + } + else + { + String errorMsg = "File " + file + " cannot be read"; + if( failOnError ) + throw new BuildException( errorMsg ); + else + log( errorMsg, Project.MSG_ERR ); + } + } + + for( int i = 0; i < filesets.size(); i++ ) + { + + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] files = ds.getIncludedFiles(); + + for( int j = 0; j < files.length; j++ ) + { + File srcFile = new File( fs.getDir( project ), files[j] ); + doValidate( srcFile ); + fileProcessed++; + } + } + log( fileProcessed + " file(s) have been successfully validated." ); + } + + protected EntityResolver getEntityResolver() + { + LocalResolver resolver = new LocalResolver(); + + for( Enumeration i = dtdLocations.elements(); i.hasMoreElements(); ) + { + DTDLocation location = ( DTDLocation )i.nextElement(); + resolver.registerDTD( location ); + } + return resolver; + } + + /* + * set a feature on the parser. + * TODO: find a way to set any feature from build.xml + */ + private boolean setFeature( String feature, boolean value, boolean warn ) + { + + boolean toReturn = false; + try + { + xmlReader.setFeature( feature, value ); + toReturn = true; + } + catch( SAXNotRecognizedException e ) + { + if( warn ) + log( "Could not set feature '" + + feature + + "' because the parser doesn't recognize it", + Project.MSG_WARN ); + } + catch( SAXNotSupportedException e ) + { + if( warn ) + log( "Could not set feature '" + + feature + + "' because the parser doesn't support it", + Project.MSG_WARN ); + } + return toReturn; + } + + /* + * parse the file + */ + private void doValidate( File afile ) + { + try + { + log( "Validating " + afile.getName() + "... ", Project.MSG_VERBOSE ); + errorHandler.init( afile ); + InputSource is = new InputSource( new FileReader( afile ) ); + String uri = "file:" + afile.getAbsolutePath().replace( '\\', '/' ); + for( int index = uri.indexOf( '#' ); index != -1; + index = uri.indexOf( '#' ) ) + { + uri = uri.substring( 0, index ) + "%23" + uri.substring( index + 1 ); + } + is.setSystemId( uri ); + xmlReader.parse( is ); + } + catch( SAXException ex ) + { + if( failOnError ) + throw new BuildException( "Could not validate document " + afile ); + } + catch( IOException ex ) + { + throw new BuildException( "Could not validate document " + afile, ex ); + } + + if( errorHandler.getFailure() ) + { + if( failOnError ) + throw new BuildException( afile + " is not a valid XML document." ); + else + log( afile + " is not a valid XML document", Project.MSG_ERR ); + } + } + + /** + * init the parser : load the parser class, and set features if necessary + */ + private void initValidator() + { + + try + { + // load the parser class + // with JAXP, we would use a SAXParser factory + Class readerClass = null; + //Class readerImpl = null; + //Class parserImpl = null; + if( classpath != null ) + { + AntClassLoader loader = new AntClassLoader( project, classpath ); +// loader.addSystemPackageRoot("org.xml"); // needed to avoid conflict + readerClass = loader.loadClass( readerClassName ); + AntClassLoader.initializeClass( readerClass ); + } + else + readerClass = Class.forName( readerClassName ); + + // then check it implements XMLReader + if( XMLReader.class.isAssignableFrom( readerClass ) ) + { + + xmlReader = ( XMLReader )readerClass.newInstance(); + log( "Using SAX2 reader " + readerClassName, Project.MSG_VERBOSE ); + } + else + { + + // see if it is a SAX1 Parser + if( Parser.class.isAssignableFrom( readerClass ) ) + { + Parser parser = ( Parser )readerClass.newInstance(); + xmlReader = new ParserAdapter( parser ); + log( "Using SAX1 parser " + readerClassName, Project.MSG_VERBOSE ); + } + else + { + throw new BuildException( INIT_FAILED_MSG + + readerClassName + + " implements nor SAX1 Parser nor SAX2 XMLReader." ); + } + } + } + catch( ClassNotFoundException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + catch( InstantiationException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + + xmlReader.setEntityResolver( getEntityResolver() ); + xmlReader.setErrorHandler( errorHandler ); + + if( !( xmlReader instanceof ParserAdapter ) ) + { + // turn validation on + if( !lenient ) + { + boolean ok = setFeature( "http://xml.org/sax/features/validation", true, true ); + if( !ok ) + { + throw new BuildException( INIT_FAILED_MSG + + readerClassName + + " doesn't provide validation" ); + } + } + // set other features + Enumeration enum = features.keys(); + while( enum.hasMoreElements() ) + { + String featureId = ( String )enum.nextElement(); + setFeature( featureId, ( ( Boolean )features.get( featureId ) ).booleanValue(), true ); + } + } + } + + public static class DTDLocation + { + private String publicId = null; + private String location = null; + + public void setLocation( String location ) + { + this.location = location; + } + + public void setPublicId( String publicId ) + { + this.publicId = publicId; + } + + public String getLocation() + { + return location; + } + + public String getPublicId() + { + return publicId; + } + } + + /* + * ValidatorErrorHandler role : + *

        + *
      • log SAX parse exceptions, + *
      • remember if an error occured + *
      + */ + protected class ValidatorErrorHandler implements ErrorHandler + { + + protected File currentFile = null; + protected String lastErrorMessage = null; + protected boolean failed = false; + + // did an error happen during last parsing ? + public boolean getFailure() + { + + return failed; + } + + public void error( SAXParseException exception ) + { + failed = true; + doLog( exception, Project.MSG_ERR ); + } + + public void fatalError( SAXParseException exception ) + { + failed = true; + doLog( exception, Project.MSG_ERR ); + } + + public void init( File file ) + { + currentFile = file; + failed = false; + } + + public void warning( SAXParseException exception ) + { + // depending on implementation, XMLReader can yield hips of warning, + // only output then if user explicitely asked for it + if( warn ) + doLog( exception, Project.MSG_WARN ); + } + + private String getMessage( SAXParseException e ) + { + String sysID = e.getSystemId(); + if( sysID != null ) + { + try + { + int line = e.getLineNumber(); + int col = e.getColumnNumber(); + return new URL( sysID ).getFile() + + ( line == -1 ? "" : ( ":" + line + + ( col == -1 ? "" : ( ":" + col ) ) ) ) + + ": " + e.getMessage(); + } + catch( MalformedURLException mfue ) + { + } + } + return e.getMessage(); + } + + private void doLog( SAXParseException e, int logLevel ) + { + + log( getMessage( e ), logLevel ); + } + } + + private class LocalResolver + implements EntityResolver + { + private Hashtable fileDTDs = new Hashtable(); + private Hashtable resourceDTDs = new Hashtable(); + private Hashtable urlDTDs = new Hashtable(); + + public LocalResolver() { } + + public void registerDTD( String publicId, String location ) + { + if( location == null ) + { + return; + } + + File fileDTD = new File( location ); + if( fileDTD.exists() ) + { + if( publicId != null ) + { + fileDTDs.put( publicId, fileDTD ); + log( "Mapped publicId " + publicId + " to file " + fileDTD, Project.MSG_VERBOSE ); + } + return; + } + + if( LocalResolver.this.getClass().getResource( location ) != null ) + { + if( publicId != null ) + { + resourceDTDs.put( publicId, location ); + log( "Mapped publicId " + publicId + " to resource " + location, Project.MSG_VERBOSE ); + } + } + + try + { + if( publicId != null ) + { + URL urldtd = new URL( location ); + urlDTDs.put( publicId, urldtd ); + } + } + catch( java.net.MalformedURLException e ) + { + //ignored + } + } + + public void registerDTD( DTDLocation location ) + { + registerDTD( location.getPublicId(), location.getLocation() ); + } + + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + File dtdFile = ( File )fileDTDs.get( publicId ); + if( dtdFile != null ) + { + try + { + log( "Resolved " + publicId + " to local file " + dtdFile, Project.MSG_VERBOSE ); + return new InputSource( new FileInputStream( dtdFile ) ); + } + catch( FileNotFoundException ex ) + { + // ignore + } + } + + String dtdResourceName = ( String )resourceDTDs.get( publicId ); + if( dtdResourceName != null ) + { + InputStream is = this.getClass().getResourceAsStream( dtdResourceName ); + if( is != null ) + { + log( "Resolved " + publicId + " to local resource " + dtdResourceName, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + } + + URL dtdUrl = ( URL )urlDTDs.get( publicId ); + if( dtdUrl != null ) + { + try + { + InputStream is = dtdUrl.openStream(); + log( "Resolved " + publicId + " to url " + dtdUrl, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + catch( IOException ioe ) + { + //ignore + } + } + + log( "Could not resolve ( publicId: " + publicId + ", systemId: " + systemId + ") to a local entity", + Project.MSG_INFO ); + + return null; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java new file mode 100644 index 000000000..3798629fc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.apache.xalan.xslt.XSLTInputSource; +import org.apache.xalan.xslt.XSLTProcessor; +import org.apache.xalan.xslt.XSLTProcessorFactory; +import org.apache.xalan.xslt.XSLTResultTarget; + +/** + * Concrete liaison for Xalan 1.x API. + * + * @author Sam Ruby + * @author Stephane Bailliez + */ +public class XalanLiaison implements XSLTLiaison +{ + + protected XSLTProcessor processor; + protected File stylesheet; + + public XalanLiaison() + throws Exception + { + processor = XSLTProcessorFactory.getProcessor(); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File stylesheet ) + throws Exception + { + this.stylesheet = stylesheet; + } + + public void addParam( String name, String value ) + { + processor.setStylesheetParam( name, value ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileInputStream fis = null; + FileOutputStream fos = null; + FileInputStream xslStream = null; + try + { + xslStream = new FileInputStream( stylesheet ); + fis = new FileInputStream( infile ); + fos = new FileOutputStream( outfile ); + // systemid such as file:/// + getAbsolutePath() are considered + // invalid here... + XSLTInputSource xslSheet = new XSLTInputSource( xslStream ); + xslSheet.setSystemId( stylesheet.getAbsolutePath() ); + XSLTInputSource src = new XSLTInputSource( fis ); + src.setSystemId( infile.getAbsolutePath() ); + XSLTResultTarget res = new XSLTResultTarget( fos ); + processor.process( src, xslSheet, res ); + } + finally + { + // make sure to close all handles, otherwise the garbage + // collector will close them...whenever possible and + // Windows may complain about not being able to delete files. + try + { + if( xslStream != null ) + { + xslStream.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fis != null ) + { + fis.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fos != null ) + { + fos.close(); + } + } + catch( IOException ignored ) + {} + } + } +}//-- XalanLiaison diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java new file mode 100644 index 000000000..7ae71c808 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import com.kvisco.xsl.XSLProcessor; +import com.kvisco.xsl.XSLReader; +import com.kvisco.xsl.XSLStylesheet; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; + + + +/** + * Concrete liaison for XSLP + * + * @author Sam Ruby + * @author Stephane Bailliez + */ +public class XslpLiaison implements XSLTLiaison +{ + + protected XSLProcessor processor; + protected XSLStylesheet xslSheet; + + public XslpLiaison() + { + processor = new XSLProcessor(); + // uh ?! I'm forced to do that otherwise a setProperty crashes with NPE ! + // I don't understand why the property map is static though... + // how can we do multithreading w/ multiple identical parameters ? + processor.getProperty( "dummy-to-init-properties-map" ); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File fileName ) + throws Exception + { + XSLReader xslReader = new XSLReader(); + // a file:/// + getAbsolutePath() does not work here + // it is really the pathname + xslSheet = xslReader.read( fileName.getAbsolutePath() ); + } + + public void addParam( String name, String expression ) + { + processor.setProperty( name, expression ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileOutputStream fos = new FileOutputStream( outfile ); + // XSLP does not support encoding...we're in hot water. + OutputStreamWriter out = new OutputStreamWriter( fos, "UTF8" ); + processor.process( infile.getAbsolutePath(), xslSheet, out ); + } + +}//-- XSLPLiaison diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java new file mode 100644 index 000000000..974e06ce5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * Class common to all check commands (checkout, checkin,checkin default task); + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheck extends Continuus +{ + + /** + * -comment flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "/comment"; + + /** + * -task flag -- associate checckout task with task + */ + public final static String FLAG_TASK = "/task"; + + private File _file = null; + private String _comment = null; + private String _task = null; + + public CCMCheck() + { + super(); + } + + /** + * Set the value of comment. + * + * @param v Value to assign to comment. + */ + public void setComment( String v ) + { + this._comment = v; + } + + /** + * Set the value of file. + * + * @param v Value to assign to file. + */ + public void setFile( File v ) + { + this._file = v; + } + + /** + * Set the value of task. + * + * @param v Value to assign to task. + */ + public void setTask( String v ) + { + this._task = v; + } + + /** + * Get the value of comment. + * + * @return value of comment. + */ + public String getComment() + { + return _comment; + } + + /** + * Get the value of file. + * + * @return value of file. + */ + public File getFile() + { + return _file; + } + + + /** + * Get the value of task. + * + * @return value of task. + */ + public String getTask() + { + return _task; + } + + + /** + * Executes the task.

      + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

      + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format is + // ccm co /t .. files + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + + if( getTask() != null ) + { + cmd.createArgument().setValue( FLAG_TASK ); + cmd.createArgument().setValue( getTask() ); + }// end of if () + + if( getFile() != null ) + { + cmd.createArgument().setValue( _file.getAbsolutePath() ); + }// end of if () + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java new file mode 100644 index 000000000..ed7d1d1f1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.util.Date; + +/** + * Task to perform Checkin command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckin extends CCMCheck +{ + + public CCMCheckin() + { + super(); + setCcmAction( COMMAND_CHECKIN ); + setComment( "Checkin " + new Date() ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java new file mode 100644 index 000000000..de5fb324d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; + +/** + * Task to perform Checkin Default task command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckinDefault extends CCMCheck +{ + + public final static String DEFAULT_TASK = "default"; + + public CCMCheckinDefault() + { + super(); + setCcmAction( COMMAND_CHECKIN ); + setTask( DEFAULT_TASK ); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java new file mode 100644 index 000000000..a01d72e68 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; + +/** + * Task to perform Checkout command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckout extends CCMCheck +{ + + public CCMCheckout() + { + super(); + setCcmAction( COMMAND_CHECKOUT ); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java new file mode 100644 index 000000000..f4ed8a11a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java @@ -0,0 +1,356 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * Task allows to create new ccm task and set it as the default + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCreateTask extends Continuus implements ExecuteStreamHandler +{ + + /** + * /comment -- comments associated to the task + */ + public final static String FLAG_COMMENT = "/synopsis"; + + /** + * /platform flag -- target platform + */ + public final static String FLAG_PLATFORM = "/plat"; + + /** + * /resolver flag + */ + public final static String FLAG_RESOLVER = "/resolver"; + + /** + * /release flag + */ + public final static String FLAG_RELEASE = "/release"; + + /** + * /release flag + */ + public final static String FLAG_SUBSYSTEM = "/subsystem"; + + /** + * -task flag -- associate checckout task with task + */ + public final static String FLAG_TASK = "/task"; + + private String comment = null; + private String platform = null; + private String resolver = null; + private String release = null; + private String subSystem = null; + private String task = null; + + public CCMCreateTask() + { + super(); + setCcmAction( COMMAND_CREATE_TASK ); + } + + /** + * Set the value of comment. + * + * @param v Value to assign to comment. + */ + public void setComment( String v ) + { + this.comment = v; + } + + /** + * Set the value of platform. + * + * @param v Value to assign to platform. + */ + public void setPlatform( String v ) + { + this.platform = v; + } + + /** + * @param is The new ProcessErrorStream value + * @exception IOException Description of Exception + */ + public void setProcessErrorStream( InputStream is ) + throws IOException + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String s = reader.readLine(); + if( s != null ) + { + log( "err " + s, Project.MSG_DEBUG ); + }// end of if () + } + + /** + * @param param1 + * @exception IOException Description of Exception + */ + public void setProcessInputStream( OutputStream param1 ) + throws IOException { } + + /** + * read the output stream to retrieve the new task number. + * + * @param is InputStream + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + + String buffer = ""; + try + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + buffer = reader.readLine(); + if( buffer != null ) + { + log( "buffer:" + buffer, Project.MSG_DEBUG ); + String taskstring = buffer.substring( buffer.indexOf( ' ' ) ).trim(); + taskstring = taskstring.substring( 0, taskstring.lastIndexOf( ' ' ) ).trim(); + setTask( taskstring ); + log( "task is " + getTask(), Project.MSG_DEBUG ); + }// end of if () + } + catch( NullPointerException npe ) + { + log( "error procession stream , null pointer exception", Project.MSG_ERR ); + npe.printStackTrace(); + throw new BuildException( npe.getClass().getName() ); + }// end of catch + catch( Exception e ) + { + log( "error procession stream " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( e.getMessage() ); + }// end of try-catch + + } + + /** + * Set the value of release. + * + * @param v Value to assign to release. + */ + public void setRelease( String v ) + { + this.release = v; + } + + /** + * Set the value of resolver. + * + * @param v Value to assign to resolver. + */ + public void setResolver( String v ) + { + this.resolver = v; + } + + /** + * Set the value of subSystem. + * + * @param v Value to assign to subSystem. + */ + public void setSubSystem( String v ) + { + this.subSystem = v; + } + + /** + * Set the value of task. + * + * @param v Value to assign to task. + */ + public void setTask( String v ) + { + this.task = v; + } + + + /** + * Get the value of comment. + * + * @return value of comment. + */ + public String getComment() + { + return comment; + } + + + /** + * Get the value of platform. + * + * @return value of platform. + */ + public String getPlatform() + { + return platform; + } + + + /** + * Get the value of release. + * + * @return value of release. + */ + public String getRelease() + { + return release; + } + + + /** + * Get the value of resolver. + * + * @return value of resolver. + */ + public String getResolver() + { + return resolver; + } + + /** + * Get the value of subSystem. + * + * @return value of subSystem. + */ + public String getSubSystem() + { + return subSystem; + } + + + /** + * Get the value of task. + * + * @return value of task. + */ + public String getTask() + { + return task; + } + + + /** + * Executes the task.

      + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

      + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format + // as specified in the CCM.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine, this ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + + //create task ok, set this task as the default one + Commandline commandLine2 = new Commandline(); + commandLine2.setExecutable( getCcmCommand() ); + commandLine2.createArgument().setValue( COMMAND_DEFAULT_TASK ); + commandLine2.createArgument().setValue( getTask() ); + + log( commandLine.toString(), Project.MSG_DEBUG ); + + result = run( commandLine2 ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine2.toString(); + throw new BuildException( msg, location ); + } + + } + + + // implementation of org.apache.tools.ant.taskdefs.ExecuteStreamHandler interface + + /** + * @exception IOException Description of Exception + */ + public void start() + throws IOException { } + + /** + */ + public void stop() { } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( "\"" + getComment() + "\"" ); + } + + if( getPlatform() != null ) + { + cmd.createArgument().setValue( FLAG_PLATFORM ); + cmd.createArgument().setValue( getPlatform() ); + }// end of if () + + if( getResolver() != null ) + { + cmd.createArgument().setValue( FLAG_RESOLVER ); + cmd.createArgument().setValue( getResolver() ); + }// end of if () + + if( getSubSystem() != null ) + { + cmd.createArgument().setValue( FLAG_SUBSYSTEM ); + cmd.createArgument().setValue( "\"" + getSubSystem() + "\"" ); + }// end of if () + + if( getRelease() != null ) + { + cmd.createArgument().setValue( FLAG_RELEASE ); + cmd.createArgument().setValue( getRelease() ); + }// end of if () + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java new file mode 100644 index 000000000..d079f2ae7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + +/** + * Task allows to reconfigure a project, recurcively or not + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMReconfigure extends Continuus +{ + + /** + * /recurse -- + */ + public final static String FLAG_RECURSE = "/recurse"; + + /** + * /recurse -- + */ + public final static String FLAG_VERBOSE = "/verbose"; + + /** + * /project flag -- target project + */ + public final static String FLAG_PROJECT = "/project"; + + private String project = null; + private boolean recurse = false; + private boolean verbose = false; + + public CCMReconfigure() + { + super(); + setCcmAction( COMMAND_RECONFIGURE ); + } + + /** + * Set the value of project. + * + * @param v Value to assign to project. + */ + public void setCcmProject( String v ) + { + this.project = v; + } + + /** + * Set the value of recurse. + * + * @param v Value to assign to recurse. + */ + public void setRecurse( boolean v ) + { + this.recurse = v; + } + + /** + * Set the value of verbose. + * + * @param v Value to assign to verbose. + */ + public void setVerbose( boolean v ) + { + this.verbose = v; + } + + /** + * Get the value of project. + * + * @return value of project. + */ + public String getCcmProject() + { + return project; + } + + + /** + * Get the value of recurse. + * + * @return value of recurse. + */ + public boolean isRecurse() + { + return recurse; + } + + + /** + * Get the value of verbose. + * + * @return value of verbose. + */ + public boolean isVerbose() + { + return verbose; + } + + + /** + * Executes the task.

      + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

      + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format + // as specified in the CCM.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + + if( isRecurse() == true ) + { + cmd.createArgument().setValue( FLAG_RECURSE ); + }// end of if () + + if( isVerbose() == true ) + { + cmd.createArgument().setValue( FLAG_VERBOSE ); + }// end of if () + + if( getCcmProject() != null ) + { + cmd.createArgument().setValue( FLAG_PROJECT ); + cmd.createArgument().setValue( getCcmProject() ); + } + + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java new file mode 100644 index 000000000..efc30650d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * A base class for creating tasks for executing commands on Continuus 5.1

      + * + * The class extends the task as it operates by executing the ccm.exe program + * supplied with Continuus/Synergy. By default the task expects the ccm + * executable to be in the path, you can override this be specifying the ccmdir + * attribute.

      + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public abstract class Continuus extends Task +{ + + /** + * Constant for the thing to execute + */ + private final static String CCM_EXE = "ccm"; + + /** + * The 'CreateTask' command + */ + public final static String COMMAND_CREATE_TASK = "create_task"; + /** + * The 'Checkout' command + */ + public final static String COMMAND_CHECKOUT = "co"; + /** + * The 'Checkin' command + */ + public final static String COMMAND_CHECKIN = "ci"; + /** + * The 'Reconfigure' command + */ + public final static String COMMAND_RECONFIGURE = "reconfigure"; + + /** + * The 'Reconfigure' command + */ + public final static String COMMAND_DEFAULT_TASK = "default_task"; + + private String _ccmDir = ""; + private String _ccmAction = ""; + + + /** + * Set the directory where the ccm executable is located + * + * @param dir the directory containing the ccm executable + */ + public final void setCcmDir( String dir ) + { + _ccmDir = project.translatePath( dir ); + } + + /** + * Set the value of ccmAction. + * + * @param v Value to assign to ccmAction. + */ + public void setCcmAction( String v ) + { + this._ccmAction = v; + } + + /** + * Get the value of ccmAction. + * + * @return value of ccmAction. + */ + public String getCcmAction() + { + return _ccmAction; + } + + /** + * Builds and returns the command string to execute ccm + * + * @return String containing path to the executable + */ + protected final String getCcmCommand() + { + String toReturn = _ccmDir; + if( !toReturn.equals( "" ) && !toReturn.endsWith( "/" ) ) + { + toReturn += "/"; + } + + toReturn += CCM_EXE; + + return toReturn; + } + + + protected int run( Commandline cmd, ExecuteStreamHandler handler ) + { + try + { + Execute exe = new Execute( handler ); + exe.setAntRun( getProject() ); + exe.setWorkingDirectory( getProject().getBaseDir() ); + exe.setCommandline( cmd.getCommandline() ); + return exe.execute(); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + + protected int run( Commandline cmd ) + { + return run( cmd, new LogStreamHandler( this, Project.MSG_VERBOSE, Project.MSG_WARN ) ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java new file mode 100644 index 000000000..e4104295a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform Checkin command to ClearCase.

      + * + * The following attributes are interpreted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * comment + * + * + * + * Specify a comment. Only one of comment or cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * commentfile + * + * + * + * Specify a file containing a comment. Only one of comment or + * cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * nowarn + * + * + * + * Suppress warning messages + * + * + * + * No + * + * + * + * + * + * + * + * preservetime + * + * + * + * Preserve the modification time + * + * + * + * No + * + * + * + * + * + * + * + * keepcopy + * + * + * + * Keeps a copy of the file with a .keep extension + * + * + * + * + * No + * + * + * + * + * + * + * + * identical + * + * + * + * Allows the file to be checked in even if it is + * identical to the original + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCCheckin extends ClearCase +{ + + /** + * -c flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "-c"; + /** + * -cfile flag -- file containing a comment to attach to the file + */ + public final static String FLAG_COMMENTFILE = "-cfile"; + /** + * -nc flag -- no comment is specified + */ + public final static String FLAG_NOCOMMENT = "-nc"; + /** + * -nwarn flag -- suppresses warning messages + */ + public final static String FLAG_NOWARN = "-nwarn"; + /** + * -ptime flag -- preserves the modification time + */ + public final static String FLAG_PRESERVETIME = "-ptime"; + /** + * -keep flag -- keeps a copy of the file with a .keep extension + */ + public final static String FLAG_KEEPCOPY = "-keep"; + /** + * -identical flag -- allows the file to be checked in even if it is + * identical to the original + */ + public final static String FLAG_IDENTICAL = "-identical"; + private String m_Comment = null; + private String m_Cfile = null; + private boolean m_Nwarn = false; + private boolean m_Ptime = false; + private boolean m_Keep = false; + private boolean m_Identical = true; + + + /** + * Set comment string + * + * @param comment the comment string + */ + public void setComment( String comment ) + { + m_Comment = comment; + } + + /** + * Set comment file + * + * @param cfile the path to the comment file + */ + public void setCommentFile( String cfile ) + { + m_Cfile = cfile; + } + + /** + * Set the identical flag + * + * @param identical the status to set the flag to + */ + public void setIdentical( boolean identical ) + { + m_Identical = identical; + } + + /** + * Set the keepcopy flag + * + * @param keep the status to set the flag to + */ + public void setKeepCopy( boolean keep ) + { + m_Keep = keep; + } + + /** + * Set the nowarn flag + * + * @param nwarn the status to set the flag to + */ + public void setNoWarn( boolean nwarn ) + { + m_Nwarn = nwarn; + } + + /** + * Set preservetime flag + * + * @param ptime the status to set the flag to + */ + public void setPreserveTime( boolean ptime ) + { + m_Ptime = ptime; + } + + /** + * Get comment string + * + * @return String containing the comment + */ + public String getComment() + { + return m_Comment; + } + + /** + * Get comment file + * + * @return String containing the path to the comment file + */ + public String getCommentFile() + { + return m_Cfile; + } + + /** + * Get identical flag status + * + * @return boolean containing status of identical flag + */ + public boolean getIdentical() + { + return m_Identical; + } + + /** + * Get keepcopy flag status + * + * @return boolean containing status of keepcopy flag + */ + public boolean getKeepCopy() + { + return m_Keep; + } + + /** + * Get nowarn flag status + * + * @return boolean containing status of nwarn flag + */ + public boolean getNoWarn() + { + return m_Nwarn; + } + + /** + * Get preservetime flag status + * + * @return boolean containing status of preservetime flag + */ + public boolean getPreserveTime() + { + return m_Ptime; + } + + /** + * Executes the task.

      + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got. the format is + // cleartool checkin [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_CHECKIN ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Get the 'comment' command + * + * @param cmd Description of Parameter + */ + private void getCommentCommand( Commandline cmd ) + { + if( getComment() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + } + + /** + * Get the 'commentfile' command + * + * @param cmd Description of Parameter + */ + private void getCommentFileCommand( Commandline cmd ) + { + if( getCommentFile() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENTFILE ); + cmd.createArgument().setValue( getCommentFile() ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + // -c + getCommentCommand( cmd ); + } + else + { + if( getCommentFile() != null ) + { + // -cfile + getCommentFileCommand( cmd ); + } + else + { + cmd.createArgument().setValue( FLAG_NOCOMMENT ); + } + } + + if( getNoWarn() ) + { + // -nwarn + cmd.createArgument().setValue( FLAG_NOWARN ); + } + + if( getPreserveTime() ) + { + // -ptime + cmd.createArgument().setValue( FLAG_PRESERVETIME ); + } + + if( getKeepCopy() ) + { + // -keep + cmd.createArgument().setValue( FLAG_KEEPCOPY ); + } + + if( getIdentical() ) + { + // -identical + cmd.createArgument().setValue( FLAG_IDENTICAL ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java new file mode 100644 index 000000000..7c649bc5b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java @@ -0,0 +1,602 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform Checkout command to ClearCase.

      + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * reserved + * + * + * + * Specifies whether to check out the file as reserved or not + * + * + * + * Yes + * + * + * + * + * + * + * + * out + * + * + * + * Creates a writable file under a different filename + * + * + * + * No + * + * + * + * + * + * + * + * nodata + * + * + * + * Checks out the file but does not create an editable file + * containing its data + * + * + * + * No + * + * + * + * + * + * + * + * branch + * + * + * + * Specify a branch to check out the file to + * + * + * + * No + * + * + * + * + * + * + * + * version + * + * + * + * Allows checkout of a version other than main latest + * + * + * + * + * No + * + * + * + * + * + * + * + * nowarn + * + * + * + * Suppress warning messages + * + * + * + * No + * + * + * + * + * + * + * + * comment + * + * + * + * Specify a comment. Only one of comment or + * cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * commentfile + * + * + * + * Specify a file containing a comment. + * Only one of comment or cfile may be + * used. + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCCheckout extends ClearCase +{ + + /** + * -reserved flag -- check out the file as reserved + */ + public final static String FLAG_RESERVED = "-reserved"; + /** + * -reserved flag -- check out the file as unreserved + */ + public final static String FLAG_UNRESERVED = "-unreserved"; + /** + * -out flag -- create a writable file under a different filename + */ + public final static String FLAG_OUT = "-out"; + /** + * -ndata flag -- checks out the file but does not create an editable file + * containing its data + */ + public final static String FLAG_NODATA = "-ndata"; + /** + * -branch flag -- checks out the file on a specified branch + */ + public final static String FLAG_BRANCH = "-branch"; + /** + * -version flag -- allows checkout of a version that is not main latest + */ + public final static String FLAG_VERSION = "-version"; + /** + * -nwarn flag -- suppresses warning messages + */ + public final static String FLAG_NOWARN = "-nwarn"; + /** + * -c flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "-c"; + /** + * -cfile flag -- file containing a comment to attach to the file + */ + public final static String FLAG_COMMENTFILE = "-cfile"; + /** + * -nc flag -- no comment is specified + */ + public final static String FLAG_NOCOMMENT = "-nc"; + private boolean m_Reserved = true; + private String m_Out = null; + private boolean m_Ndata = false; + private String m_Branch = null; + private boolean m_Version = false; + private boolean m_Nwarn = false; + private String m_Comment = null; + private String m_Cfile = null; + + /** + * Set branch name + * + * @param branch the name of the branch + */ + public void setBranch( String branch ) + { + m_Branch = branch; + } + + /** + * Set comment string + * + * @param comment the comment string + */ + public void setComment( String comment ) + { + m_Comment = comment; + } + + /** + * Set comment file + * + * @param cfile the path to the comment file + */ + public void setCommentFile( String cfile ) + { + m_Cfile = cfile; + } + + /** + * Set the nodata flag + * + * @param ndata the status to set the flag to + */ + public void setNoData( boolean ndata ) + { + m_Ndata = ndata; + } + + /** + * Set the nowarn flag + * + * @param nwarn the status to set the flag to + */ + public void setNoWarn( boolean nwarn ) + { + m_Nwarn = nwarn; + } + + /** + * Set out file + * + * @param outf the path to the out file + */ + public void setOut( String outf ) + { + m_Out = outf; + } + + /** + * Set reserved flag status + * + * @param reserved the status to set the flag to + */ + public void setReserved( boolean reserved ) + { + m_Reserved = reserved; + } + + /** + * Set the version flag + * + * @param version the status to set the flag to + */ + public void setVersion( boolean version ) + { + m_Version = version; + } + + /** + * Get branch name + * + * @return String containing the name of the branch + */ + public String getBranch() + { + return m_Branch; + } + + /** + * Get comment string + * + * @return String containing the comment + */ + public String getComment() + { + return m_Comment; + } + + /** + * Get comment file + * + * @return String containing the path to the comment file + */ + public String getCommentFile() + { + return m_Cfile; + } + + /** + * Get nodata flag status + * + * @return boolean containing status of ndata flag + */ + public boolean getNoData() + { + return m_Ndata; + } + + /** + * Get nowarn flag status + * + * @return boolean containing status of nwarn flag + */ + public boolean getNoWarn() + { + return m_Nwarn; + } + + /** + * Get out file + * + * @return String containing the path to the out file + */ + public String getOut() + { + return m_Out; + } + + /** + * Get reserved flag status + * + * @return boolean containing status of reserved flag + */ + public boolean getReserved() + { + return m_Reserved; + } + + /** + * Get version flag status + * + * @return boolean containing status of version flag + */ + public boolean getVersion() + { + return m_Version; + } + + /** + * Executes the task.

      + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool checkout [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_CHECKOUT ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + /** + * Get the 'branch' command + * + * @param cmd Description of Parameter + */ + private void getBranchCommand( Commandline cmd ) + { + if( getBranch() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_BRANCH ); + cmd.createArgument().setValue( getBranch() ); + } + } + + + /** + * Get the 'comment' command + * + * @param cmd Description of Parameter + */ + private void getCommentCommand( Commandline cmd ) + { + if( getComment() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + } + + /** + * Get the 'cfile' command + * + * @param cmd Description of Parameter + */ + private void getCommentFileCommand( Commandline cmd ) + { + if( getCommentFile() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENTFILE ); + cmd.createArgument().setValue( getCommentFile() ); + } + } + + /** + * Get the 'out' command + * + * @param cmd Description of Parameter + */ + private void getOutCommand( Commandline cmd ) + { + if( getOut() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_OUT ); + cmd.createArgument().setValue( getOut() ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getReserved() ) + { + // -reserved + cmd.createArgument().setValue( FLAG_RESERVED ); + } + else + { + // -unreserved + cmd.createArgument().setValue( FLAG_UNRESERVED ); + } + + if( getOut() != null ) + { + // -out + getOutCommand( cmd ); + } + else + { + if( getNoData() ) + { + // -ndata + cmd.createArgument().setValue( FLAG_NODATA ); + } + + } + + if( getBranch() != null ) + { + // -branch + getBranchCommand( cmd ); + } + else + { + if( getVersion() ) + { + // -version + cmd.createArgument().setValue( FLAG_VERSION ); + } + + } + + if( getNoWarn() ) + { + // -nwarn + cmd.createArgument().setValue( FLAG_NOWARN ); + } + + if( getComment() != null ) + { + // -c + getCommentCommand( cmd ); + } + else + { + if( getCommentFile() != null ) + { + // -cfile + getCommentFileCommand( cmd ); + } + else + { + cmd.createArgument().setValue( FLAG_NOCOMMENT ); + } + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java new file mode 100644 index 000000000..02a442f2c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform UnCheckout command to ClearCase.

      + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * keepcopy + * + * + * + * Specifies whether to keep a copy of the file with a .keep extension + * or not + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCUnCheckout extends ClearCase +{ + + /** + * -keep flag -- keep a copy of the file with .keep extension + */ + public final static String FLAG_KEEPCOPY = "-keep"; + /** + * -rm flag -- remove the copy of the file + */ + public final static String FLAG_RM = "-rm"; + private boolean m_Keep = false; + + /** + * Set keepcopy flag status + * + * @param keep the status to set the flag to + */ + public void setKeepCopy( boolean keep ) + { + m_Keep = keep; + } + + /** + * Get keepcopy flag status + * + * @return boolean containing status of keep flag + */ + public boolean getKeepCopy() + { + return m_Keep; + } + + /** + * Executes the task.

      + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool uncheckout [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_UNCHECKOUT ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getKeepCopy() ) + { + // -keep + cmd.createArgument().setValue( FLAG_KEEPCOPY ); + } + else + { + // -rm + cmd.createArgument().setValue( FLAG_RM ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java new file mode 100644 index 000000000..2d88f71c4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform an Update command to ClearCase.

      + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * graphical + * + * + * + * Displays a graphical dialog during the update + * + * + * + * No + * + * + * + * + * + * + * + * log + * + * + * + * Specifies a log file for ClearCase to write to + * + * + * + * No + * + * + * + * + * + * + * + * overwrite + * + * + * + * Specifies whether to overwrite hijacked files or not + * + * + * + * No + * + * + * + * + * + * + * + * rename + * + * + * + * Specifies that hijacked files should be renamed with a + * .keep extension + * + * + * + * No + * + * + * + * + * + * + * + * currenttime + * + * + * + * Specifies that modification time should be written + * as the current time. Either currenttime or + * preservetime can be specified. + * + * + * + * No + * + * + * + * + * + * + * + * preservetime + * + * + * + * Specifies that modification time should + * preserved from the VOB time. Either currenttime + * or preservetime can be specified. + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCUpdate extends ClearCase +{ + + /** + * -graphical flag -- display graphical dialog during update operation + */ + public final static String FLAG_GRAPHICAL = "-graphical"; + /** + * -log flag -- file to log status to + */ + public final static String FLAG_LOG = "-log"; + /** + * -overwrite flag -- overwrite hijacked files + */ + public final static String FLAG_OVERWRITE = "-overwrite"; + /** + * -noverwrite flag -- do not overwrite hijacked files + */ + public final static String FLAG_NOVERWRITE = "-noverwrite"; + /** + * -rename flag -- rename hijacked files with .keep extension + */ + public final static String FLAG_RENAME = "-rename"; + /** + * -ctime flag -- modified time is written as the current time + */ + public final static String FLAG_CURRENTTIME = "-ctime"; + /** + * -ptime flag -- modified time is written as the VOB time + */ + public final static String FLAG_PRESERVETIME = "-ptime"; + private boolean m_Graphical = false; + private boolean m_Overwrite = false; + private boolean m_Rename = false; + private boolean m_Ctime = false; + private boolean m_Ptime = false; + private String m_Log = null; + + /** + * Set modified time based on current time + * + * @param ct the status to set the flag to + */ + public void setCurrentTime( boolean ct ) + { + m_Ctime = ct; + } + + /** + * Set graphical flag status + * + * @param graphical the status to set the flag to + */ + public void setGraphical( boolean graphical ) + { + m_Graphical = graphical; + } + + /** + * Set log file where cleartool can record the status of the command + * + * @param log the path to the log file + */ + public void setLog( String log ) + { + m_Log = log; + } + + /** + * Set overwrite hijacked files status + * + * @param ow the status to set the flag to + */ + public void setOverwrite( boolean ow ) + { + m_Overwrite = ow; + } + + /** + * Preserve modified time from the VOB time + * + * @param pt the status to set the flag to + */ + public void setPreserveTime( boolean pt ) + { + m_Ptime = pt; + } + + /** + * Set rename hijacked files status + * + * @param ren the status to set the flag to + */ + public void setRename( boolean ren ) + { + m_Rename = ren; + } + + /** + * Get current time status + * + * @return boolean containing status of current time flag + */ + public boolean getCurrentTime() + { + return m_Ctime; + } + + /** + * Get graphical flag status + * + * @return boolean containing status of graphical flag + */ + public boolean getGraphical() + { + return m_Graphical; + } + + /** + * Get log file + * + * @return String containing the path to the log file + */ + public String getLog() + { + return m_Log; + } + + /** + * Get overwrite hijacked files status + * + * @return boolean containing status of overwrite flag + */ + public boolean getOverwrite() + { + return m_Overwrite; + } + + /** + * Get preserve time status + * + * @return boolean containing status of preserve time flag + */ + public boolean getPreserveTime() + { + return m_Ptime; + } + + /** + * Get rename hijacked files status + * + * @return boolean containing status of rename flag + */ + public boolean getRename() + { + return m_Rename; + } + + /** + * Executes the task.

      + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool update [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_UPDATE ); + + // Check the command line options + checkOptions( commandLine ); + + // For debugging + System.out.println( commandLine.toString() ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + /** + * Get the 'log' command + * + * @param cmd Description of Parameter + */ + private void getLogCommand( Commandline cmd ) + { + if( getLog() == null ) + { + return; + } + else + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_LOG ); + cmd.createArgument().setValue( getLog() ); + } + } + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getGraphical() ) + { + // -graphical + cmd.createArgument().setValue( FLAG_GRAPHICAL ); + } + else + { + if( getOverwrite() ) + { + // -overwrite + cmd.createArgument().setValue( FLAG_OVERWRITE ); + } + else + { + if( getRename() ) + { + // -rename + cmd.createArgument().setValue( FLAG_RENAME ); + } + else + { + // -noverwrite + cmd.createArgument().setValue( FLAG_NOVERWRITE ); + } + } + + if( getCurrentTime() ) + { + // -ctime + cmd.createArgument().setValue( FLAG_CURRENTTIME ); + } + else + { + if( getPreserveTime() ) + { + // -ptime + cmd.createArgument().setValue( FLAG_PRESERVETIME ); + } + } + + // -log logname + getLogCommand( cmd ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java new file mode 100644 index 000000000..917152f39 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * A base class for creating tasks for executing commands on ClearCase.

      + * + * The class extends the 'exec' task as it operates by executing the cleartool + * program supplied with ClearCase. By default the task expects the cleartool + * executable to be in the path, * you can override this be specifying the + * cleartooldir attribute.

      + * + * This class provides set and get methods for the 'viewpath' attribute. It also + * contains constants for the flags that can be passed to cleartool.

      + * + * @author Curtis White + */ +public abstract class ClearCase extends Task +{ + + /** + * Constant for the thing to execute + */ + private final static String CLEARTOOL_EXE = "cleartool"; + + /** + * The 'Update' command + */ + public final static String COMMAND_UPDATE = "update"; + /** + * The 'Checkout' command + */ + public final static String COMMAND_CHECKOUT = "checkout"; + /** + * The 'Checkin' command + */ + public final static String COMMAND_CHECKIN = "checkin"; + /** + * The 'UndoCheckout' command + */ + public final static String COMMAND_UNCHECKOUT = "uncheckout"; + private String m_ClearToolDir = ""; + private String m_viewPath = null; + + /** + * Set the directory where the cleartool executable is located + * + * @param dir the directory containing the cleartool executable + */ + public final void setClearToolDir( String dir ) + { + m_ClearToolDir = project.translatePath( dir ); + } + + /** + * Set the path to the item in a clearcase view to operate on + * + * @param viewPath Path to the view directory or file + */ + public final void setViewPath( String viewPath ) + { + m_viewPath = viewPath; + } + + /** + * Get the path to the item in a clearcase view + * + * @return m_viewPath + */ + public String getViewPath() + { + return m_viewPath; + } + + /** + * Builds and returns the command string to execute cleartool + * + * @return String containing path to the executable + */ + protected final String getClearToolCommand() + { + String toReturn = m_ClearToolDir; + if( !toReturn.equals( "" ) && !toReturn.endsWith( "/" ) ) + { + toReturn += "/"; + } + + toReturn += CLEARTOOL_EXE; + + return toReturn; + } + + + protected int run( Commandline cmd ) + { + try + { + Project aProj = getProject(); + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ) ); + exe.setAntRun( aProj ); + exe.setWorkingDirectory( aProj.getBaseDir() ); + exe.setCommandline( cmd.getCommandline() ); + return exe.execute(); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java new file mode 100644 index 000000000..f0479ae8c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ClassCPInfo; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPool; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPoolEntry; + +/** + * A ClassFile object stores information about a Java class. The class may be + * read from a DataInputStream.and written to a DataOutputStream. These are + * usually streams from a Java class file or a class file component of a Jar + * file. + * + * @author Conor MacNeill + */ +public class ClassFile +{ + + /** + * The Magic Value that marks the start of a Java class file + */ + private final static int CLASS_MAGIC = 0xCAFEBABE; + + /** + * The class name for this class. + */ + private String className; + + /** + * This class' constant pool. + */ + private ConstantPool constantPool; + + + /** + * Get the classes which this class references. + * + * @return The ClassRefs value + */ + public Vector getClassRefs() + { + + Vector classRefs = new Vector(); + + for( int i = 0; i < constantPool.size(); ++i ) + { + ConstantPoolEntry entry = constantPool.getEntry( i ); + + if( entry != null && entry.getTag() == ConstantPoolEntry.CONSTANT_Class ) + { + ClassCPInfo classEntry = ( ClassCPInfo )entry; + + if( !classEntry.getClassName().equals( className ) ) + { + classRefs.addElement( ClassFileUtils.convertSlashName( classEntry.getClassName() ) ); + } + } + } + + return classRefs; + } + + /** + * Get the class' fully qualified name in dot format. + * + * @return the class name in dot format (eg. java.lang.Object) + */ + public String getFullClassName() + { + return ClassFileUtils.convertSlashName( className ); + } + + /** + * Read the class from a data stream. This method takes an InputStream as + * input and parses the class from the stream.

      + * + * + * + * @param stream an InputStream from which the class will be read + * @throws IOException if there is a problem reading from the given stream. + * @throws ClassFormatError if the class cannot be parsed correctly + */ + public void read( InputStream stream ) + throws IOException, ClassFormatError + { + DataInputStream classStream = new DataInputStream( stream ); + + if( classStream.readInt() != CLASS_MAGIC ) + { + throw new ClassFormatError( "No Magic Code Found - probably not a Java class file." ); + } + + // right we have a good looking class file. + int minorVersion = classStream.readUnsignedShort(); + int majorVersion = classStream.readUnsignedShort(); + + // read the constant pool in and resolve it + constantPool = new ConstantPool(); + + constantPool.read( classStream ); + constantPool.resolve(); + + int accessFlags = classStream.readUnsignedShort(); + int thisClassIndex = classStream.readUnsignedShort(); + int superClassIndex = classStream.readUnsignedShort(); + className = ( ( ClassCPInfo )constantPool.getEntry( thisClassIndex ) ).getClassName(); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java new file mode 100644 index 000000000..de4ad9499 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; + + +public interface ClassFileIterator +{ + + ClassFile getNextClassFile(); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java new file mode 100644 index 000000000..054ef68f8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; + +/** + * Utility class file routines. This class porovides a number of static utility + * methods to convert between the formats used in the Java class file format and + * those commonly used in Java programming. + * + * @author Conor MacNeill + */ +public class ClassFileUtils +{ + + /** + * Convert a class name from java source file dot notation to class file + * slash notation.. + * + * @param dotName the class name in dot notation (eg. java.lang.Object). + * @return the class name in slash notation (eg. java/lang/Object). + */ + public static String convertDotName( String dotName ) + { + return dotName.replace( '.', '/' ); + } + + /** + * Convert a class name from class file slash notation to java source file + * dot notation. + * + * @param name Description of Parameter + * @return the class name in dot notation (eg. java.lang.Object). + */ + public static String convertSlashName( String name ) + { + return name.replace( '\\', '.' ).replace( '/', '.' ); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/Depend.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/Depend.java new file mode 100644 index 000000000..62ec1812e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/Depend.java @@ -0,0 +1,764 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + + +/** + * Generate a dependency file for a given set of classes + * + * @author Conor MacNeill + */ +public class Depend extends MatchingTask +{ + + /** + * constants used with the cache file + */ + private final static String CACHE_FILE_NAME = "dependencies.txt"; + private final static String CLASSNAME_PREPEND = "||:"; + + /** + * indicates that the dependency relationships should be extended beyond + * direct dependencies to include all classes. So if A directly affects B + * abd B directly affects C, then A indirectly affects C. + */ + private boolean closure = false; + + /** + * Flag which controls whether the reversed dependencies should be dumped to + * the log + */ + private boolean dump = false; + + /** + * A map which gives for every class a list of te class which it affects. + */ + private Hashtable affectedClassMap; + + /** + * The directory which contains the dependency cache. + */ + private File cache; + + /** + * A map which gives information about a class + */ + private Hashtable classFileInfoMap; + + /** + * A map which gives the list of jars and classes from the classpath that a + * class depends upon + */ + private Hashtable classpathDependencies; + + /** + * The classpath to look for additional dependencies + */ + private Path dependClasspath; + + /** + * The path where compiled class files exist. + */ + private Path destPath; + + /** + * The list of classes which are out of date. + */ + private Hashtable outOfDateClasses; + + /** + * The path where source files exist + */ + private Path srcPath; + + public void setCache( File cache ) + { + this.cache = cache; + } + + /** + * Set the classpath to be used for this dependency check. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( dependClasspath == null ) + { + dependClasspath = classpath; + } + else + { + dependClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setClosure( boolean closure ) + { + this.closure = closure; + } + + /** + * Set the destination directory where the compiled java files exist. + * + * @param destPath The new DestDir value + */ + public void setDestDir( Path destPath ) + { + this.destPath = destPath; + } + + /** + * Flag to indicate whether the reverse dependency list should be dumped to + * debug + * + * @param dump The new Dump value + */ + public void setDump( boolean dump ) + { + this.dump = dump; + } + + + /** + * Set the source dirs to find the source Java files. + * + * @param srcPath The new Srcdir value + */ + public void setSrcdir( Path srcPath ) + { + this.srcPath = srcPath; + } + + /** + * Gets the classpath to be used for this dependency check. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return dependClasspath; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( dependClasspath == null ) + { + dependClasspath = new Path( project ); + } + return dependClasspath.createPath(); + } + + /** + * Does the work. + * + * @exception BuildException Thrown in unrecovrable error. + */ + public void execute() + throws BuildException + { + try + { + long start = System.currentTimeMillis(); + String[] srcPathList = srcPath.list(); + if( srcPathList.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + + if( destPath == null ) + { + destPath = srcPath; + } + + if( cache != null && cache.exists() && !cache.isDirectory() ) + { + throw new BuildException( "The cache, if specified, must point to a directory" ); + } + + if( cache != null && !cache.exists() ) + { + cache.mkdirs(); + } + + determineDependencies(); + + if( dump ) + { + log( "Reverse Dependency Dump for " + affectedClassMap.size() + + " classes:", Project.MSG_DEBUG ); + for( Enumeration e = affectedClassMap.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + log( " Class " + className + " affects:", Project.MSG_DEBUG ); + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( className ); + for( Enumeration e2 = affectedClasses.keys(); e2.hasMoreElements(); ) + { + String affectedClass = ( String )e2.nextElement(); + ClassFileInfo info = ( ClassFileInfo )affectedClasses.get( affectedClass ); + log( " " + affectedClass + " in " + info.absoluteFile.getPath(), Project.MSG_DEBUG ); + } + } + + if( classpathDependencies != null ) + { + log( "Classpath file dependencies (Forward):", Project.MSG_DEBUG ); + for( Enumeration e = classpathDependencies.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + log( " Class " + className + " depends on:", Project.MSG_DEBUG ); + Hashtable dependencies = ( Hashtable )classpathDependencies.get( className ); + for( Enumeration e2 = dependencies.elements(); e2.hasMoreElements(); ) + { + File classpathFile = ( File )e2.nextElement(); + log( " " + classpathFile.getPath(), Project.MSG_DEBUG ); + } + } + } + + } + + // we now need to scan for out of date files. When we have the list + // we go through and delete all class files which are affected by these files. + outOfDateClasses = new Hashtable(); + for( int i = 0; i < srcPathList.length; i++ ) + { + File srcDir = ( File )project.resolveFile( srcPathList[i] ); + if( srcDir.exists() ) + { + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + scanDir( srcDir, files ); + } + } + + // now check classpath file dependencies + if( classpathDependencies != null ) + { + for( Enumeration e = classpathDependencies.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + if( !outOfDateClasses.containsKey( className ) ) + { + ClassFileInfo info = ( ClassFileInfo )classFileInfoMap.get( className ); + + // if we have no info about the class - it may have been deleted already and we + // are using cached info. + if( info != null ) + { + Hashtable dependencies = ( Hashtable )classpathDependencies.get( className ); + for( Enumeration e2 = dependencies.elements(); e2.hasMoreElements(); ) + { + File classpathFile = ( File )e2.nextElement(); + if( classpathFile.lastModified() > info.absoluteFile.lastModified() ) + { + log( "Class " + className + + " is out of date with respect to " + classpathFile, Project.MSG_DEBUG ); + outOfDateClasses.put( className, className ); + break; + } + } + } + } + } + } + + // we now have a complete list of classes which are out of date + // We scan through the affected classes, deleting any affected classes. + int count = deleteAllAffectedFiles(); + + long duration = ( System.currentTimeMillis() - start ) / 1000; + log( "Deleted " + count + " out of date files in " + duration + " seconds" ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + /** + * Scans the directory looking for source files that are newer than their + * class files. The results are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, String files[] ) + { + + long now = System.currentTimeMillis(); + + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".java" ) ) + { + String filePath = srcFile.getPath(); + String className = filePath.substring( srcDir.getPath().length() + 1, + filePath.length() - ".java".length() ); + className = ClassFileUtils.convertSlashName( className ); + ClassFileInfo info = ( ClassFileInfo )classFileInfoMap.get( className ); + if( info == null ) + { + // there was no class file. add this class to the list + outOfDateClasses.put( className, className ); + } + else + { + if( srcFile.lastModified() > info.absoluteFile.lastModified() ) + { + outOfDateClasses.put( className, className ); + } + } + } + } + } + + + /** + * Get the list of class files we are going to analyse. + * + * @param classLocations a path structure containing all the directories + * where classes can be found. + * @return a vector containing the classes to analyse. + */ + private Vector getClassFiles( Path classLocations ) + { + // break the classLocations into its components. + String[] classLocationsList = classLocations.list(); + + Vector classFileList = new Vector(); + + for( int i = 0; i < classLocationsList.length; ++i ) + { + File dir = new File( classLocationsList[i] ); + if( dir.isDirectory() ) + { + addClassFiles( classFileList, dir, dir ); + } + } + + return classFileList; + } + + /** + * Add the list of class files from the given directory to the class file + * vector, including any subdirectories. + * + * @param classFileList The feature to be added to the ClassFiles attribute + * @param dir The feature to be added to the ClassFiles attribute + * @param root The feature to be added to the ClassFiles attribute + */ + private void addClassFiles( Vector classFileList, File dir, File root ) + { + String[] filesInDir = dir.list(); + + if( filesInDir != null ) + { + int length = filesInDir.length; + + for( int i = 0; i < length; ++i ) + { + File file = new File( dir, filesInDir[i] ); + if( file.isDirectory() ) + { + addClassFiles( classFileList, file, root ); + } + else if( file.getName().endsWith( ".class" ) ) + { + ClassFileInfo info = new ClassFileInfo(); + info.absoluteFile = file; + info.relativeName = file.getPath().substring( root.getPath().length() + 1, + file.getPath().length() - 6 ); + info.className = ClassFileUtils.convertSlashName( info.relativeName ); + classFileList.addElement( info ); + } + } + } + } + + private int deleteAffectedFiles( String className ) + { + int count = 0; + + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( className ); + if( affectedClasses != null ) + { + for( Enumeration e = affectedClasses.keys(); e.hasMoreElements(); ) + { + String affectedClassName = ( String )e.nextElement(); + ClassFileInfo affectedClassInfo = ( ClassFileInfo )affectedClasses.get( affectedClassName ); + if( affectedClassInfo.absoluteFile.exists() ) + { + log( "Deleting file " + affectedClassInfo.absoluteFile.getPath() + " since " + + className + " out of date", Project.MSG_VERBOSE ); + affectedClassInfo.absoluteFile.delete(); + count++; + if( closure ) + { + count += deleteAffectedFiles( affectedClassName ); + } + else + { + // without closure we may delete an inner class but not the + // top level class which would not trigger a recompile. + + if( affectedClassName.indexOf( "$" ) != -1 ) + { + // need to delete the main class + String topLevelClassName + = affectedClassName.substring( 0, affectedClassName.indexOf( "$" ) ); + log( "Top level class = " + topLevelClassName, Project.MSG_VERBOSE ); + ClassFileInfo topLevelClassInfo + = ( ClassFileInfo )classFileInfoMap.get( topLevelClassName ); + if( topLevelClassInfo != null && + topLevelClassInfo.absoluteFile.exists() ) + { + log( "Deleting file " + topLevelClassInfo.absoluteFile.getPath() + " since " + + "one of its inner classes was removed", Project.MSG_VERBOSE ); + topLevelClassInfo.absoluteFile.delete(); + count++; + if( closure ) + { + count += deleteAffectedFiles( topLevelClassName ); + } + } + } + } + } + } + } + return count; + } + + private int deleteAllAffectedFiles() + { + int count = 0; + for( Enumeration e = outOfDateClasses.elements(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + count += deleteAffectedFiles( className ); + ClassFileInfo classInfo = ( ClassFileInfo )classFileInfoMap.get( className ); + if( classInfo != null && classInfo.absoluteFile.exists() ) + { + classInfo.absoluteFile.delete(); + count++; + } + } + return count; + } + + + /** + * Determine the dependencies between classes. Class dependencies are + * determined by examining the class references in a class file to other + * classes + * + * @exception IOException Description of Exception + */ + private void determineDependencies() + throws IOException + { + affectedClassMap = new Hashtable(); + classFileInfoMap = new Hashtable(); + boolean cacheDirty = false; + + Hashtable dependencyMap = new Hashtable(); + File depCacheFile = null; + boolean depCacheFileExists = true; + long depCacheFileLastModified = Long.MAX_VALUE; + + // read the dependency cache from the disk + if( cache != null ) + { + dependencyMap = readCachedDependencies(); + depCacheFile = new File( cache, CACHE_FILE_NAME ); + depCacheFileExists = depCacheFile.exists(); + depCacheFileLastModified = depCacheFile.lastModified(); + } + for( Enumeration e = getClassFiles( destPath ).elements(); e.hasMoreElements(); ) + { + ClassFileInfo info = ( ClassFileInfo )e.nextElement(); + log( "Adding class info for " + info.className, Project.MSG_DEBUG ); + classFileInfoMap.put( info.className, info ); + + Vector dependencyList = null; + + if( cache != null ) + { + // try to read the dependency info from the map if it is not out of date + if( depCacheFileExists && depCacheFileLastModified > info.absoluteFile.lastModified() ) + { + // depFile exists and is newer than the class file + // need to get dependency list from the map. + dependencyList = ( Vector )dependencyMap.get( info.className ); + } + } + + if( dependencyList == null ) + { + // not cached - so need to read directly from the class file + FileInputStream inFileStream = null; + try + { + inFileStream = new FileInputStream( info.absoluteFile ); + ClassFile classFile = new ClassFile(); + classFile.read( inFileStream ); + + dependencyList = classFile.getClassRefs(); + if( dependencyList != null ) + { + cacheDirty = true; + dependencyMap.put( info.className, dependencyList ); + } + + } + finally + { + if( inFileStream != null ) + { + inFileStream.close(); + } + } + } + + // This class depends on each class in the dependency list. For each + // one of those, add this class into their affected classes list + for( Enumeration depEnum = dependencyList.elements(); depEnum.hasMoreElements(); ) + { + String dependentClass = ( String )depEnum.nextElement(); + + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( dependentClass ); + if( affectedClasses == null ) + { + affectedClasses = new Hashtable(); + affectedClassMap.put( dependentClass, affectedClasses ); + } + + affectedClasses.put( info.className, info ); + } + } + + classpathDependencies = null; + if( dependClasspath != null ) + { + // now determine which jars each class depends upon + classpathDependencies = new Hashtable(); + AntClassLoader loader = new AntClassLoader( getProject(), dependClasspath ); + + Hashtable classpathFileCache = new Hashtable(); + Object nullFileMarker = new Object(); + for( Enumeration e = dependencyMap.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + Vector dependencyList = ( Vector )dependencyMap.get( className ); + Hashtable dependencies = new Hashtable(); + classpathDependencies.put( className, dependencies ); + for( Enumeration e2 = dependencyList.elements(); e2.hasMoreElements(); ) + { + String dependency = ( String )e2.nextElement(); + Object classpathFileObject = classpathFileCache.get( dependency ); + if( classpathFileObject == null ) + { + classpathFileObject = nullFileMarker; + + if( !dependency.startsWith( "java." ) && !dependency.startsWith( "javax." ) ) + { + URL classURL = loader.getResource( dependency.replace( '.', '/' ) + ".class" ); + if( classURL != null ) + { + if( classURL.getProtocol().equals( "jar" ) ) + { + String jarFilePath = classURL.getFile(); + if( jarFilePath.startsWith( "file:" ) ) + { + int classMarker = jarFilePath.indexOf( '!' ); + jarFilePath = jarFilePath.substring( 5, classMarker ); + } + classpathFileObject = new File( jarFilePath ); + } + else if( classURL.getProtocol().equals( "file" ) ) + { + String classFilePath = classURL.getFile(); + classpathFileObject = new File( classFilePath ); + } + log( "Class " + className + + " depends on " + classpathFileObject + + " due to " + dependency, Project.MSG_DEBUG ); + } + } + classpathFileCache.put( dependency, classpathFileObject ); + } + if( classpathFileObject != null && classpathFileObject != nullFileMarker ) + { + // we need to add this jar to the list for this class. + File jarFile = ( File )classpathFileObject; + dependencies.put( jarFile, jarFile ); + } + } + } + } + + // write the dependency cache to the disk + if( cache != null && cacheDirty ) + { + writeCachedDependencies( dependencyMap ); + } + } + + /** + * Read the dependencies from cache file + * + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + private Hashtable readCachedDependencies() + throws IOException + { + Hashtable dependencyMap = new Hashtable(); + + if( cache != null ) + { + File depFile = new File( cache, CACHE_FILE_NAME ); + BufferedReader in = null; + if( depFile.exists() ) + { + try + { + in = new BufferedReader( new FileReader( depFile ) ); + String line = null; + Vector dependencyList = null; + String className = null; + int prependLength = CLASSNAME_PREPEND.length(); + while( ( line = in.readLine() ) != null ) + { + if( line.startsWith( CLASSNAME_PREPEND ) ) + { + dependencyList = new Vector(); + className = line.substring( prependLength ); + dependencyMap.put( className, dependencyList ); + } + else + { + dependencyList.addElement( line ); + } + } + } + finally + { + if( in != null ) + { + in.close(); + } + } + } + } + + return dependencyMap; + } + + /** + * Write the dependencies to cache file + * + * @param dependencyMap Description of Parameter + * @exception IOException Description of Exception + */ + private void writeCachedDependencies( Hashtable dependencyMap ) + throws IOException + { + if( cache != null ) + { + PrintWriter pw = null; + try + { + cache.mkdirs(); + File depFile = new File( cache, CACHE_FILE_NAME ); + + pw = new PrintWriter( new FileWriter( depFile ) ); + for( Enumeration deps = dependencyMap.keys(); deps.hasMoreElements(); ) + { + String className = ( String )deps.nextElement(); + + pw.println( CLASSNAME_PREPEND + className ); + + Vector dependencyList = ( Vector )dependencyMap.get( className ); + int size = dependencyList.size(); + for( int x = 0; x < size; x++ ) + { + pw.println( dependencyList.elementAt( x ) ); + } + } + } + finally + { + if( pw != null ) + { + pw.close(); + } + } + } + } + + /** + * A class (struct) user to manage information about a class + * + * @author RT + */ + private static class ClassFileInfo + { + /** + * The file where the class file is stored in the file system + */ + public File absoluteFile; + + /** + * The Java class name of this class + */ + public String className; + + /** + * The location of the file relative to its base directory - the root of + * the package namespace + */ + public String relativeName; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java new file mode 100644 index 000000000..e003411d4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Stack; +import java.util.Vector; + +/** + * An iterator which iterates through the contents of a java directory. The + * iterator should be created with the directory at the root of the Java + * namespace. + * + * @author Conor MacNeill + */ +public class DirectoryIterator implements ClassFileIterator +{ + + /** + * The length of the root directory. This is used to remove the root + * directory from full paths. + */ + int rootLength; + + /** + * The current directory iterator. As directories encounter lower level + * directories, the current iterator is pushed onto the iterator stack and a + * new iterator over the sub directory becomes the current directory. This + * implements a depth first traversal of the directory namespace. + */ + private Enumeration currentEnum; + + /** + * This is a stack of current iterators supporting the depth first traversal + * of the directory tree. + */ + private Stack enumStack; + + /** + * Creates a directory iterator. The directory iterator is created to scan + * the root directory. If the changeInto flag is given, then the entries + * returned will be relative to this directory and not the current + * directory. + * + * @param rootDirectory the root if the directory namespace which is to be + * iterated over + * @param changeInto if true then the returned entries will be relative to + * the rootDirectory and not the current directory. + * @exception IOException Description of Exception + * @throws IOException if there is a problem reading the directory + * information. + */ + public DirectoryIterator( File rootDirectory, boolean changeInto ) + throws IOException + { + super(); + + enumStack = new Stack(); + + if( rootDirectory.isAbsolute() || changeInto ) + { + rootLength = rootDirectory.getPath().length() + 1; + } + else + { + rootLength = 0; + } + + Vector filesInRoot = getDirectoryEntries( rootDirectory ); + + currentEnum = filesInRoot.elements(); + } + + /** + * Template method to allow subclasses to supply elements for the iteration. + * The directory iterator maintains a stack of iterators covering each level + * in the directory hierarchy. The current iterator covers the current + * directory being scanned. If the next entry in that directory is a + * subdirectory, the current iterator is pushed onto the stack and a new + * iterator is created for the subdirectory. If the entry is a file, it is + * returned as the next element and the iterator remains valid. If there are + * no more entries in the current directory, the topmost iterator on the + * statck is popped off to become the current iterator. + * + * @return the next ClassFile in the iteration. + */ + public ClassFile getNextClassFile() + { + ClassFile nextElement = null; + + try + { + while( nextElement == null ) + { + if( currentEnum.hasMoreElements() ) + { + File element = ( File )currentEnum.nextElement(); + + if( element.isDirectory() ) + { + + // push the current iterator onto the stack and then + // iterate through this directory. + enumStack.push( currentEnum ); + + Vector files = getDirectoryEntries( element ); + + currentEnum = files.elements(); + } + else + { + + // we have a file. create a stream for it + FileInputStream inFileStream = new FileInputStream( element ); + + if( element.getName().endsWith( ".class" ) ) + { + + // create a data input stream from the jar input stream + ClassFile javaClass = new ClassFile(); + + javaClass.read( inFileStream ); + + nextElement = javaClass; + } + } + } + else + { + // this iterator is exhausted. Can we pop one off the stack + if( enumStack.empty() ) + { + break; + } + else + { + currentEnum = ( Enumeration )enumStack.pop(); + } + } + } + } + catch( IOException e ) + { + nextElement = null; + } + + return nextElement; + } + + /** + * Get a vector covering all the entries (files and subdirectories in a + * directory). + * + * @param directory the directory to be scanned. + * @return a vector containing File objects for each entry in the directory. + */ + private Vector getDirectoryEntries( File directory ) + { + Vector files = new Vector(); + + // File[] filesInDir = directory.listFiles(); + String[] filesInDir = directory.list(); + + if( filesInDir != null ) + { + int length = filesInDir.length; + + for( int i = 0; i < length; ++i ) + { + files.addElement( new File( directory, filesInDir[i] ) ); + } + } + + return files; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java new file mode 100644 index 000000000..1715255bf --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * A class file iterator which iterates through the contents of a Java jar file. + * + * @author Conor MacNeill + */ +public class JarFileIterator implements ClassFileIterator +{ + private ZipInputStream jarStream; + + public JarFileIterator( InputStream stream ) + throws IOException + { + super(); + + jarStream = new ZipInputStream( stream ); + } + + public ClassFile getNextClassFile() + { + ZipEntry jarEntry; + ClassFile nextElement = null; + + try + { + jarEntry = jarStream.getNextEntry(); + + while( nextElement == null && jarEntry != null ) + { + String entryName = jarEntry.getName(); + + if( !jarEntry.isDirectory() && entryName.endsWith( ".class" ) ) + { + + // create a data input stream from the jar input stream + ClassFile javaClass = new ClassFile(); + + javaClass.read( jarStream ); + + nextElement = javaClass; + } + else + { + + jarEntry = jarStream.getNextEntry(); + } + } + } + catch( IOException e ) + { + String message = e.getMessage(); + String text = e.getClass().getName(); + + if( message != null ) + { + text += ": " + message; + } + + throw new RuntimeException( "Problem reading JAR file: " + text ); + } + + return nextElement; + } + + private byte[] getEntryBytes( InputStream stream ) + throws IOException + { + byte[] buffer = new byte[8192]; + ByteArrayOutputStream baos = new ByteArrayOutputStream( 2048 ); + int n; + + while( ( n = stream.read( buffer, 0, buffer.length ) ) != -1 ) + { + baos.write( buffer, 0, n ); + } + + return baos.toByteArray(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java new file mode 100644 index 000000000..919486a22 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * The constant pool entry which stores class information. + * + * @author Conor MacNeill + */ +public class ClassCPInfo extends ConstantPoolEntry +{ + + /** + * The class' name. This will be only valid if the entry has been resolved + * against the constant pool. + */ + private String className; + + /** + * The index into the constant pool where this class' name is stored. If the + * class name is changed, this entry is invalid until this entry is + * connected to a constant pool. + */ + private int index; + + /** + * Constructor. Sets the tag value for this entry to type Class + */ + public ClassCPInfo() + { + super( CONSTANT_Class, 1 ); + } + + /** + * Get the class name of this entry. + * + * @return the class' name. + */ + public String getClassName() + { + return className; + } + + /** + * Read the entry from a stream. + * + * @param cpStream the stream containing the constant pool entry to be read. + * @exception IOException thrown if there is a problem reading the entry + * from the stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + index = cpStream.readUnsignedShort(); + className = "unresolved"; + } + + /** + * Resolve this class info against the given constant pool. + * + * @param constantPool the constant pool with which to resolve the class. + */ + public void resolve( ConstantPool constantPool ) + { + className = ( ( Utf8CPInfo )constantPool.getEntry( index ) ).getValue(); + + super.resolve( constantPool ); + } + + /** + * Generate a string readable version of this entry + * + * @return Description of the Returned Value + */ + public String toString() + { + return "Class Constant Pool Entry for " + className + "[" + index + "]"; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java new file mode 100644 index 000000000..f8ba55828 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; + + +/** + * A Constant Pool entry which represents a constant value. + * + * @author Conor MacNeill + */ +public abstract class ConstantCPInfo extends ConstantPoolEntry +{ + + /** + * The entry's untyped value. Each subclass interprets the constant value + * based on the subclass's type. The value here must be compatible. + */ + private Object value; + + /** + * Initialise the constant entry. + * + * @param tagValue the constant pool entry type to be used. + * @param entries the number of constant pool entry slots occupied by this + * entry. + */ + protected ConstantCPInfo( int tagValue, int entries ) + { + super( tagValue, entries ); + } + + /** + * Set the constant value. + * + * @param newValue the new untyped value of this constant. + */ + public void setValue( Object newValue ) + { + value = newValue; + } + + /** + * Get the value of the constant. + * + * @return the value of the constant (untyped). + */ + public Object getValue() + { + return value; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java new file mode 100644 index 000000000..ee3131ec3 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +/** + * The constant pool of a Java class. The constant pool is a collection of + * constants used in a Java class file. It stores strings, constant values, + * class names, method names, field names etc. + * + * @author Conor MacNeill + * @see The Java Virtual + * Machine Specification + */ +public class ConstantPool +{ + + /** + * The entries in the constant pool. + */ + private Vector entries; + + /** + * A Hashtable of UTF8 entries - used to get constant pool indexes of the + * UTF8 values quickly + */ + private Hashtable utf8Indexes; + + /** + * Initialise the constant pool. + */ + public ConstantPool() + { + entries = new Vector(); + + // The zero index is never present in the constant pool itself so + // we add a null entry for it + entries.addElement( null ); + + utf8Indexes = new Hashtable(); + } + + /** + * Get the index of a given CONSTANT_Class entry in the constant pool. + * + * @param className the name of the class for which the class entry index is + * required. + * @return the index at which the given class entry occurs in the constant + * pool or -1 if the value does not occur. + */ + public int getClassEntry( String className ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof ClassCPInfo ) + { + ClassCPInfo classinfo = ( ClassCPInfo )element; + + if( classinfo.getClassName().equals( className ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given constant value entry in the constant pool. + * + * @param constantValue the constant value for which the index is required. + * @return the index at which the given value entry occurs in the constant + * pool or -1 if the value does not occur. + */ + public int getConstantEntry( Object constantValue ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof ConstantCPInfo ) + { + ConstantCPInfo constantEntry = ( ConstantCPInfo )element; + + if( constantEntry.getValue().equals( constantValue ) ) + { + index = i; + } + } + } + + return index; + } + + + /** + * Get an constant pool entry at a particular index. + * + * @param index the index into the constant pool. + * @return the constant pool entry at that index. + */ + public ConstantPoolEntry getEntry( int index ) + { + return ( ConstantPoolEntry )entries.elementAt( index ); + } + + /** + * Get the index of a given CONSTANT_FieldRef entry in the constant pool. + * + * @param fieldClassName the name of the class which contains the field + * being referenced. + * @param fieldName the name of the field being referenced. + * @param fieldType the type descriptor of the field being referenced. + * @return the index at which the given field ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getFieldRefEntry( String fieldClassName, String fieldName, String fieldType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof FieldRefCPInfo ) + { + FieldRefCPInfo fieldRefEntry = ( FieldRefCPInfo )element; + + if( fieldRefEntry.getFieldClassName().equals( fieldClassName ) && fieldRefEntry.getFieldName().equals( fieldName ) + && fieldRefEntry.getFieldType().equals( fieldType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_InterfaceMethodRef entry in the + * constant pool. + * + * @param interfaceMethodClassName the name of the interface which contains + * the method being referenced. + * @param interfaceMethodName the name of the method being referenced. + * @param interfaceMethodType the type descriptor of the metho dbeing + * referenced. + * @return the index at which the given method ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getInterfaceMethodRefEntry( String interfaceMethodClassName, String interfaceMethodName, String interfaceMethodType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof InterfaceMethodRefCPInfo ) + { + InterfaceMethodRefCPInfo interfaceMethodRefEntry = ( InterfaceMethodRefCPInfo )element; + + if( interfaceMethodRefEntry.getInterfaceMethodClassName().equals( interfaceMethodClassName ) + && interfaceMethodRefEntry.getInterfaceMethodName().equals( interfaceMethodName ) + && interfaceMethodRefEntry.getInterfaceMethodType().equals( interfaceMethodType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_MethodRef entry in the constant pool. + * + * @param methodClassName the name of the class which contains the method + * being referenced. + * @param methodName the name of the method being referenced. + * @param methodType the type descriptor of the metho dbeing referenced. + * @return the index at which the given method ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getMethodRefEntry( String methodClassName, String methodName, String methodType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof MethodRefCPInfo ) + { + MethodRefCPInfo methodRefEntry = ( MethodRefCPInfo )element; + + if( methodRefEntry.getMethodClassName().equals( methodClassName ) + && methodRefEntry.getMethodName().equals( methodName ) && methodRefEntry.getMethodType().equals( methodType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_NameAndType entry in the constant pool. + * + * @param name the name + * @param type the type + * @return the index at which the given NameAndType entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getNameAndTypeEntry( String name, String type ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof NameAndTypeCPInfo ) + { + NameAndTypeCPInfo nameAndTypeEntry = ( NameAndTypeCPInfo )element; + + if( nameAndTypeEntry.getName().equals( name ) && nameAndTypeEntry.getType().equals( type ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given UTF8 constant pool entry. + * + * @param value the string value of the UTF8 entry. + * @return the index at which the given string occurs in the constant pool + * or -1 if the value does not occur. + */ + public int getUTF8Entry( String value ) + { + int index = -1; + Integer indexInteger = ( Integer )utf8Indexes.get( value ); + + if( indexInteger != null ) + { + index = indexInteger.intValue(); + } + + return index; + } + + /** + * Add an entry to the constant pool. + * + * @param entry the new entry to be added to the constant pool. + * @return the index into the constant pool at which the entry is stored. + */ + public int addEntry( ConstantPoolEntry entry ) + { + int index = entries.size(); + + entries.addElement( entry ); + + int numSlots = entry.getNumEntries(); + + // add null entries for any additional slots required. + for( int j = 0; j < numSlots - 1; ++j ) + { + entries.addElement( null ); + } + + if( entry instanceof Utf8CPInfo ) + { + Utf8CPInfo utf8Info = ( Utf8CPInfo )entry; + + utf8Indexes.put( utf8Info.getValue(), new Integer( index ) ); + } + + return index; + } + + /** + * Read the constant pool from a class input stream. + * + * @param classStream the DataInputStream of a class file. + * @throws IOException if there is a problem reading the constant pool from + * the stream + */ + public void read( DataInputStream classStream ) + throws IOException + { + int numEntries = classStream.readUnsignedShort(); + + for( int i = 1; i < numEntries; ) + { + ConstantPoolEntry nextEntry = ConstantPoolEntry.readEntry( classStream ); + + i += nextEntry.getNumEntries(); + + addEntry( nextEntry ); + } + } + + /** + * Resolve the entries in the constant pool. Resolution of the constant pool + * involves transforming indexes to other constant pool entries into the + * actual data for that entry. + */ + public void resolve() + { + for( Enumeration i = entries.elements(); i.hasMoreElements(); ) + { + ConstantPoolEntry poolInfo = ( ConstantPoolEntry )i.nextElement(); + + if( poolInfo != null && !poolInfo.isResolved() ) + { + poolInfo.resolve( this ); + } + } + } + + /** + * Get the size of the constant pool. + * + * @return Description of the Returned Value + */ + public int size() + { + return entries.size(); + } + + /** + * Dump the constant pool to a string. + * + * @return the constant pool entries as strings + */ + public String toString() + { + StringBuffer sb = new StringBuffer( "\n" ); + int size = entries.size(); + + for( int i = 0; i < size; ++i ) + { + sb.append( "[" + i + "] = " + getEntry( i ) + "\n" ); + } + + return sb.toString(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java new file mode 100644 index 000000000..7e1a89c31 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * An entry in the constant pool. This class contains a represenation of the + * constant pool entries. It is an abstract base class for all the different + * forms of constant pool entry. + * + * @author Conor MacNeill + * @see ConstantPool + */ +public abstract class ConstantPoolEntry +{ + + /** + * Tag value for UTF8 entries. + */ + public final static int CONSTANT_Utf8 = 1; + + /** + * Tag value for Integer entries. + */ + public final static int CONSTANT_Integer = 3; + + /** + * Tag value for Float entries. + */ + public final static int CONSTANT_Float = 4; + + /** + * Tag value for Long entries. + */ + public final static int CONSTANT_Long = 5; + + /** + * Tag value for Double entries. + */ + public final static int CONSTANT_Double = 6; + + /** + * Tag value for Class entries. + */ + public final static int CONSTANT_Class = 7; + + /** + * Tag value for String entries. + */ + public final static int CONSTANT_String = 8; + + /** + * Tag value for Field Reference entries. + */ + public final static int CONSTANT_FieldRef = 9; + + /** + * Tag value for Method Reference entries. + */ + public final static int CONSTANT_MethodRef = 10; + + /** + * Tag value for Interface Method Reference entries. + */ + public final static int CONSTANT_InterfaceMethodRef = 11; + + /** + * Tag value for Name and Type entries. + */ + public final static int CONSTANT_NameAndType = 12; + + /** + * The number of slots in the constant pool, occupied by this entry. + */ + private int numEntries; + + /** + * A flag which indiciates if this entry has been resolved or not. + */ + private boolean resolved; + + /** + * This entry's tag which identifies the type of this constant pool entry. + */ + private int tag; + + /** + * Initialse the constant pool entry. + * + * @param tagValue the tag value which identifies which type of constant + * pool entry this is. + * @param entries the number of constant pool entry slots this entry + * occupies. + */ + public ConstantPoolEntry( int tagValue, int entries ) + { + tag = tagValue; + numEntries = entries; + resolved = false; + } + + /** + * Read a constant pool entry from a stream. This is a factory method which + * reads a constant pool entry form a stream and returns the appropriate + * subclass for the entry. + * + * @param cpStream the stream from which the constant pool entry is to be + * read. + * @return Description of the Returned Value + * @exception IOException Description of Exception + * @returns the appropriate ConstantPoolEntry subclass representing the + * constant pool entry from the stream. + * @throws IOExcception if there is a problem reading the entry from the + * stream. + */ + public static ConstantPoolEntry readEntry( DataInputStream cpStream ) + throws IOException + { + ConstantPoolEntry cpInfo = null; + int cpTag = cpStream.readUnsignedByte(); + + switch ( cpTag ) + { + + case CONSTANT_Utf8: + cpInfo = new Utf8CPInfo(); + + break; + case CONSTANT_Integer: + cpInfo = new IntegerCPInfo(); + + break; + case CONSTANT_Float: + cpInfo = new FloatCPInfo(); + + break; + case CONSTANT_Long: + cpInfo = new LongCPInfo(); + + break; + case CONSTANT_Double: + cpInfo = new DoubleCPInfo(); + + break; + case CONSTANT_Class: + cpInfo = new ClassCPInfo(); + + break; + case CONSTANT_String: + cpInfo = new StringCPInfo(); + + break; + case CONSTANT_FieldRef: + cpInfo = new FieldRefCPInfo(); + + break; + case CONSTANT_MethodRef: + cpInfo = new MethodRefCPInfo(); + + break; + case CONSTANT_InterfaceMethodRef: + cpInfo = new InterfaceMethodRefCPInfo(); + + break; + case CONSTANT_NameAndType: + cpInfo = new NameAndTypeCPInfo(); + + break; + default: + throw new ClassFormatError( "Invalid Constant Pool entry Type " + cpTag ); + } + + cpInfo.read( cpStream ); + + return cpInfo; + } + + /** + * Get the number of Constant Pool Entry slots within the constant pool + * occupied by this entry. + * + * @return the number of slots used. + */ + public final int getNumEntries() + { + return numEntries; + } + + /** + * Get the Entry's type tag. + * + * @return The Tag value of this entry + */ + public int getTag() + { + return tag; + } + + /** + * Indicates whether this entry has been resolved. In general a constant + * pool entry can reference another constant pool entry by its index value. + * Resolution involves replacing this index value with the constant pool + * entry at that index. + * + * @return true if this entry has been resolved. + */ + public boolean isResolved() + { + return resolved; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public abstract void read( DataInputStream cpStream ) + throws IOException; + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + resolved = true; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java new file mode 100644 index 000000000..94122a060 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * The constant pool entry subclass used to represent double constant values. + * + * @author Conor MacNeill + */ +public class DoubleCPInfo extends ConstantCPInfo +{ + public DoubleCPInfo() + { + super( CONSTANT_Double, 2 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Double( cpStream.readDouble() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Double Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java new file mode 100644 index 000000000..bf56aee85 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A FieldRef CP Info + * + * @author Conor MacNeill + */ +public class FieldRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String fieldClassName; + private String fieldName; + private String fieldType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public FieldRefCPInfo() + { + super( CONSTANT_FieldRef, 1 ); + } + + public String getFieldClassName() + { + return fieldClassName; + } + + public String getFieldName() + { + return fieldName; + } + + public String getFieldType() + { + return fieldType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo fieldClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + fieldClass.resolve( constantPool ); + + fieldClassName = fieldClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + fieldName = nt.getName(); + fieldType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Field : Class = " + fieldClassName + ", name = " + fieldName + ", type = " + fieldType; + } + else + { + value = "Field : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java new file mode 100644 index 000000000..affdeefc1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * A Float CP Info + * + * @author Conor MacNeill + */ +public class FloatCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public FloatCPInfo() + { + super( CONSTANT_Float, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Float( cpStream.readFloat() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Float Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java new file mode 100644 index 000000000..ce7acbc52 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * An Integer CP Info + * + * @author Conor MacNeill + */ +public class IntegerCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public IntegerCPInfo() + { + super( CONSTANT_Integer, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Integer( cpStream.readInt() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Integer Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java new file mode 100644 index 000000000..bc2077fed --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A InterfaceMethodRef CP Info + * + * @author Conor MacNeill + */ +public class InterfaceMethodRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String interfaceMethodClassName; + private String interfaceMethodName; + private String interfaceMethodType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public InterfaceMethodRefCPInfo() + { + super( CONSTANT_InterfaceMethodRef, 1 ); + } + + public String getInterfaceMethodClassName() + { + return interfaceMethodClassName; + } + + public String getInterfaceMethodName() + { + return interfaceMethodName; + } + + public String getInterfaceMethodType() + { + return interfaceMethodType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo interfaceMethodClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + interfaceMethodClass.resolve( constantPool ); + + interfaceMethodClassName = interfaceMethodClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + interfaceMethodName = nt.getName(); + interfaceMethodType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "InterfaceMethod : Class = " + interfaceMethodClassName + ", name = " + interfaceMethodName + ", type = " + + interfaceMethodType; + } + else + { + value = "InterfaceMethod : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java new file mode 100644 index 000000000..12f981faf --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * A Long CP Info + * + * @author Conor MacNeill + */ +public class LongCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public LongCPInfo() + { + super( CONSTANT_Long, 2 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Long( cpStream.readLong() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Long Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java new file mode 100644 index 000000000..a284adcf9 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A MethodRef CP Info + * + * @author Conor MacNeill + */ +public class MethodRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String methodClassName; + private String methodName; + private String methodType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public MethodRefCPInfo() + { + super( CONSTANT_MethodRef, 1 ); + } + + public String getMethodClassName() + { + return methodClassName; + } + + public String getMethodName() + { + return methodName; + } + + public String getMethodType() + { + return methodType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo methodClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + methodClass.resolve( constantPool ); + + methodClassName = methodClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + methodName = nt.getName(); + methodType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Method : Class = " + methodClassName + ", name = " + methodName + ", type = " + methodType; + } + else + { + value = "Method : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java new file mode 100644 index 000000000..8b769629b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A NameAndType CP Info + * + * @author Conor MacNeill + */ +public class NameAndTypeCPInfo extends ConstantPoolEntry +{ + private int descriptorIndex; + + private String name; + private int nameIndex; + private String type; + + /** + * Constructor. + */ + public NameAndTypeCPInfo() + { + super( CONSTANT_NameAndType, 1 ); + } + + public String getName() + { + return name; + } + + public String getType() + { + return type; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + nameIndex = cpStream.readUnsignedShort(); + descriptorIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + name = ( ( Utf8CPInfo )constantPool.getEntry( nameIndex ) ).getValue(); + type = ( ( Utf8CPInfo )constantPool.getEntry( descriptorIndex ) ).getValue(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Name = " + name + ", type = " + type; + } + else + { + value = "Name index = " + nameIndex + ", descriptor index = " + descriptorIndex; + } + + return value; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java new file mode 100644 index 000000000..f65ad291b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A String Constant Pool Entry. The String info contains an index into the + * constant pool where a UTF8 string is stored. + * + * @author Conor MacNeill + */ +public class StringCPInfo extends ConstantCPInfo +{ + + private int index; + + /** + * Constructor. + */ + public StringCPInfo() + { + super( CONSTANT_String, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + index = cpStream.readUnsignedShort(); + + setValue( "unresolved" ); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + setValue( ( ( Utf8CPInfo )constantPool.getEntry( index ) ).getValue() ); + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "String Constant Pool Entry for " + getValue() + "[" + index + "]"; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java new file mode 100644 index 000000000..dc42d1984 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A UTF8 Constant Pool Entry. + * + * @author Conor MacNeill + */ +public class Utf8CPInfo extends ConstantPoolEntry +{ + private String value; + + /** + * Constructor. + */ + public Utf8CPInfo() + { + super( CONSTANT_Utf8, 1 ); + } + + public String getValue() + { + return value; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + value = cpStream.readUTF(); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "UTF8 Value = " + value; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java new file mode 100644 index 000000000..371264a4a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java @@ -0,0 +1,965 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet; +import java.io.File;// ==================================================================== +// imports +// ==================================================================== +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.types.Path; + + +// ==================================================================== +/** + * This task compiles CSharp source into executables or modules. The task will + * only work on win2K until other platforms support csc.exe or an equivalent. + * CSC.exe must be on the execute path too.

      + * + * All parameters are optional: <csc/> should suffice to produce a debug + * build of all *.cs files. References to external files do require explicit + * enumeration, so are one of the first attributes to consider adding.

      + * + * The task is a directory based task, so attributes like includes="*.cs" + * and excludes="broken.cs" can be used to control the files pulled in. + * By default, all *.cs files from the project folder down are included in the + * command. When this happens the output file -if not specified- is taken as the + * first file in the list, which may be somewhat hard to control. Specifying the + * output file with 'outfile' seems prudent.

      + * + *

      + * + * TODO + *

        + *
      1. is incremental build still broken in beta-1? + *
      2. is Win32Icon broken? + *
      3. all the missing options + *
      + *

      + * + * History + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      + * 0.3 + * + * Beta 1 edition + * + * To avoid having to remember which assemblies to include, the task + * automatically refers to the main dotnet libraries in Beta1. + *
      + * 0.2 + * + * Slightly different + * + * Split command execution to a separate class; + *
      + * 0.1 + * + * "I can't believe it's so rudimentary" + * + * First pass; minimal builds only support; + *
      + * + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.3 + */ + +public class CSharp + extends org.apache.tools.ant.taskdefs.MatchingTask +{ + + /** + * name of the executable. the .exe suffix is deliberately not included in + * anticipation of the unix version + */ + protected final static String csc_exe_name = "csc"; + + /** + * what is the file extension we search on? + */ + protected final static String csc_file_ext = "cs"; + + /** + * derive the search pattern from the extension + */ + protected final static String csc_file_pattern = "**/*." + csc_file_ext; + + /** + * Fix C# reference inclusion. C# is really dumb in how it handles + * inclusion. You have to list every 'assembly' -read DLL that is imported. + * So already you are making a platform assumption -shared libraries have a + * .dll;"+ extension and the poor developer has to know every library which + * is included why the compiler cant find classes on the path or in a + * directory, is a mystery. To reduce the need to be explicit, here is a + * long list of the core libraries used in Beta-1 of .NET ommitting the + * blatantly non portable (MS.win32.interop) and the .designer libraries. + * (ripping out Com was tempting) Casing is chosen to match that of the file + * system exactly so may work on a unix box too. + */ + + protected final static String DEFAULT_REFERENCE_LIST = + "Accessibility.dll;" + + "cscompmgd.dll;" + + "CustomMarshalers.dll;" + + "IEExecRemote.dll;" + + "IEHost.dll;" + + "IIEHost.dll;" + + "ISymWrapper.dll;" + + "Microsoft.JScript.dll;" + + "Microsoft.VisualBasic.dll;" + + "Microsoft.VisualC.dll;" + + "Microsoft.Vsa.dll;" + + "Mscorcfg.dll;" + + "RegCode.dll;" + + "System.Configuration.Install.dll;" + + "System.Data.dll;" + + "System.Design.dll;" + + "System.DirectoryServices.dll;" + + "System.EnterpriseServices.dll;" + + "System.dll;" + + "System.Drawing.Design.dll;" + + "System.Drawing.dll;" + + "System.Management.dll;" + + "System.Messaging.dll;" + + "System.Runtime.Remoting.dll;" + + "System.Runtime.Serialization.Formatters.Soap.dll;" + + "System.Security.dll;" + + "System.ServiceProcess.dll;" + + "System.Web.dll;" + + "System.Web.RegularExpressions.dll;" + + "System.Web.Services.dll;" + + "System.Windows.Forms.dll;" + + "System.XML.dll;"; + + /** + * utf out flag + */ + + protected boolean _utf8output = false; + + protected boolean _noconfig = false; + + // /fullpaths + protected boolean _fullpaths = false; + + /** + * debug flag. Controls generation of debug information. + */ + protected boolean _debug; + + /** + * output XML documentation flag + */ + protected File _docFile; + + /** + * any extra command options? + */ + protected String _extraOptions; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * flag to enable automatic reference inclusion + */ + protected boolean _includeDefaultReferences; + + /** + * incremental build flag + */ + protected boolean _incremental; + + /** + * main class (or null for automatic choice) + */ + protected String _mainClass; + + /** + * optimise flag + */ + protected boolean _optimize; + + /** + * output file. If not supplied this is derived from the source file + */ + protected File _outputFile; + + /** + * using the path approach didnt work as it could not handle the implicit + * execution path. Perhaps that could be extracted from the runtime and then + * the path approach would be viable + */ + protected Path _referenceFiles; + + /** + * list of reference classes. (pretty much a classpath equivalent) + */ + protected String _references; + + /** + * type of target. Should be one of exe|library|module|winexe|(null) default + * is exe; the actual value (if not null) is fed to the command line.
      + * See /target + */ + protected String _targetType; + + /** + * enable unsafe code flag. Clearly set to false by default + */ + protected boolean _unsafe; + + /** + * icon for incorporation into apps + */ + protected File _win32icon; + /** + * icon for incorporation into apps + */ + protected File _win32res; + + /** + * list of extra modules to refer to + */ + String _additionalModules; + + /** + * defines list something like 'RELEASE;WIN32;NO_SANITY_CHECKS;;SOMETHING_ELSE' + */ + String _definitions; + + /** + * destination directory (null means use the source directory) NB: this is + * currently not used + */ + private File _destDir; + + /** + * source directory upon which the search pattern is applied + */ + private File _srcDir; + + /** + * warning level: 0-4, with 4 being most verbose + */ + private int _warnLevel; + + /** + * constructor inits everything and set up the search pattern + */ + + public CSharp() + { + Clear(); + setIncludes( csc_file_pattern ); + } + + /** + * Set the definitions + * + * @param params The new AdditionalModules value + */ + public void setAdditionalModules( String params ) + { + _additionalModules = params; + } + + /** + * set the debug flag on or off + * + * @param f on/off flag + */ + public void setDebug( boolean f ) + { + _debug = f; + } + + /** + * Set the definitions + * + * @param params The new Definitions value + */ + public void setDefinitions( String params ) + { + _definitions = params; + } + + /** + * Set the destination dir to find the files to be compiled + * + * @param dirName The new DestDir value + */ + public void setDestDir( File dirName ) + { + _destDir = dirName; + } + + /** + * file for generated XML documentation + * + * @param f output file + */ + public void setDocFile( File f ) + { + _docFile = f; + } + + /** + * Sets the ExtraOptions attribute + * + * @param extraOptions The new ExtraOptions value + */ + public void setExtraOptions( String extraOptions ) + { + this._extraOptions = extraOptions; + } + + /** + * set fail on error flag + * + * @param b The new FailOnError value + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + public void setFullPaths( boolean enabled ) + { + _fullpaths = enabled; + } + + /** + * set the automatic reference inclusion flag on or off this flag controls + * the string of references and the /nostdlib option in CSC + * + * @param f on/off flag + */ + public void setIncludeDefaultReferences( boolean f ) + { + _includeDefaultReferences = f; + } + + /** + * set the incremental compilation flag on or off + * + * @param f on/off flag + */ + public void setIncremental( boolean f ) + { + _incremental = f; + } + + /** + * Sets the MainClass attribute + * + * @param mainClass The new MainClass value + */ + public void setMainClass( String mainClass ) + { + this._mainClass = mainClass; + } + + /** + * set the optimise flag on or off + * + * @param f on/off flag + */ + public void setOptimize( boolean f ) + { + _optimize = f; + } + + /** + * Set the definitions + * + * @param params The new OutputFile value + */ + public void setOutputFile( File params ) + { + _outputFile = params; + } + + /** + * add another path to the reference file path list + * + * @param path another path to append + */ + public void setReferenceFiles( Path path ) + { + //demand create pathlist + if( _referenceFiles == null ) + _referenceFiles = new Path( this.project ); + _referenceFiles.append( path ); + } + + /** + * Set the reference list to be used for this compilation. + * + * @param s The new References value + */ + public void setReferences( String s ) + { + _references = s; + } + + /** + * Set the source dir to find the files to be compiled + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + _srcDir = srcDirName; + } + + /** + * define the target + * + * @param targetType The new TargetType value + * @exception BuildException if target is not one of + * exe|library|module|winexe + */ + public void setTargetType( String targetType ) + throws BuildException + { + targetType = targetType.toLowerCase(); + if( targetType.equals( "exe" ) || targetType.equals( "library" ) || + targetType.equals( "module" ) || targetType.equals( "winexe" ) ) + { + _targetType = targetType; + } + else + throw new BuildException( "targetType " + targetType + " is not a valid type" ); + } + + /** + * Sets the Unsafe attribute + * + * @param unsafe The new Unsafe value + */ + public void setUnsafe( boolean unsafe ) + { + this._unsafe = unsafe; + } + + /** + * enable generation of utf8 output from the compiler. + * + * @param enabled The new Utf8Output value + */ + public void setUtf8Output( boolean enabled ) + { + _utf8output = enabled; + } + + /** + * set warn level (no range checking) + * + * @param warnLevel warn level -see .net docs for valid range (probably 0-4) + */ + public void setWarnLevel( int warnLevel ) + { + this._warnLevel = warnLevel; + } + + /** + * Set the win32 icon + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setWin32Icon( File fileName ) + { + _win32icon = fileName; + } + + /** + * Set the win32 icon + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setWin32Res( File fileName ) + { + _win32res = fileName; + } + + /** + * query the debug flag + * + * @return true if debug is turned on + */ + public boolean getDebug() + { + return _debug; + } + + /** + * Gets the ExtraOptions attribute + * + * @return The ExtraOptions value + */ + public String getExtraOptions() + { + return this._extraOptions; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * query the optimise flag + * + * @return true if optimise is turned on + */ + public boolean getIncludeDefaultReferences() + { + return _includeDefaultReferences; + } + + /** + * query the incrementalflag + * + * @return true iff incremental compilation is turned on + */ + public boolean getIncremental() + { + return _incremental; + } + + /** + * Gets the MainClass attribute + * + * @return The MainClass value + */ + public String getMainClass() + { + return this._mainClass; + } + + /** + * query the optimise flag + * + * @return true if optimise is turned on + */ + public boolean getOptimize() + { + return _optimize; + } + + /** + * Gets the TargetType attribute + * + * @return The TargetType value + */ + public String getTargetType() + { + return _targetType; + } + + /** + * query the Unsafe attribute + * + * @return The Unsafe value + */ + public boolean getUnsafe() + { + return this._unsafe; + } + + /** + * query warn level + * + * @return current value + */ + public int getWarnLevel() + { + return _warnLevel; + } + + /** + * reset all contents. + */ + public void Clear() + { + _targetType = null; + _win32icon = null; + _srcDir = null; + _destDir = null; + _mainClass = null; + _unsafe = false; + _warnLevel = 3; + _docFile = null; + _incremental = false; + _optimize = false; + _debug = true; + _references = null; + _failOnError = true; + _definitions = null; + _additionalModules = null; + _includeDefaultReferences = true; + _extraOptions = null; + _fullpaths = true; + } + + /** + * do the work by building the command line and then calling it + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( _srcDir == null ) + _srcDir = project.resolveFile( "." ); + + NetCommand command = new NetCommand( this, "CSC", csc_exe_name ); + command.setFailOnError( getFailFailOnError() ); + //DEBUG helper + command.setTraceCommandLine( true ); + //fill in args + command.addArgument( "/nologo" ); + command.addArgument( getAdditionalModulesParameter() ); + command.addArgument( getDefinitionsParameter() ); + command.addArgument( getDebugParameter() ); + command.addArgument( getDocFileParameter() ); + command.addArgument( getIncrementalParameter() ); + command.addArgument( getMainClassParameter() ); + command.addArgument( getOptimizeParameter() ); + command.addArgument( getReferencesParameter() ); + command.addArgument( getTargetTypeParameter() ); + command.addArgument( getUnsafeParameter() ); + command.addArgument( getWarnLevelParameter() ); + command.addArgument( getWin32IconParameter() ); + command.addArgument( getOutputFileParameter() ); + command.addArgument( getIncludeDefaultReferencesParameter() ); + command.addArgument( getDefaultReferenceParameter() ); + command.addArgument( getWin32ResParameter() ); + command.addArgument( getUtf8OutpuParameter() ); + command.addArgument( getNoConfigParameter() ); + command.addArgument( getFullPathsParameter() ); + command.addArgument( getExtraOptionsParameter() ); + + //get dependencies list. + DirectoryScanner scanner = super.getDirectoryScanner( _srcDir ); + String[] dependencies = scanner.getIncludedFiles(); + log( "compiling " + dependencies.length + " file" + ( ( dependencies.length == 1 ) ? "" : "s" ) ); + String baseDir = scanner.getBasedir().toString(); + //add to the command + for( int i = 0; i < dependencies.length; i++ ) + { + String targetFile = dependencies[i]; + targetFile = baseDir + File.separator + targetFile; + command.addArgument( targetFile ); + } + + //now run the command of exe + settings + files + command.runCommand(); + } + + protected void setNoConfig( boolean enabled ) + { + _noconfig = enabled; + } + + /** + * get the argument or null for no argument needed + * + * @return The AdditionalModules Parameter to CSC + */ + protected String getAdditionalModulesParameter() + { + if( notEmpty( _additionalModules ) ) + return "/addmodule:" + _additionalModules; + else + return null; + } + + /** + * get the debug switch argument + * + * @return The Debug Parameter to CSC + */ + protected String getDebugParameter() + { + return "/debug" + ( _debug ? "+" : "-" ); + } + + + /** + * get default reference list + * + * @return null or a string of references. + */ + protected String getDefaultReferenceParameter() + { + if( _includeDefaultReferences ) + { + StringBuffer s = new StringBuffer( "/reference:" ); + s.append( DEFAULT_REFERENCE_LIST ); + return new String( s ); + } + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Definitions Parameter to CSC + */ + protected String getDefinitionsParameter() + { + if( notEmpty( _definitions ) ) + return "/define:" + _definitions; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The DocFile Parameter to CSC + */ + protected String getDocFileParameter() + { + if( _docFile != null ) + return "/doc:" + _docFile.toString(); + else + return null; + } + + /** + * get any extra options or null for no argument needed + * + * @return The ExtraOptions Parameter to CSC + */ + protected String getExtraOptionsParameter() + { + if( _extraOptions != null && _extraOptions.length() != 0 ) + return _extraOptions; + else + return null; + } + + protected String getFullPathsParameter() + { + return _fullpaths ? "/fullpaths" : null; + } + + /** + * get the include default references flag or null for no argument needed + * + * @return The Parameter to CSC + */ + protected String getIncludeDefaultReferencesParameter() + { + return "/nostdlib" + ( _includeDefaultReferences ? "-" : "+" ); + } + + /** + * get the incremental build argument + * + * @return The Incremental Parameter to CSC + */ + protected String getIncrementalParameter() + { + return "/incremental" + ( _incremental ? "+" : "-" ); + } + + /** + * get the /main argument or null for no argument needed + * + * @return The MainClass Parameter to CSC + */ + protected String getMainClassParameter() + { + if( _mainClass != null && _mainClass.length() != 0 ) + return "/main:" + _mainClass; + else + return null; + } + + protected String getNoConfigParameter() + { + return _noconfig ? "/noconfig" : null; + } + + /** + * get the optimise flag or null for no argument needed + * + * @return The Optimize Parameter to CSC + */ + protected String getOptimizeParameter() + { + return "/optimize" + ( _optimize ? "+" : "-" ); + } + + /** + * get the argument or null for no argument needed + * + * @return The OutputFile Parameter to CSC + */ + protected String getOutputFileParameter() + { + if( _outputFile != null ) + { + File f = _outputFile; + return "/out:" + f.toString(); + } + else + return null; + } + + /** + * turn the path list into a list of files and a /references argument + * + * @return null or a string of references. + */ + protected String getReferenceFilesParameter() + { + //bail on no references + if( _references == null ) + return null; + //iterate through the ref list & generate an entry for each + //or just rely on the fact that the toString operator does this, but + //noting that the separator is ';' on windows, ':' on unix + String refpath = _references.toString(); + + //bail on no references listed + if( refpath.length() == 0 ) + return null; + + StringBuffer s = new StringBuffer( "/reference:" ); + s.append( refpath ); + return new String( s ); + } + + /** + * get the reference string or null for no argument needed + * + * @return The References Parameter to CSC + */ + protected String getReferencesParameter() + { + //bail on no references + if( notEmpty( _references ) ) + return "/reference:" + _references; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The TargetType Parameter to CSC + */ + protected String getTargetTypeParameter() + { + if( notEmpty( _targetType ) ) + return "/target:" + _targetType; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Unsafe Parameter to CSC + */ + protected String getUnsafeParameter() + { + return _unsafe ? "/unsafe" : null; + } + + protected String getUtf8OutpuParameter() + { + return _utf8output ? "/utf8output" : null; + } + + /** + * get the warn level switch + * + * @return The WarnLevel Parameter to CSC + */ + protected String getWarnLevelParameter() + { + return "/warn:" + _warnLevel; + } + + /** + * get the argument or null for no argument needed + * + * @return The Win32Icon Parameter to CSC + */ + protected String getWin32IconParameter() + { + if( _win32icon != null ) + return "/win32icon:" + _win32icon.toString(); + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Win32Icon Parameter to CSC + */ + protected String getWin32ResParameter() + { + if( _win32res != null ) + return "/win32res:" + _win32res.toString(); + else + return null; + } + + /** + * test for a string containing something useful + * + * @param s string in + * @return true if the argument is not null or empty + */ + protected boolean notEmpty( String s ) + { + return s != null && s.length() != 0; + }// end execute + +}//end class diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java new file mode 100644 index 000000000..b1440d32f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet;// ==================================================================== +// imports +// ==================================================================== +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; + + +/** + * Task to assemble .net 'Intermediate Language' files. The task will only work + * on win2K until other platforms support csc.exe or an equivalent. ilasm.exe + * must be on the execute path too.

      + * + *

      + * + * All parameters are optional: <il/> should suffice to produce a debug + * build of all *.il files. The option set is roughly compatible with the CSharp + * class; even though the command line options are only vaguely equivalent. [The + * low level commands take things like /OUT=file, csc wants /out:file ... + * /verbose is used some places; /quiet here in ildasm... etc.] It would be nice + * if someone made all the command line tools consistent (and not as brittle as + * the java cmdline tools)

      + * + * The task is a directory based task, so attributes like includes="*.il" + * and excludes="broken.il" can be used to control the files pulled in. + * Each file is built on its own, producing an appropriately named output file + * unless manually specified with outfile + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.2 + */ + +public class Ilasm + extends org.apache.tools.ant.taskdefs.MatchingTask +{ + + /** + * name of the executable. the .exe suffix is deliberately not included in + * anticipation of the unix version + */ + protected final static String exe_name = "ilasm"; + + /** + * what is the file extension we search on? + */ + protected final static String file_ext = "il"; + + /** + * and now derive the search pattern from the extension + */ + protected final static String file_pattern = "**/*." + file_ext; + + /** + * title of task for external presentation + */ + protected final static String exe_title = "ilasm"; + + /** + * debug flag. Controls generation of debug information. + */ + protected boolean _debug; + + /** + * any extra command options? + */ + protected String _extraOptions; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * listing flag + */ + + protected boolean _listing; + + /** + * output file. If not supplied this is derived from the source file + */ + protected File _outputFile; + + /** + * resource file (.res format) to include in the app. + */ + protected File _resourceFile; + + /** + * type of target. Should be one of exe|library|module|winexe|(null) default + * is exe; the actual value (if not null) is fed to the command line.
      + * See /target + */ + protected String _targetType; + + /** + * verbose flag + */ + protected boolean _verbose; + + /** + * file containing private key + */ + + private File _keyfile; + + /** + * source directory upon which the search pattern is applied + */ + private File _srcDir; + + /** + * constructor inits everything and set up the search pattern + */ + public Ilasm() + { + Clear(); + setIncludes( file_pattern ); + } + + /** + * set the debug flag on or off + * + * @param f on/off flag + */ + public void setDebug( boolean f ) + { + _debug = f; + } + + /** + * Sets the ExtraOptions attribute + * + * @param extraOptions The new ExtraOptions value + */ + public void setExtraOptions( String extraOptions ) + { + this._extraOptions = extraOptions; + } + + /** + * set fail on error flag + * + * @param b The new FailOnError value + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + public void setKeyfile( File keyfile ) + { + this._keyfile = keyfile; + } + + /** + * enable/disable listing + * + * @param b flag set to true for listing on + */ + public void setListing( boolean b ) + { + _listing = b; + } + + /** + * Set the definitions + * + * @param params The new OutputFile value + */ + public void setOutputFile( File params ) + { + _outputFile = params; + } + + + /** + * Sets the Owner attribute + * + * @param s The new Owner value + */ + + public void setOwner( String s ) + { + log( "This option is not supported by ILASM as of Beta-2, and will be ignored", Project.MSG_WARN ); + } + + /** + * Set the resource file + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setResourceFile( File fileName ) + { + _resourceFile = fileName; + } + + /** + * Set the source dir to find the files to be compiled + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + _srcDir = srcDirName; + } + + /** + * define the target + * + * @param targetType one of exe|library| + * @exception BuildException if target is not one of + * exe|library|module|winexe + */ + + public void setTargetType( String targetType ) + throws BuildException + { + targetType = targetType.toLowerCase(); + if( targetType.equals( "exe" ) || targetType.equals( "library" ) ) + { + _targetType = targetType; + } + else + throw new BuildException( "targetType " + targetType + " is not a valid type" ); + } + + /** + * enable/disable verbose ILASM output + * + * @param b flag set to true for verbose on + */ + public void setVerbose( boolean b ) + { + _verbose = b; + } + + /** + * query the debug flag + * + * @return true if debug is turned on + */ + public boolean getDebug() + { + return _debug; + } + + /** + * Gets the ExtraOptions attribute + * + * @return The ExtraOptions value + */ + public String getExtraOptions() + { + return this._extraOptions; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * accessor method for target type + * + * @return the current target option + */ + public String getTargetType() + { + return _targetType; + } + + /** + * reset all contents. + */ + public void Clear() + { + _targetType = null; + _srcDir = null; + _listing = false; + _verbose = false; + _debug = true; + _outputFile = null; + _failOnError = true; + _resourceFile = null; + _extraOptions = null; + } + + + /** + * This is the execution entry point. Build a list of files and call ilasm + * on each of them. + * + * @throws BuildException if the assembly failed and FailOnError is true + */ + public void execute() + throws BuildException + { + if( _srcDir == null ) + _srcDir = project.resolveFile( "." ); + + //get dependencies list. + DirectoryScanner scanner = super.getDirectoryScanner( _srcDir ); + String[] dependencies = scanner.getIncludedFiles(); + log( "assembling " + dependencies.length + " file" + ( ( dependencies.length == 1 ) ? "" : "s" ) ); + String baseDir = scanner.getBasedir().toString(); + //add to the command + for( int i = 0; i < dependencies.length; i++ ) + { + String targetFile = dependencies[i]; + targetFile = baseDir + File.separator + targetFile; + executeOneFile( targetFile ); + } + + }// end execute + + + /** + * do the work for one file by building the command line then calling it + * + * @param targetFile name of the the file to assemble + * @throws BuildException if the assembly failed and FailOnError is true + */ + public void executeOneFile( String targetFile ) + throws BuildException + { + NetCommand command = new NetCommand( this, exe_title, exe_name ); + command.setFailOnError( getFailFailOnError() ); + //DEBUG helper + command.setTraceCommandLine( true ); + //fill in args + command.addArgument( getDebugParameter() ); + command.addArgument( getTargetTypeParameter() ); + command.addArgument( getListingParameter() ); + command.addArgument( getOutputFileParameter() ); + command.addArgument( getResourceFileParameter() ); + command.addArgument( getVerboseParameter() ); + command.addArgument( getKeyfileParameter() ); + command.addArgument( getExtraOptionsParameter() ); + + /* + * space for more argumentativeness + * command.addArgument(); + * command.addArgument(); + */ + command.addArgument( targetFile ); + //now run the command of exe + settings + file + command.runCommand(); + } + + /** + * get the argument or null for no argument needed + * + * @return The DebugParameter value + */ + protected String getDebugParameter() + { + return _debug ? "/debug" : null; + } + + /** + * get any extra options or null for no argument needed + * + * @return The ExtraOptions Parameter to CSC + */ + protected String getExtraOptionsParameter() + { + if( _extraOptions != null && _extraOptions.length() != 0 ) + return _extraOptions; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The KeyfileParameter value + */ + protected String getKeyfileParameter() + { + if( _keyfile != null ) + return "/keyfile:" + _keyfile.toString(); + else + return null; + } + + /** + * turn the listing flag into a parameter for ILASM + * + * @return the appropriate string from the state of the listing flag + */ + protected String getListingParameter() + { + return _listing ? "/listing" : "/nolisting"; + } + + /** + * get the output file + * + * @return the argument string or null for no argument + */ + protected String getOutputFileParameter() + { + if( _outputFile == null || _outputFile.length() == 0 ) + return null; + File f = _outputFile; + return "/output=" + f.toString(); + } + + protected String getResourceFileParameter() + { + if( _resourceFile != null ) + { + return "/resource=" + _resourceFile.toString(); + } + else + { + return null; + } + } + + /** + * g get the target type or null for no argument needed + * + * @return The TargetTypeParameter value + */ + + protected String getTargetTypeParameter() + { + if( !notEmpty( _targetType ) ) + return null; + if( _targetType.equals( "exe" ) ) + return "/exe"; + else + if( _targetType.equals( "library" ) ) + return "/dll"; + else + return null; + } + + /** + * turn the verbose flag into a parameter for ILASM + * + * @return null or the appropriate command line string + */ + protected String getVerboseParameter() + { + return _verbose ? null : "/quiet"; + } + + /** + * test for a string containing something useful + * + * @param s Description of Parameter + * @return Description of the Returned Value + * @returns true if the argument is not null or empty + */ + protected boolean notEmpty( String s ) + { + return s != null && s.length() != 0; + }// end executeOneFile +}//class diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java new file mode 100644 index 000000000..4f28dffe7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet;// imports +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * This is a helper class to spawn net commands out. In its initial form it + * contains no .net specifics, just contains all the command line/exe + * construction stuff. However, it may be handy in future to have a means of + * setting the path to point to the dotnet bin directory; in which case the + * shared code should go in here. + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.3 + * @created 2000-11-01 + */ + +public class NetCommand +{ + + /** + * trace flag + */ + protected boolean _traceCommandLine = false; + + /** + * what is the command line + */ + protected Commandline _commandLine; + + /** + * executabe + */ + protected Execute _exe; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * owner project + */ + protected Task _owner; + + /** + * actual program to invoke + */ + protected String _program; + + /** + * title of the command + */ + protected String _title; + + /** + * constructor + * + * @param title (for logging/errors) + * @param owner Description of Parameter + * @param program Description of Parameter + */ + + public NetCommand( Task owner, String title, String program ) + { + _owner = owner; + _title = title; + _program = program; + _commandLine = new Commandline(); + _commandLine.setExecutable( _program ); + prepareExecutor(); + } + + /** + * set fail on error flag + * + * @param b fail flag -set to true to cause an exception to be raised if the + * return value != 0 + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + /** + * turn tracing on or off + * + * @param b trace flag + */ + public void setTraceCommandLine( boolean b ) + { + _traceCommandLine = b; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * add an argument to a command line; do nothing if the arg is null or empty + * string + * + * @param argument The feature to be added to the Argument attribute + */ + public void addArgument( String argument ) + { + if( argument != null && argument.length() != 0 ) + { + _commandLine.createArgument().setValue( argument ); + } + } + + /** + * Run the command using the given Execute instance. + * + * @exception BuildException Description of Exception + * @throws an exception of something goes wrong and the failOnError flag is + * true + */ + public void runCommand() + throws BuildException + { + int err = -1;// assume the worst + try + { + if( _traceCommandLine ) + { + _owner.log( _commandLine.toString() ); + } + else + { + //in verbose mode we always log stuff + logVerbose( _commandLine.toString() ); + } + _exe.setCommandline( _commandLine.getCommandline() ); + err = _exe.execute(); + if( err != 0 ) + { + if( _failOnError ) + { + throw new BuildException( _title + " returned: " + err, _owner.getLocation() ); + } + else + { + _owner.log( _title + " Result: " + err, Project.MSG_ERR ); + } + } + } + catch( IOException e ) + { + throw new BuildException( _title + " failed: " + e, e, _owner.getLocation() ); + } + } + + + /** + * error text log + * + * @param msg message to display as an error + */ + protected void logError( String msg ) + { + _owner.getProject().log( msg, Project.MSG_ERR ); + } + + /** + * verbose text log + * + * @param msg string to add to log iff verbose is defined for the build + */ + protected void logVerbose( String msg ) + { + _owner.getProject().log( msg, Project.MSG_VERBOSE ); + } + + /** + * set up the command sequence.. + */ + protected void prepareExecutor() + { + // default directory to the project's base directory + File dir = _owner.getProject().getBaseDir(); + ExecuteStreamHandler handler = new LogStreamHandler( _owner, + Project.MSG_INFO, Project.MSG_WARN ); + _exe = new Execute( handler, null ); + _exe.setAntRun( _owner.getProject() ); + _exe.setWorkingDirectory( dir ); + } +}//class diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java new file mode 100644 index 000000000..ff7a72719 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + + +/** + * BorlandDeploymentTool is dedicated to the Borland Application Server 4.5 and + * 4.5.1 This task generates and compiles the stubs and skeletons for all ejb + * described into the Deployement Descriptor, builds the jar file including the + * support files and verify whether the produced jar is valid or not. The + * supported options are: + *

        + *
      • debug (boolean) : turn on the debug mode for generation of stubs and + * skeletons (default:false)
      • + *
      • verify (boolean) : turn on the verification at the end of the jar + * production (default:true)
      • + *
      • verifyargs (String) : add optional argument to verify command (see vbj + * com.inprise.ejb.util.Verify)
      • + *
      • basdtd (String) : location of the BAS DTD
      • + *
      • generateclient (boolean) : turn on the client jar file generation + *
      • + *
      + *
      + *
      + *      <ejbjar srcdir="${build.classes}"  basejarname="vsmp"  descriptordir="${rsc.dir}/hrmanager">
      + *        <borland destdir="tstlib">
      + *          <classpath refid="classpath" />
      + *        </borland>
      + *        <include name="**\ejb-jar.xml"/>
      + *        <support dir="${build.classes}">
      + *          <include name="demo\smp\*.class"/>
      + *          <include name="demo\helper\*.class"/>
      + *         </support>
      + *     </ejbjar>
      + *
      + * + * @author Benoit Moussaud + */ +public class BorlandDeploymentTool extends GenericDeploymentTool implements ExecuteStreamHandler +{ + public final static String PUBLICID_BORLAND_EJB + = "-//Inprise Corporation//DTD Enterprise JavaBeans 1.1//EN"; + + protected final static String DEFAULT_BAS45_EJB11_DTD_LOCATION + = "/com/inprise/j2ee/xml/dtds/ejb-jar.dtd"; + + protected final static String DEFAULT_BAS_DTD_LOCATION + = "/com/inprise/j2ee/xml/dtds/ejb-inprise.dtd"; + + protected final static String BAS_DD = "ejb-inprise.xml"; + + /** + * Java2iiop executable * + */ + protected final static String JAVA2IIOP = "java2iiop"; + + /** + * Verify class + */ + protected final static String VERIFY = "com.inprise.ejb.util.Verify"; + + /** + * Instance variable that stores the suffix for the borland jarfile. + */ + private String jarSuffix = "-ejb.jar"; + + /** + * Instance variable that determines whether the debug mode is on + */ + private boolean java2iiopdebug = false; + + /** + * Instance variable that determines whetger the client jar file is + * generated + */ + private boolean generateclient = false; + /** + * Instance variable that determines whether it is necessary to verify the + * produced jar + */ + private boolean verify = true; + private String verifyArgs = ""; + + private Hashtable _genfiles = new Hashtable(); + + /** + * Instance variable that stores the location of the borland DTD file. + */ + private String borlandDTD; + + /** + * Setter used to store the location of the borland DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setBASdtd( String inString ) + { + this.borlandDTD = inString; + } + + /** + * set the debug mode for java2iiop (default false) + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.java2iiopdebug = debug; + } + + + /** + * setter used to store whether the task will include the generate client + * task. (see : BorlandGenerateClient task) + * + * @param b The new Generateclient value + */ + public void setGenerateclient( boolean b ) + { + this.generateclient = b; + } + + /** + * @param is The new ProcessErrorStream value + * @exception IOException Description of Exception + */ + public void setProcessErrorStream( InputStream is ) + throws IOException + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String s = reader.readLine(); + if( s != null ) + { + log( "[java2iiop] " + s, Project.MSG_DEBUG ); + }// end of if () + } + + public void setProcessInputStream( OutputStream param1 ) + throws IOException { } + + /** + * @param is + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + try + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String javafile; + while( ( javafile = reader.readLine() ) != null ) + { + log( "buffer:" + javafile, Project.MSG_DEBUG ); + if( javafile.endsWith( ".java" ) ) + { + String classfile = toClassFile( javafile ); + String key = classfile.substring( getConfig().srcDir.getAbsolutePath().length() + 1 ); + log( " generated : " + classfile, Project.MSG_DEBUG ); + log( " key : " + key, Project.MSG_DEBUG ); + _genfiles.put( key, new File( classfile ) ); + }// end of if () + }// end of while () + reader.close(); + } + catch( Exception e ) + { + String msg = "Exception while parsing java2iiop output. Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + + + /** + * Setter used to store the suffix for the generated borland jar file. + * + * @param inString the string to use as the suffix. + */ + public void setSuffix( String inString ) + { + this.jarSuffix = inString; + } + + /** + * set the verify mode for the produced jar (default true) + * + * @param verify The new Verify value + */ + public void setVerify( boolean verify ) + { + this.verify = verify; + } + + + /** + * sets some additional args to send to verify command + * + * @param args addtions command line parameters + */ + public void setVerifyArgs( String args ) + { + this.verifyArgs = args; + } + + // implementation of org.apache.tools.ant.taskdefs.ExecuteStreamHandler interface + + public void start() + throws IOException { } + + public void stop() { } + + + protected DescriptorHandler getBorlandDescriptorHandler( final File srcDir ) + { + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + protected void processElement() + { + if( currentElement.equals( "type-storage" ) ) + { + // Get the filename of vendor specific descriptor + String fileNameWithMETA = currentText; + //trim the META_INF\ off of the file name + String fileName = fileNameWithMETA.substring( META_DIR.length(), + fileNameWithMETA.length() ); + File descriptorFile = new File( srcDir, fileName ); + + ejbFiles.put( fileNameWithMETA, descriptorFile ); + } + } + }; + handler.registerDTD( PUBLICID_BORLAND_EJB, + borlandDTD == null ? DEFAULT_BAS_DTD_LOCATION : borlandDTD ); + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + + File borlandDD = new File( getConfig().descriptorDir, ddPrefix + BAS_DD ); + if( borlandDD.exists() ) + { + log( "Borland specific file found " + borlandDD, Project.MSG_VERBOSE ); + ejbFiles.put( META_DIR + BAS_DD, borlandDD ); + } + else + { + log( "Unable to locate borland deployment descriptor. It was expected to be in " + + borlandDD.getPath(), Project.MSG_WARN ); + return; + } + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarFile, Hashtable files, String publicId ) + throws BuildException + { + //build the home classes list. + Vector homes = new Vector(); + Iterator it = files.keySet().iterator(); + while( it.hasNext() ) + { + String clazz = ( String )it.next(); + if( clazz.endsWith( "Home.class" ) ) + { + //remove .class extension + String home = toClass( clazz ); + homes.add( home ); + log( " Home " + home, Project.MSG_VERBOSE ); + }// end of if () + }// end of while () + + buildBorlandStubs( homes.iterator(), files ); + + //add the gen files to the collection + files.putAll( _genfiles ); + + super.writeJar( baseName, jarFile, files, publicId ); + + if( verify ) + { + verifyBorlandJar( jarFile ); + }// end of if () + + if( generateclient ) + { + generateClient( jarFile ); + }// end of if () + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } + + /** + * Generate stubs & sketelton for each home found into the DD Add all the + * generate class file into the ejb files + * + * @param ithomes : iterator on home class + * @param files : file list , updated by the adding generated files + */ + private void buildBorlandStubs( Iterator ithomes, Hashtable files ) + { + Execute execTask = null; + + execTask = new Execute( this ); + Project project = getTask().getProject(); + execTask.setAntRun( project ); + execTask.setWorkingDirectory( project.getBaseDir() ); + + Commandline commandline = new Commandline(); + commandline.setExecutable( JAVA2IIOP ); + //debug ? + if( java2iiopdebug ) + { + commandline.createArgument().setValue( "-VBJdebug" ); + }// end of if () + //set the classpath + commandline.createArgument().setValue( "-VBJclasspath" ); + commandline.createArgument().setPath( getCombinedClasspath() ); + //list file + commandline.createArgument().setValue( "-list_files" ); + //no TIE classes + commandline.createArgument().setValue( "-no_tie" ); + //root dir + commandline.createArgument().setValue( "-root_dir" ); + commandline.createArgument().setValue( getConfig().srcDir.getAbsolutePath() ); + //compiling order + commandline.createArgument().setValue( "-compile" ); + //add the home class + while( ithomes.hasNext() ) + { + commandline.createArgument().setValue( ithomes.next().toString() ); + }// end of while () + + try + { + log( "Calling java2iiop", Project.MSG_VERBOSE ); + log( commandline.toString(), Project.MSG_DEBUG ); + execTask.setCommandline( commandline.getCommandline() ); + int result = execTask.execute(); + if( result != 0 ) + { + String msg = "Failed executing java2iiop (ret code is " + result + ")"; + throw new BuildException( msg, getTask().getLocation() ); + } + } + catch( java.io.IOException e ) + { + log( "java2iiop exception :" + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( e ); + } + } + + /** + * Generate the client jar corresponding to the jar file passed as paremeter + * the method uses the BorlandGenerateClient task. + * + * @param sourceJar java.io.File representing the produced jar file + */ + private void generateClient( File sourceJar ) + { + getTask().getProject().addTaskDefinition( "internal_bas_generateclient", + org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient.class ); + + org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient gentask = null; + log( "generate client for " + sourceJar, Project.MSG_INFO ); + try + { + String args = verifyArgs; + args += " " + sourceJar.getPath(); + + gentask = ( BorlandGenerateClient )getTask().getProject().createTask( "internal_bas_generateclient" ); + gentask.setEjbjar( sourceJar ); + gentask.setDebug( java2iiopdebug ); + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + gentask.setClasspath( classpath ); + } + gentask.setTaskName( "generate client" ); + gentask.execute(); + } + catch( Exception e ) + { + //TO DO : delete the file if it is not a valid file. + String msg = "Exception while calling " + VERIFY + " Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + + /** + * convert a class file name : A/B/C/toto.class into a class name: + * A.B.C.toto + * + * @param filename Description of Parameter + * @return Description of the Returned Value + */ + private String toClass( String filename ) + { + //remove the .class + String classname = filename.substring( 0, filename.lastIndexOf( ".class" ) ); + classname = classname.replace( '\\', '.' ); + return classname; + } + + /** + * convert a file name : A/B/C/toto.java into a class name: A/B/C/toto.class + * + * @param filename Description of Parameter + * @return Description of the Returned Value + */ + private String toClassFile( String filename ) + { + //remove the .class + String classfile = filename.substring( 0, filename.lastIndexOf( ".java" ) ); + classfile = classfile + ".class"; + return classfile; + } + + /** + * Verify the produced jar file by invoking the Borland verify tool + * + * @param sourceJar java.io.File representing the produced jar file + */ + private void verifyBorlandJar( File sourceJar ) + { + org.apache.tools.ant.taskdefs.Java javaTask = null; + log( "verify " + sourceJar, Project.MSG_INFO ); + try + { + + String args = verifyArgs; + args += " " + sourceJar.getPath(); + + javaTask = ( Java )getTask().getProject().createTask( "java" ); + javaTask.setTaskName( "verify" ); + javaTask.setClassname( VERIFY ); + Commandline.Argument arguments = javaTask.createArg(); + arguments.setLine( args ); + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + javaTask.setClasspath( classpath ); + javaTask.setFork( true ); + } + + log( "Calling " + VERIFY + " for " + sourceJar.toString(), Project.MSG_VERBOSE ); + javaTask.execute(); + } + catch( Exception e ) + { + //TO DO : delete the file if it is not a valid file. + String msg = "Exception while calling " + VERIFY + " Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java new file mode 100644 index 000000000..1dab7dd20 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + + +/** + * BorlandGenerateClient is dedicated to the Borland Application Server 4.5 This + * task generates the client jar using as input the ejb jar file. Two mode are + * available: java mode (default) and fork mode. With the fork mode, it is + * impossible to add classpath to the commmand line. + * + * @author Benoit Moussaud + */ +public class BorlandGenerateClient extends Task +{ + final static String JAVA_MODE = "java"; + final static String FORK_MODE = "fork"; + + /** + * debug the generateclient task + */ + boolean debug = false; + + /** + * hold the ejbjar file name + */ + File ejbjarfile = null; + + /** + * hold the client jar file name + */ + File clientjarfile = null; + + /** + * hold the mode (java|fork) + */ + String mode = JAVA_MODE; + + /** + * hold the classpath + */ + Path classpath; + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setClientjar( File clientjar ) + { + clientjarfile = clientjar; + } + + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + public void setEjbjar( File ejbfile ) + { + ejbjarfile = ejbfile; + } + + public void setMode( String s ) + { + mode = s; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a java task. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( ejbjarfile == null || + ejbjarfile.isDirectory() ) + { + throw new BuildException( "invalid ejb jar file." ); + }// end of if () + + if( clientjarfile == null || + clientjarfile.isDirectory() ) + { + log( "invalid or missing client jar file.", Project.MSG_VERBOSE ); + String ejbjarname = ejbjarfile.getAbsolutePath(); + //clientname = ejbjarfile+client.jar + String clientname = ejbjarname.substring( 0, ejbjarname.lastIndexOf( "." ) ); + clientname = clientname + "client.jar"; + clientjarfile = new File( clientname ); + + }// end of if () + + if( mode == null ) + { + log( "mode is null default mode is java" ); + setMode( JAVA_MODE ); + }// end of if () + + log( "client jar file is " + clientjarfile ); + + if( mode.equalsIgnoreCase( FORK_MODE ) ) + { + executeFork(); + }// end of if () + else + { + executeJava(); + }// end of else + } + + /** + * launch the generate client using system api + * + * @exception BuildException Description of Exception + */ + protected void executeFork() + throws BuildException + { + try + { + log( "mode : fork" ); + + org.apache.tools.ant.taskdefs.ExecTask execTask = null; + execTask = ( ExecTask )getProject().createTask( "exec" ); + + execTask.setDir( new File( "." ) ); + execTask.setExecutable( "iastool" ); + execTask.createArg().setValue( "generateclient" ); + if( debug ) + { + execTask.createArg().setValue( "-trace" ); + }// end of if () + + // + execTask.createArg().setValue( "-short" ); + execTask.createArg().setValue( "-jarfile" ); + // ejb jar file + execTask.createArg().setValue( ejbjarfile.getAbsolutePath() ); + //client jar file + execTask.createArg().setValue( "-single" ); + execTask.createArg().setValue( "-clientjarfile" ); + execTask.createArg().setValue( clientjarfile.getAbsolutePath() ); + + log( "Calling java2iiop", Project.MSG_VERBOSE ); + execTask.execute(); + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling generateclient Details: " + e.toString(); + throw new BuildException( msg, e ); + } + + } + + /** + * launch the generate client using java api + * + * @exception BuildException Description of Exception + */ + protected void executeJava() + throws BuildException + { + try + { + log( "mode : java" ); + + org.apache.tools.ant.taskdefs.Java execTask = null; + execTask = ( Java )getProject().createTask( "java" ); + + execTask.setDir( new File( "." ) ); + execTask.setClassname( "com.inprise.server.commandline.EJBUtilities" ); + //classpath + //add at the end of the classpath + //the system classpath in order to find the tools.jar file + execTask.setClasspath( classpath.concatSystemClasspath() ); + + execTask.setFork( true ); + execTask.createArg().setValue( "generateclient" ); + if( debug ) + { + execTask.createArg().setValue( "-trace" ); + }// end of if () + + // + execTask.createArg().setValue( "-short" ); + execTask.createArg().setValue( "-jarfile" ); + // ejb jar file + execTask.createArg().setValue( ejbjarfile.getAbsolutePath() ); + //client jar file + execTask.createArg().setValue( "-single" ); + execTask.createArg().setValue( "-clientjarfile" ); + execTask.createArg().setValue( clientjarfile.getAbsolutePath() ); + + log( "Calling EJBUtilities", Project.MSG_VERBOSE ); + execTask.execute(); + + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling generateclient Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java new file mode 100644 index 000000000..25218e180 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * Build a serialised deployment descriptor given a text file description of the + * descriptor in the format supported by WebLogic. This ant task is a front end + * for the weblogic DDCreator tool. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class DDCreator extends MatchingTask +{ + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes necessary fro DDCreator and the implementation + * classes of the home and remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the textual deployment + * desciptors. The actual deployment descriptor files are selected using + * include and exclude constructs on the EJBC task, as supported by the + * MatchingTask superclass. + */ + private File descriptorDirectory; + + /** + * The directory where generated serialised deployment descriptors are + * placed. + */ + private File generatedFilesDirectory; + + /** + * Set the classpath to be used for this compilation. + * + * @param s the classpath to use for the ddcreator tool. + */ + public void setClasspath( String s ) + { + this.classpath = project.translatePath( s ); + } + + /** + * Set the directory from where the text descriptions of the deployment + * descriptors are to be read. + * + * @param dirName the name of the directory containing the text deployment + * descriptor files. + */ + public void setDescriptors( String dirName ) + { + descriptorDirectory = new File( dirName ); + } + + /** + * Set the directory into which the serialised deployment descriptors are to + * be written. + * + * @param dirName the name of the directory into which the serialised + * deployment descriptors are written. + */ + public void setDest( String dirName ) + { + generatedFilesDirectory = new File( dirName ); + } + + /** + * Do the work. The work is actually done by creating a helper task. This + * approach allows the classpath of the helper task to be set. Since the + * weblogic tools require the class files of the project's home and remote + * interfaces to be available in the classpath, this also avoids having to + * start ant with the class path of the project it is building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( descriptorDirectory == null || + !descriptorDirectory.isDirectory() ) + { + throw new BuildException( "descriptors directory " + descriptorDirectory.getPath() + + " is not valid" ); + } + if( generatedFilesDirectory == null || + !generatedFilesDirectory.isDirectory() ) + { + throw new BuildException( "dest directory " + generatedFilesDirectory.getPath() + + " is not valid" ); + } + + String args = descriptorDirectory + " " + generatedFilesDirectory; + + // get all the files in the descriptor directory + DirectoryScanner ds = super.getDirectoryScanner( descriptorDirectory ); + + String[] files = ds.getIncludedFiles(); + + for( int i = 0; i < files.length; ++i ) + { + args += " " + files[i]; + } + + String systemClassPath = System.getProperty( "java.class.path" ); + String execClassPath = project.translatePath( systemClassPath + ":" + classpath ); + Java ddCreatorTask = ( Java )project.createTask( "java" ); + ddCreatorTask.setTaskName( getTaskName() ); + ddCreatorTask.setFork( true ); + ddCreatorTask.setClassname( "org.apache.tools.ant.taskdefs.optional.ejb.DDCreatorHelper" ); + Commandline.Argument arguments = ddCreatorTask.createArg(); + arguments.setLine( args ); + ddCreatorTask.setClasspath( new Path( project, execClassPath ) ); + if( ddCreatorTask.executeJava() != 0 ) + { + throw new BuildException( "Execution of ddcreator helper failed" ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java new file mode 100644 index 000000000..f59a3288c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import javax.ejb.deployment.DeploymentDescriptor; + +/** + * A helper class which performs the actual work of the ddcreator task. This + * class is run with a classpath which includes the weblogic tools and the home + * and remote interface class files referenced in the deployment descriptors + * being built. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class DDCreatorHelper +{ + + /** + * The descriptor text files for which a serialised descriptor is to be + * created. + */ + String[] descriptors; + /** + * The root directory of the tree containing the textual deployment + * desciptors. + */ + private File descriptorDirectory; + + /** + * The directory where generated serialised desployment descriptors are + * written. + */ + private File generatedFilesDirectory; + + /** + * Initialise the helper with the command arguments. + * + * @param args Description of Parameter + */ + private DDCreatorHelper( String[] args ) + { + int index = 0; + descriptorDirectory = new File( args[index++] ); + generatedFilesDirectory = new File( args[index++] ); + + descriptors = new String[args.length - index]; + for( int i = 0; index < args.length; ++i ) + { + descriptors[i] = args[index++]; + } + } + + /** + * The main method. The main method creates an instance of the + * DDCreatorHelper, passing it the args which it then processes. + * + * @param args The command line arguments + * @exception Exception Description of Exception + */ + public static void main( String[] args ) + throws Exception + { + DDCreatorHelper helper = new DDCreatorHelper( args ); + helper.process(); + } + + /** + * Do the actual work. The work proceeds by examining each descriptor given. + * If the serialised file does not exist or is older than the text + * description, the weblogic DDCreator tool is invoked directly to build the + * serialised descriptor. + * + * @exception Exception Description of Exception + */ + private void process() + throws Exception + { + for( int i = 0; i < descriptors.length; ++i ) + { + String descriptorName = descriptors[i]; + File descriptorFile = new File( descriptorDirectory, descriptorName ); + + int extIndex = descriptorName.lastIndexOf( "." ); + String serName = null; + if( extIndex != -1 ) + { + serName = descriptorName.substring( 0, extIndex ) + ".ser"; + } + else + { + serName = descriptorName + ".ser"; + } + File serFile = new File( generatedFilesDirectory, serName ); + + // do we need to regenerate the file + if( !serFile.exists() || serFile.lastModified() < descriptorFile.lastModified() + || regenerateSerializedFile( serFile ) ) + { + + String[] args = {"-noexit", + "-d", serFile.getParent(), + "-outputfile", serFile.getName(), + descriptorFile.getPath()}; + try + { + weblogic.ejb.utils.DDCreator.main( args ); + } + catch( Exception e ) + { + // there was an exception - run with no exit to get proper error + String[] newArgs = {"-d", generatedFilesDirectory.getPath(), + "-outputfile", serFile.getName(), + descriptorFile.getPath()}; + weblogic.ejb.utils.DDCreator.main( newArgs ); + } + } + } + } + + /** + * EJBC will fail if the serialized descriptor file does not match the bean + * classes. You can test for this by trying to load the deployment + * descriptor. If it fails, the serialized file needs to be regenerated + * because the associated class files don't match. + * + * @param serFile Description of Parameter + * @return Description of the Returned Value + */ + private boolean regenerateSerializedFile( File serFile ) + { + try + { + + FileInputStream fis = new FileInputStream( serFile ); + ObjectInputStream ois = new ObjectInputStream( fis ); + DeploymentDescriptor dd = ( DeploymentDescriptor )ois.readObject(); + fis.close(); + + // Since the descriptor read properly, everything should be o.k. + return false; + } + catch( Exception e ) + { + + // Weblogic will throw an error if the deployment descriptor does + // not match the class files. + return true; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java new file mode 100644 index 000000000..4f0a226cc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Hashtable; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.xml.sax.AttributeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Inner class used by EjbJar to facilitate the parsing of deployment + * descriptors and the capture of appropriate information. Extends HandlerBase + * so it only implements the methods needed. During parsing creates a hashtable + * consisting of entries mapping the name it should be inserted into an EJB jar + * as to a File representing the file on disk. This list can then be accessed + * through the getFiles() method. + * + * @author RT + */ +public class DescriptorHandler extends org.xml.sax.HandlerBase +{ + private final static int STATE_LOOKING_EJBJAR = 1; + private final static int STATE_IN_EJBJAR = 2; + private final static int STATE_IN_BEANS = 3; + private final static int STATE_IN_SESSION = 4; + private final static int STATE_IN_ENTITY = 5; + private final static int STATE_IN_MESSAGE = 6; + + /** + * Bunch of constants used for storing entries in a hashtable, and for + * constructing the filenames of various parts of the ejb jar. + */ + private final static String EJB_REF = "ejb-ref"; + private final static String HOME_INTERFACE = "home"; + private final static String REMOTE_INTERFACE = "remote"; + private final static String LOCAL_HOME_INTERFACE = "local-home"; + private final static String LOCAL_INTERFACE = "local"; + private final static String BEAN_CLASS = "ejb-class"; + private final static String PK_CLASS = "prim-key-class"; + private final static String EJB_NAME = "ejb-name"; + private final static String EJB_JAR = "ejb-jar"; + private final static String ENTERPRISE_BEANS = "enterprise-beans"; + private final static String ENTITY_BEAN = "entity"; + private final static String SESSION_BEAN = "session"; + private final static String MESSAGE_BEAN = "message-driven"; + + private String publicId = null; + + /** + * The state of the parsing + */ + private int parseState = STATE_LOOKING_EJBJAR; + + /** + * Instance variable used to store the name of the current element being + * processed by the SAX parser. Accessed by the SAX parser call-back methods + * startElement() and endElement(). + */ + protected String currentElement = null; + + /** + * The text of the current element + */ + protected String currentText = null; + + /** + * Instance variable that stores the names of the files as they will be put + * into the jar file, mapped to File objects Accessed by the SAX parser + * call-back method characters(). + */ + protected Hashtable ejbFiles = null; + + /** + * Instance variable that stores the value found in the <ejb-name> + * element + */ + protected String ejbName = null; + + private Hashtable fileDTDs = new Hashtable(); + + private Hashtable resourceDTDs = new Hashtable(); + + private boolean inEJBRef = false; + + private Hashtable urlDTDs = new Hashtable(); + + private Task owningTask; + + /** + * The directory containing the bean classes and interfaces. This is used + * for performing dependency file lookups. + */ + private File srcDir; + + public DescriptorHandler( Task task, File srcDir ) + { + this.owningTask = task; + this.srcDir = srcDir; + } + + /** + * Getter method that returns the value of the <ejb-name> element. + * + * @return The EjbName value + */ + public String getEjbName() + { + return ejbName; + } + + /** + * Getter method that returns the set of files to include in the EJB jar. + * + * @return The Files value + */ + public Hashtable getFiles() + { + return ( ejbFiles == null ) ? new Hashtable() : ejbFiles; + } + + /** + * Get the publicId of the DTD + * + * @return The PublicId value + */ + public String getPublicId() + { + return publicId; + } + + /** + * SAX parser call-back method invoked whenever characters are located + * within an element. currentAttribute (modified by startElement and + * endElement) tells us whether we are in an interesting element (one of the + * up to four classes of an EJB). If so then converts the classname from the + * format org.apache.tools.ant.Parser to the convention for storing such a + * class, org/apache/tools/ant/Parser.class. This is then resolved into a + * file object under the srcdir which is stored in a Hashtable. + * + * @param ch A character array containing all the characters in the element, + * and maybe others that should be ignored. + * @param start An integer marking the position in the char array to start + * reading from. + * @param length An integer representing an offset into the char array where + * the current data terminates. + * @exception SAXException Description of Exception + */ + public void characters( char[] ch, int start, int length ) + throws SAXException + { + + currentText += new String( ch, start, length ); + } + + + /** + * SAX parser call-back method that is invoked when an element is exited. + * Used to blank out (set to the empty string, not nullify) the name of the + * currentAttribute. A better method would be to use a stack as an instance + * variable, however since we are only interested in leaf-node data this is + * a simpler and workable solution. + * + * @param name The name of the attribute being exited. Ignored in this + * implementation. + * @exception SAXException Description of Exception + */ + public void endElement( String name ) + throws SAXException + { + processElement(); + currentText = ""; + this.currentElement = ""; + if( name.equals( EJB_REF ) ) + { + inEJBRef = false; + } + else if( parseState == STATE_IN_ENTITY && name.equals( ENTITY_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_SESSION && name.equals( SESSION_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_MESSAGE && name.equals( MESSAGE_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_BEANS && name.equals( ENTERPRISE_BEANS ) ) + { + parseState = STATE_IN_EJBJAR; + } + else if( parseState == STATE_IN_EJBJAR && name.equals( EJB_JAR ) ) + { + parseState = STATE_LOOKING_EJBJAR; + } + } + + public void registerDTD( String publicId, String location ) + { + if( location == null ) + { + return; + } + + File fileDTD = new File( location ); + if( fileDTD.exists() ) + { + if( publicId != null ) + { + fileDTDs.put( publicId, fileDTD ); + owningTask.log( "Mapped publicId " + publicId + " to file " + fileDTD, Project.MSG_VERBOSE ); + } + return; + } + + if( getClass().getResource( location ) != null ) + { + if( publicId != null ) + { + resourceDTDs.put( publicId, location ); + owningTask.log( "Mapped publicId " + publicId + " to resource " + location, Project.MSG_VERBOSE ); + } + } + + try + { + if( publicId != null ) + { + URL urldtd = new URL( location ); + urlDTDs.put( publicId, urldtd ); + } + } + catch( java.net.MalformedURLException e ) + { + //ignored + } + + } + + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + this.publicId = publicId; + + File dtdFile = ( File )fileDTDs.get( publicId ); + if( dtdFile != null ) + { + try + { + owningTask.log( "Resolved " + publicId + " to local file " + dtdFile, Project.MSG_VERBOSE ); + return new InputSource( new FileInputStream( dtdFile ) ); + } + catch( FileNotFoundException ex ) + { + // ignore + } + } + + String dtdResourceName = ( String )resourceDTDs.get( publicId ); + if( dtdResourceName != null ) + { + InputStream is = this.getClass().getResourceAsStream( dtdResourceName ); + if( is != null ) + { + owningTask.log( "Resolved " + publicId + " to local resource " + dtdResourceName, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + } + + URL dtdUrl = ( URL )urlDTDs.get( publicId ); + if( dtdUrl != null ) + { + try + { + InputStream is = dtdUrl.openStream(); + owningTask.log( "Resolved " + publicId + " to url " + dtdUrl, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + catch( IOException ioe ) + { + //ignore + } + } + + owningTask.log( "Could not resolve ( publicId: " + publicId + ", systemId: " + systemId + ") to a local entity", + Project.MSG_INFO ); + + return null; + } + + /** + * SAX parser call-back method that is used to initialize the values of some + * instance variables to ensure safe operation. + * + * @exception SAXException Description of Exception + */ + public void startDocument() + throws SAXException + { + this.ejbFiles = new Hashtable( 10, 1 ); + this.currentElement = null; + inEJBRef = false; + } + + + /** + * SAX parser call-back method that is invoked when a new element is entered + * into. Used to store the context (attribute name) in the currentAttribute + * instance variable. + * + * @param name The name of the element being entered. + * @param attrs Attributes associated to the element. + * @exception SAXException Description of Exception + */ + public void startElement( String name, AttributeList attrs ) + throws SAXException + { + this.currentElement = name; + currentText = ""; + if( name.equals( EJB_REF ) ) + { + inEJBRef = true; + } + else if( parseState == STATE_LOOKING_EJBJAR && name.equals( EJB_JAR ) ) + { + parseState = STATE_IN_EJBJAR; + } + else if( parseState == STATE_IN_EJBJAR && name.equals( ENTERPRISE_BEANS ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_BEANS && name.equals( SESSION_BEAN ) ) + { + parseState = STATE_IN_SESSION; + } + else if( parseState == STATE_IN_BEANS && name.equals( ENTITY_BEAN ) ) + { + parseState = STATE_IN_ENTITY; + } + else if( parseState == STATE_IN_BEANS && name.equals( MESSAGE_BEAN ) ) + { + parseState = STATE_IN_MESSAGE; + } + } + + + protected void processElement() + { + if( inEJBRef || + ( parseState != STATE_IN_ENTITY && parseState != STATE_IN_SESSION && parseState != STATE_IN_MESSAGE ) ) + { + return; + } + + if( currentElement.equals( HOME_INTERFACE ) || + currentElement.equals( REMOTE_INTERFACE ) || + currentElement.equals( LOCAL_INTERFACE ) || + currentElement.equals( LOCAL_HOME_INTERFACE ) || + currentElement.equals( BEAN_CLASS ) || + currentElement.equals( PK_CLASS ) ) + { + + // Get the filename into a String object + File classFile = null; + String className = currentText.trim(); + + // If it's a primitive wrapper then we shouldn't try and put + // it into the jar, so ignore it. + if( !className.startsWith( "java." ) && + !className.startsWith( "javax." ) ) + { + // Translate periods into path separators, add .class to the + // name, create the File object and add it to the Hashtable. + className = className.replace( '.', File.separatorChar ); + className += ".class"; + classFile = new File( srcDir, className ); + ejbFiles.put( className, classFile ); + } + } + + // Get the value of the tag. Only the first occurence. + if( currentElement.equals( EJB_NAME ) ) + { + if( ejbName == null ) + { + ejbName = currentText.trim(); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java new file mode 100644 index 000000000..147faed55 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import javax.xml.parsers.SAXParser; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +public interface EJBDeploymentTool +{ + /** + * Process a deployment descriptor, generating the necessary vendor specific + * deployment files. + * + * @param descriptorFilename the name of the deployment descriptor + * @param saxParser a SAX parser which can be used to parse the deployment + * descriptor. + * @exception BuildException Description of Exception + */ + void processDescriptor( String descriptorFilename, SAXParser saxParser ) + throws BuildException; + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + void validateConfigured() + throws BuildException; + + /** + * Set the task which owns this tool + * + * @param task The new Task value + */ + void setTask( Task task ); + + /** + * Configure this tool for use in the ejbjar task. + * + * @param config Description of Parameter + */ + void configure( EjbJar.Config config ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java new file mode 100644 index 000000000..df7aa90bb --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java @@ -0,0 +1,567 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb;// Standard java imports +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.xml.parsers.ParserConfigurationException;// XML imports +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory;// Apache/Ant imports +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.xml.sax.SAXException; + +/** + *

      + * + * Provides automated ejb jar file creation for ant. Extends the MatchingTask + * class provided in the default ant distribution to provide a directory + * scanning EJB jarfile generator.

      + * + * The task works by taking the deployment descriptors one at a time and parsing + * them to locate the names of the classes which should be placed in the jar. + * The classnames are translated to java.io.Files by replacing periods with + * File.separatorChar and resolving the generated filename as a relative path + * under the srcDir attribute. All necessary files are then assembled into a + * jarfile. One jarfile is constructed for each deployment descriptor found. + *

      + * + * Functionality is currently provided for standard EJB1.1 jars and Weblogic 5.1 + * jars. The weblogic deployment descriptors, used in constructing the Weblogic + * jar, are located based on a simple naming convention. The name of the + * standard deployment descriptor is taken upto the first instance of a String, + * specified by the attribute baseNameTerminator, and then the regular Weblogic + * descriptor name is appended. For example if baseNameTerminator is set to '-', + * its default value, and a standard descriptor is called Foo-ejb-jar.xml then + * the files Foo-weblogic-ejb-jar.xml and Foo-weblogic-cmp-rdbms-jar.xml will be + * looked for, and if found, included in the jarfile.

      + * + * Attributes and setter methods are provided to support optional generation of + * Weblogic5.1 jars, optional deletion of generic jar files, setting alternate + * values for baseNameTerminator, and setting the strings to append to the names + * of the generated jarfiles.

      + * + * @author Tim Fennell + */ +public class EjbJar extends MatchingTask +{ + + private Config config = new Config(); + + /** + * Instance variable that stores the suffix for the generated jarfile. + */ + private String genericJarSuffix = "-generic.jar"; + + /** + * The list of deployment tools we are going to run. + */ + private ArrayList deploymentTools = new ArrayList(); + + /** + * Stores a handle to the directory to put the Jar files in. This is only + * used by the generic deployment descriptor tool which is created if no + * other deployment descriptor tools are provided. Normally each deployment + * tool will specify the desitination dir itself. + */ + private File destDir; + + /** + * Set the base name of the EJB jar that is to be created if it is not to be + * determined from the name of the deployment descriptor files. + * + * @param inValue the basename that will be used when writing the jar file + * containing the EJB + */ + public void setBasejarname( String inValue ) + { + config.baseJarName = inValue; + if( config.namingScheme == null ) + { + config.namingScheme = new NamingScheme(); + config.namingScheme.setValue( NamingScheme.BASEJARNAME ); + } + else if( !config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) ) + { + throw new BuildException( "The basejarname attribute is not compatible with the " + + config.namingScheme.getValue() + " naming scheme" ); + } + } + + /** + * Set the baseNameTerminator. The basename terminator is the string which + * terminates the bean name. The convention used by this task is that bean + * descriptors are named as the BeanName with some suffix. The + * baseNameTerminator string separates the bean name and the suffix and is + * used to determine the bean name. + * + * @param inValue a string which marks the end of the basename. + */ + public void setBasenameterminator( String inValue ) + { + config.baseNameTerminator = inValue; + } + + /** + * Set the classpath to use when resolving classes for inclusion in the jar. + * + * @param classpath the classpath to use. + */ + public void setClasspath( Path classpath ) + { + config.classpath = classpath; + } + + /** + * Set the descriptor directory. The descriptor directory contains the EJB + * deployment descriptors. These are XML files that declare the properties + * of a bean in a particular deployment scenario. Such properties include, + * for example, the transactional nature of the bean and the security access + * control to the bean's methods. + * + * @param inDir the directory containing the deployment descriptors. + */ + public void setDescriptordir( File inDir ) + { + config.descriptorDir = inDir; + } + + + /** + * Set the destination directory. The EJB jar files will be written into + * this directory. The jar files that exist in this directory are also used + * when determining if the contents of the jar file have changed. Note that + * this parameter is only used if no deployment tools are specified. + * Typically each deployment tool will specify its own destination + * directory. + * + * @param inDir The new Destdir value + */ + public void setDestdir( File inDir ) + { + this.destDir = inDir; + } + + /** + * Set the flat dest dir flag. This flag controls whether the destination + * jars are written out in the destination directory with the same + * hierarchal structure from which the deployment descriptors have been + * read. If this is set to true the generated EJB jars are written into the + * root of the destination directory, otherwise they are written out in the + * same relative position as the deployment descriptors in the descriptor + * directory. + * + * @param inValue the new value of the flatdestdir flag. + */ + public void setFlatdestdir( boolean inValue ) + { + config.flatDestDir = inValue; + } + + /** + * Set the suffix for the generated jar file. When generic jars are + * generated, they have a suffix which is appended to the the bean name to + * create the name of the jar file. Note that this suffix includes the + * extension fo te jar file and should therefore end with an appropriate + * extension such as .jar or .ear + * + * @param inString the string to use as the suffix. + */ + public void setGenericjarsuffix( String inString ) + { + this.genericJarSuffix = inString; + } + + + /** + * Set the Manifest file to use when jarring. As of EJB 1.1, manifest files + * are no longer used to configure the EJB. However, they still have a vital + * importance if the EJB is intended to be packaged in an EAR file. By + * adding "Class-Path" settings to a Manifest file, the EJB can look for + * classes inside the EAR file itself, allowing for easier deployment. This + * is outlined in the J2EE specification, and all J2EE components are meant + * to support it. + * + * @param manifest The new Manifest value + */ + public void setManifest( File manifest ) + { + config.manifest = manifest; + } + + /** + * Set the naming scheme used to determine the name of the generated jars + * from the deployment descriptor + * + * @param namingScheme The new Naming value + */ + public void setNaming( NamingScheme namingScheme ) + { + config.namingScheme = namingScheme; + if( !config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) && + config.baseJarName != null ) + { + throw new BuildException( "The basejarname attribute is not compatible with the " + + config.namingScheme.getValue() + " naming scheme" ); + } + } + + /** + * Set the srcdir attribute. The source directory is the directory that + * contains the classes that will be added to the EJB jar. Typically this + * will include the home and remote interfaces and the bean class. + * + * @param inDir the source directory. + */ + public void setSrcdir( File inDir ) + { + config.srcDir = inDir; + } + + /** + * Create a Borland nested element used to configure a deployment tool for + * Borland server. + * + * @return the deployment tool instance to be configured. + */ + public BorlandDeploymentTool createBorland() + { + log( "Borland deployment tools", Project.MSG_VERBOSE ); + + BorlandDeploymentTool tool = new BorlandDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * creates a nested classpath element. This classpath is used to locate the + * super classes and interfaces of the classes that will make up the EJB + * jar. + * + * @return the path to be configured. + */ + public Path createClasspath() + { + if( config.classpath == null ) + { + config.classpath = new Path( project ); + } + return config.classpath.createPath(); + } + + /** + * Create a DTD location record. This stores the location of a DTD. The DTD + * is identified by its public Id. The location may either be a file + * location or a resource location. + * + * @return Description of the Returned Value + */ + public DTDLocation createDTD() + { + DTDLocation dtdLocation = new DTDLocation(); + config.dtdLocations.add( dtdLocation ); + + return dtdLocation; + } + + /** + * Create a nested element used to configure a deployment tool for iPlanet + * Application Server. + * + * @return the deployment tool instance to be configured. + */ + public IPlanetDeploymentTool createIplanet() + { + log( "iPlanet Application Server deployment tools", Project.MSG_VERBOSE ); + + IPlanetDeploymentTool tool = new IPlanetDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a jboss nested element used to configure a deployment tool for + * Jboss server. + * + * @return the deployment tool instance to be configured. + */ + public JbossDeploymentTool createJboss() + { + JbossDeploymentTool tool = new JbossDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a file set for support elements + * + * @return a fileset which can be populated with support files. + */ + public FileSet createSupport() + { + FileSet supportFileSet = new FileSet(); + config.supportFileSets.add( supportFileSet ); + return supportFileSet; + } + + /** + * Create a weblogic nested element used to configure a deployment tool for + * Weblogic server. + * + * @return the deployment tool instance to be configured. + */ + public WeblogicDeploymentTool createWeblogic() + { + WeblogicDeploymentTool tool = new WeblogicDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a nested element for weblogic when using the Toplink Object- + * Relational mapping. + * + * @return the deployment tool instance to be configured. + */ + public WeblogicTOPLinkDeploymentTool createWeblogictoplink() + { + WeblogicTOPLinkDeploymentTool tool = new WeblogicTOPLinkDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a websphere nested element used to configure a deployment tool for + * Websphere 4.0 server. + * + * @return the deployment tool instance to be configured. + */ + public WebsphereDeploymentTool createWebsphere() + { + WebsphereDeploymentTool tool = new WebsphereDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Invoked by Ant after the task is prepared, when it is ready to execute + * this task. This will configure all of the nested deployment tools to + * allow them to process the jar. If no deployment tools have been + * configured a generic tool is created to handle the jar. A parser is + * configured and then each descriptor found is passed to all the deployment + * tool elements for processing. + * + * @exception BuildException thrown whenever a problem is encountered that + * cannot be recovered from, to signal to ant that a major problem + * occurred within this task. + */ + public void execute() + throws BuildException + { + validateConfig(); + + if( deploymentTools.size() == 0 ) + { + GenericDeploymentTool genericTool = new GenericDeploymentTool(); + genericTool.setTask( this ); + genericTool.setDestdir( destDir ); + genericTool.setGenericJarSuffix( genericJarSuffix ); + deploymentTools.add( genericTool ); + } + + for( Iterator i = deploymentTools.iterator(); i.hasNext(); ) + { + EJBDeploymentTool tool = ( EJBDeploymentTool )i.next(); + tool.configure( config ); + tool.validateConfigured(); + } + + try + { + // Create the parser using whatever parser the system dictates + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + SAXParser saxParser = saxParserFactory.newSAXParser(); + + DirectoryScanner ds = getDirectoryScanner( config.descriptorDir ); + ds.scan(); + String[] files = ds.getIncludedFiles(); + + log( files.length + " deployment descriptors located.", + Project.MSG_VERBOSE ); + + // Loop through the files. Each file represents one deployment + // descriptor, and hence one bean in our model. + for( int index = 0; index < files.length; ++index ) + { + // process the deployment descriptor in each tool + for( Iterator i = deploymentTools.iterator(); i.hasNext(); ) + { + EJBDeploymentTool tool = ( EJBDeploymentTool )i.next(); + tool.processDescriptor( files[index], saxParser ); + } + } + } + catch( SAXException se ) + { + String msg = "SAXException while creating parser." + + " Details: " + + se.getMessage(); + throw new BuildException( msg, se ); + } + catch( ParserConfigurationException pce ) + { + String msg = "ParserConfigurationException while creating parser. " + + "Details: " + pce.getMessage(); + throw new BuildException( msg, pce ); + } + } + + private void validateConfig() + { + if( config.srcDir == null ) + { + throw new BuildException( "The srcDir attribute must be specified" ); + } + + if( config.descriptorDir == null ) + { + config.descriptorDir = config.srcDir; + } + + if( config.namingScheme == null ) + { + config.namingScheme = new NamingScheme(); + config.namingScheme.setValue( NamingScheme.DESCRIPTOR ); + } + else if( config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) && + config.baseJarName == null ) + { + throw new BuildException( "The basejarname attribute must be specified " + + "with the basejarname naming scheme" ); + } + } + + public static class DTDLocation + { + private String publicId = null; + private String location = null; + + public void setLocation( String location ) + { + this.location = location; + } + + public void setPublicId( String publicId ) + { + this.publicId = publicId; + } + + public String getLocation() + { + return location; + } + + public String getPublicId() + { + return publicId; + } + } + + public static class NamingScheme extends EnumeratedAttribute + { + public final static String EJB_NAME = "ejb-name"; + public final static String DIRECTORY = "directory"; + public final static String DESCRIPTOR = "descriptor"; + public final static String BASEJARNAME = "basejarname"; + + public String[] getValues() + { + return new String[]{EJB_NAME, DIRECTORY, DESCRIPTOR, BASEJARNAME}; + } + } + + /** + * A class which contains the configuration state of the ejbjar task. This + * state is passed to the deployment tools for configuration + * + * @author RT + */ + static class Config + { + + /** + * Instance variable that marks the end of the 'basename' + */ + public String baseNameTerminator = "-"; + + /** + * Instance variable that determines whether to use a package structure + * of a flat directory as the destination for the jar files. + */ + public boolean flatDestDir = false; + + /** + * A Fileset of support classes + */ + public List supportFileSets = new ArrayList(); + + /** + * The list of configured DTD locations + */ + public ArrayList dtdLocations = new ArrayList(); + + /** + * Stores a handle to the destination EJB Jar file + */ + public String baseJarName; + + /** + * The classpath to use when loading classes + */ + public Path classpath; + + /** + * Stores a handle to the directory under which to search for deployment + * descriptors + */ + public File descriptorDir; + + /** + * The Manifest file + */ + public File manifest; + + /** + * The naming scheme used to determine the generated jar name from the + * descriptor information + */ + public NamingScheme namingScheme; + /** + * Stores a handle to the directory under which to search for class + * files + */ + public File srcDir; + }// end of execute() +} + + + + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java new file mode 100644 index 000000000..0041c3756 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * Build EJB support classes using Weblogic's ejbc tool from a directory + * containing a set of deployment descriptors. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class Ejbc extends MatchingTask +{ + + public boolean keepgenerated; + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the serialised deployment + * desciptors. The actual deployment descriptor files are selected using + * include and exclude constructs on the ejbc task provided by the + * MatchingTask superclass. + */ + private File descriptorDirectory; + + /** + * The directory where generated files are placed. + */ + private File generatedFilesDirectory; + + /** + * The name of the manifest file generated for the EJB jar. + */ + private File generatedManifestFile; + + /** + * The source directory for the home and remote interfaces. This is used to + * determine if the generated deployment classes are out of date. + */ + private File sourceDirectory; + + /** + * Set the classpath to be used for this compilation. + * + * @param s The new Classpath value + */ + public void setClasspath( String s ) + { + this.classpath = project.translatePath( s ); + } + + /** + * Set the directory from where the serialised deployment descriptors are to + * be read. + * + * @param dirName the name of the directory containing the serialised + * deployment descriptors. + */ + public void setDescriptors( String dirName ) + { + descriptorDirectory = new File( dirName ); + } + + /** + * Set the directory into which the support classes, RMI stubs, etc are to + * be written + * + * @param dirName the name of the directory into which code is generated + */ + public void setDest( String dirName ) + { + generatedFilesDirectory = new File( dirName ); + } + + public void setKeepgenerated( String newKeepgenerated ) + { + keepgenerated = Boolean.valueOf( newKeepgenerated.trim() ).booleanValue(); + + } + + /** + * Set the generated manifest file. For each EJB that is processed an entry + * is created in this file. This can then be used to create a jar file for + * dploying the beans. + * + * @param manifestFilename The new Manifest value + */ + public void setManifest( String manifestFilename ) + { + generatedManifestFile = new File( manifestFilename ); + } + + /** + * Set the directory containing the source code for the home interface, + * remote interface and public key class definitions. + * + * @param dirName the directory containg the source tree for the EJB's + * interface classes. + */ + public void setSrc( String dirName ) + { + sourceDirectory = new File( dirName ); + } + + public boolean getKeepgenerated() + { + return keepgenerated; + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a helper task. This approach allows the classpath of the helper task to + * be set. Since the weblogic tools require the class files of the project's + * home and remote interfaces to be available in the classpath, this also + * avoids having to start ant with the class path of the project it is + * building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( descriptorDirectory == null || + !descriptorDirectory.isDirectory() ) + { + throw new BuildException( "descriptors directory " + descriptorDirectory.getPath() + + " is not valid" ); + } + if( generatedFilesDirectory == null || + !generatedFilesDirectory.isDirectory() ) + { + throw new BuildException( "dest directory " + generatedFilesDirectory.getPath() + + " is not valid" ); + } + + if( sourceDirectory == null || + !sourceDirectory.isDirectory() ) + { + throw new BuildException( "src directory " + sourceDirectory.getPath() + + " is not valid" ); + } + + String systemClassPath = System.getProperty( "java.class.path" ); + String execClassPath = project.translatePath( systemClassPath + ":" + classpath + + ":" + generatedFilesDirectory ); + // get all the files in the descriptor directory + DirectoryScanner ds = super.getDirectoryScanner( descriptorDirectory ); + + String[] files = ds.getIncludedFiles(); + + Java helperTask = ( Java )project.createTask( "java" ); + helperTask.setTaskName( getTaskName() ); + helperTask.setFork( true ); + helperTask.setClassname( "org.apache.tools.ant.taskdefs.optional.ejb.EjbcHelper" ); + String args = ""; + args += " " + descriptorDirectory; + args += " " + generatedFilesDirectory; + args += " " + sourceDirectory; + args += " " + generatedManifestFile; + args += " " + keepgenerated; + + for( int i = 0; i < files.length; ++i ) + { + args += " " + files[i]; + } + + Commandline.Argument arguments = helperTask.createArg(); + arguments.setLine( args ); + helperTask.setClasspath( new Path( project, execClassPath ) ); + if( helperTask.executeJava() != 0 ) + { + throw new BuildException( "Execution of ejbc helper failed" ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java new file mode 100644 index 000000000..73b2ed414 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.PrintWriter; +import java.util.Vector; +import javax.ejb.deployment.DeploymentDescriptor; +import javax.ejb.deployment.EntityDescriptor; + + +/** + * A helper class which performs the actual work of the ejbc task. This class is + * run with a classpath which includes the weblogic tools and the home and + * remote interface class files referenced in the deployment descriptors being + * processed. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class EjbcHelper +{ + + /** + * The names of the serialised deployment descriptors + */ + String[] descriptors; + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the serialised deployment + * desciptors. + */ + private File descriptorDirectory; + + /** + * The directory where generated files are placed. + */ + private File generatedFilesDirectory; + + private boolean keepGenerated; + + /** + * The name of the manifest file generated for the EJB jar. + */ + private File manifestFile; + + /** + * The source directory for the home and remote interfaces. This is used to + * determine if the generated deployment classes are out of date. + */ + private File sourceDirectory; + + /** + * Initialise the EjbcHelper by reading the command arguments. + * + * @param args Description of Parameter + */ + private EjbcHelper( String[] args ) + { + int index = 0; + descriptorDirectory = new File( args[index++] ); + generatedFilesDirectory = new File( args[index++] ); + sourceDirectory = new File( args[index++] ); + manifestFile = new File( args[index++] ); + keepGenerated = Boolean.valueOf( args[index++] ).booleanValue(); + + descriptors = new String[args.length - index]; + for( int i = 0; index < args.length; ++i ) + { + descriptors[i] = args[index++]; + } + } + + /** + * Command line interface for the ejbc helper task. + * + * @param args The command line arguments + * @exception Exception Description of Exception + */ + public static void main( String[] args ) + throws Exception + { + EjbcHelper helper = new EjbcHelper( args ); + helper.process(); + } + + private String[] getCommandLine( boolean debug, File descriptorFile ) + { + Vector v = new Vector(); + if( !debug ) + { + v.addElement( "-noexit" ); + } + if( keepGenerated ) + { + v.addElement( "-keepgenerated" ); + } + v.addElement( "-d" ); + v.addElement( generatedFilesDirectory.getPath() ); + v.addElement( descriptorFile.getPath() ); + + String[] args = new String[v.size()]; + v.copyInto( args ); + return args; + } + + /** + * Determine if the weblogic EJB support classes need to be regenerated for + * a given deployment descriptor. This process attempts to determine if the + * support classes need to be rebuilt. It does this by examining only some + * of the support classes which are typically generated. If the ejbc task is + * interrupted generating the support classes for a bean, all of the support + * classes should be removed to force regeneration of the support classes. + * + * @param descriptorFile the serialised deployment descriptor + * @return true if the support classes need to be regenerated. + * @throws IOException if the descriptor file cannot be closed. + */ + private boolean isRegenRequired( File descriptorFile ) + throws IOException + { + // read in the descriptor. Under weblogic, the descriptor is a weblogic + // specific subclass which has references to the implementation classes. + // These classes must, therefore, be in the classpath when the deployment + // descriptor is loaded from the .ser file + FileInputStream fis = null; + try + { + fis = new FileInputStream( descriptorFile ); + ObjectInputStream ois = new ObjectInputStream( fis ); + DeploymentDescriptor dd = ( DeploymentDescriptor )ois.readObject(); + fis.close(); + + String homeInterfacePath = dd.getHomeInterfaceClassName().replace( '.', '/' ) + ".java"; + String remoteInterfacePath = dd.getRemoteInterfaceClassName().replace( '.', '/' ) + ".java"; + String primaryKeyClassPath = null; + if( dd instanceof EntityDescriptor ) + { + primaryKeyClassPath = ( ( EntityDescriptor )dd ).getPrimaryKeyClassName().replace( '.', '/' ) + ".java"; + ; + } + + File homeInterfaceSource = new File( sourceDirectory, homeInterfacePath ); + File remoteInterfaceSource = new File( sourceDirectory, remoteInterfacePath ); + File primaryKeyClassSource = null; + if( primaryKeyClassPath != null ) + { + primaryKeyClassSource = new File( sourceDirectory, remoteInterfacePath ); + } + + // are any of the above out of date. + // we find the implementation classes and see if they are older than any + // of the above or the .ser file itself. + String beanClassBase = dd.getEnterpriseBeanClassName().replace( '.', '/' ); + File ejbImplentationClass + = new File( generatedFilesDirectory, beanClassBase + "EOImpl.class" ); + File homeImplementationClass + = new File( generatedFilesDirectory, beanClassBase + "HomeImpl.class" ); + File beanStubClass + = new File( generatedFilesDirectory, beanClassBase + "EOImpl_WLStub.class" ); + + // if the implementation classes don;t exist regenerate + if( !ejbImplentationClass.exists() || !homeImplementationClass.exists() || + !beanStubClass.exists() ) + { + return true; + } + + // Is the ser file or any of the source files newer then the class files. + // firstly find the oldest of the two class files. + long classModificationTime = ejbImplentationClass.lastModified(); + if( homeImplementationClass.lastModified() < classModificationTime ) + { + classModificationTime = homeImplementationClass.lastModified(); + } + if( beanStubClass.lastModified() < classModificationTime ) + { + classModificationTime = beanStubClass.lastModified(); + } + + if( descriptorFile.lastModified() > classModificationTime || + homeInterfaceSource.lastModified() > classModificationTime || + remoteInterfaceSource.lastModified() > classModificationTime ) + { + return true; + } + + if( primaryKeyClassSource != null && + primaryKeyClassSource.lastModified() > classModificationTime ) + { + return true; + } + } + catch( Throwable descriptorLoadException ) + { + System.out.println( "Exception occurred reading " + descriptorFile.getName() + " - continuing" ); + // any problems - just regenerate + return true; + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + + return false; + } + + /** + * Process the descriptors in turn generating support classes for each and a + * manifest file for all of the beans. + * + * @exception Exception Description of Exception + */ + private void process() + throws Exception + { + String manifest = "Manifest-Version: 1.0\n\n"; + for( int i = 0; i < descriptors.length; ++i ) + { + String descriptorName = descriptors[i]; + File descriptorFile = new File( descriptorDirectory, descriptorName ); + + if( isRegenRequired( descriptorFile ) ) + { + System.out.println( "Running ejbc for " + descriptorFile.getName() ); + regenerateSupportClasses( descriptorFile ); + } + else + { + System.out.println( descriptorFile.getName() + " is up to date" ); + } + manifest += "Name: " + descriptorName.replace( '\\', '/' ) + "\nEnterprise-Bean: True\n\n"; + } + + FileWriter fw = new FileWriter( manifestFile ); + PrintWriter pw = new PrintWriter( fw ); + pw.print( manifest ); + fw.flush(); + fw.close(); + } + + /** + * Perform the weblogic.ejbc call to regenerate the support classes. Note + * that this method relies on an undocumented -noexit option to the ejbc + * tool to stop the ejbc tool exiting the VM altogether. + * + * @param descriptorFile Description of Parameter + * @exception Exception Description of Exception + */ + private void regenerateSupportClasses( File descriptorFile ) + throws Exception + { + // create a Java task to do the rebuild + + + String[] args = getCommandLine( false, descriptorFile ); + + try + { + weblogic.ejbc.main( args ); + } + catch( Exception e ) + { + // run with no exit for better reporting + String[] newArgs = getCommandLine( true, descriptorFile ); + weblogic.ejbc.main( newArgs ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java new file mode 100644 index 000000000..29a49f9aa --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java @@ -0,0 +1,970 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import javax.xml.parsers.SAXParser; +import org.apache.bcel.*; +import org.apache.bcel.classfile.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.*; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.depend.Dependencies; +import org.apache.tools.ant.util.depend.Filter; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + + +/** + * A deployment tool which creates generic EJB jars. Generic jars contains only + * those classes and META-INF entries specified in the EJB 1.1 standard This + * class is also used as a framework for the creation of vendor specific + * deployment tools. A number of template methods are provided through which the + * vendor specific tool can hook into the EJB creation process. + * + * @author RT + */ +public class GenericDeploymentTool implements EJBDeploymentTool +{ + /** + * Private constants that are used when constructing the standard jarfile + */ + protected final static String META_DIR = "META-INF/"; + protected final static String EJB_DD = "ejb-jar.xml"; + + /** + * Instance variable that stores the suffix for the generated jarfile. + */ + private String genericJarSuffix = "-generic.jar"; + + /** + * The classloader generated from the given classpath to load the super + * classes and super interfaces. + */ + private ClassLoader classpathLoader = null; + + /** + * List of files have been loaded into the EJB jar + */ + private List addedfiles; + + /** + * The classpath to use with this deployment tool. This is appended to any + * paths from the ejbjar task itself. + */ + private Path classpath; + + /** + * The configuration from the containing task. This config combined with the + * settings of the individual attributes here constitues the complete config + * for this deployment tool. + */ + private EjbJar.Config config; + + /** + * Stores a handle to the directory to put the Jar files in + */ + private File destDir; + + /** + * Handler used to parse the EJB XML descriptor + */ + private DescriptorHandler handler; + + /** + * The task to which this tool belongs. This is used to access services + * provided by the ant core, such as logging. + */ + private Task task; + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + this.classpath = classpath; + } + + /** + * Setter used to store the value of destination directory prior to + * execute() being called. + * + * @param inDir the destination directory. + */ + public void setDestdir( File inDir ) + { + this.destDir = inDir; + } + + /** + * Setter used to store the suffix for the generated jar file. + * + * @param inString the string to use as the suffix. + */ + public void setGenericJarSuffix( String inString ) + { + this.genericJarSuffix = inString; + } + + + /** + * Set the task which owns this tool + * + * @param task The new Task value + */ + public void setTask( Task task ) + { + this.task = task; + } + + /** + * Get the prefix for vendor deployment descriptors. This will contain the + * path and the start of the descriptor name, depending on the naming scheme + * + * @param baseName Description of Parameter + * @param descriptorFileName Description of Parameter + * @return The VendorDDPrefix value + */ + public String getVendorDDPrefix( String baseName, String descriptorFileName ) + { + String ddPrefix = null; + + if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DESCRIPTOR ) ) + { + ddPrefix = baseName + config.baseNameTerminator; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.BASEJARNAME ) || + config.namingScheme.getValue().equals( EjbJar.NamingScheme.EJB_NAME ) || + config.namingScheme.getValue().equals( EjbJar.NamingScheme.DIRECTORY ) ) + { + String canonicalDescriptor = descriptorFileName.replace( '\\', '/' ); + int index = canonicalDescriptor.lastIndexOf( '/' ); + if( index == -1 ) + { + ddPrefix = ""; + } + else + { + ddPrefix = descriptorFileName.substring( 0, index + 1 ); + } + } + return ddPrefix; + } + + + /** + * Configure this tool for use in the ejbjar task. + * + * @param config Description of Parameter + */ + public void configure( EjbJar.Config config ) + { + this.config = config; + + classpathLoader = null; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( task.getProject() ); + } + return classpath.createPath(); + } + + public void processDescriptor( String descriptorFileName, SAXParser saxParser ) + { + + checkConfiguration( descriptorFileName, saxParser ); + + try + { + handler = getDescriptorHandler( config.srcDir ); + + // Retrive the files to be added to JAR from EJB descriptor + Hashtable ejbFiles = parseEjbFiles( descriptorFileName, saxParser ); + + // Add any support classes specified in the build file + addSupportClasses( ejbFiles ); + + // Determine the JAR filename (without filename extension) + String baseName = getJarBaseName( descriptorFileName ); + + String ddPrefix = getVendorDDPrefix( baseName, descriptorFileName ); + + // First the regular deployment descriptor + ejbFiles.put( META_DIR + EJB_DD, + new File( config.descriptorDir, descriptorFileName ) ); + + // now the vendor specific files, if any + addVendorFiles( ejbFiles, ddPrefix ); + + // add any dependent files + checkAndAddDependants( ejbFiles ); + + // Lastly create File object for the Jar files. If we are using + // a flat destination dir, then we need to redefine baseName! + if( config.flatDestDir && baseName.length() != 0 ) + { + int startName = baseName.lastIndexOf( File.separator ); + if( startName == -1 ) + { + startName = 0; + } + + int endName = baseName.length(); + baseName = baseName.substring( startName, endName ); + } + + File jarFile = getVendorOutputJarFile( baseName ); + + // Check to see if we need a build and start doing the work! + if( needToRebuild( ejbFiles, jarFile ) ) + { + // Log that we are going to build... + log( "building " + + jarFile.getName() + + " with " + + String.valueOf( ejbFiles.size() ) + + " files", + Project.MSG_INFO ); + + // Use helper method to write the jarfile + String publicId = getPublicId(); + writeJar( baseName, jarFile, ejbFiles, publicId ); + + } + else + { + // Log that the file is up to date... + log( jarFile.toString() + " is up to date.", + Project.MSG_VERBOSE ); + } + + } + catch( SAXException se ) + { + String msg = "SAXException while parsing '" + + descriptorFileName.toString() + + "'. This probably indicates badly-formed XML." + + " Details: " + + se.getMessage(); + throw new BuildException( msg, se ); + } + catch( IOException ioe ) + { + String msg = "IOException while parsing'" + + descriptorFileName.toString() + + "'. This probably indicates that the descriptor" + + " doesn't exist. Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @throws BuildException If the Deployment Tool's configuration isn't valid + */ + public void validateConfigured() + throws BuildException + { + if( ( destDir == null ) || ( !destDir.isDirectory() ) ) + { + String msg = "A valid destination directory must be specified " + + "using the \"destdir\" attribute."; + throw new BuildException( msg, getLocation() ); + } + } + + + /** + * Returns a Classloader object which parses the passed in generic EjbJar + * classpath. The loader is used to dynamically load classes from + * javax.ejb.* and the classes being added to the jar. + * + * @return The ClassLoaderForBuild value + */ + protected ClassLoader getClassLoaderForBuild() + { + if( classpathLoader != null ) + { + return classpathLoader; + } + + Path combinedClasspath = getCombinedClasspath(); + + // only generate a new ClassLoader if we have a classpath + if( combinedClasspath == null ) + { + classpathLoader = getClass().getClassLoader(); + } + else + { + classpathLoader = new AntClassLoader( getTask().getProject(), combinedClasspath ); + } + + return classpathLoader; + } + + /** + * Get the classpath by combining the one from the surrounding task, if any + * and the one from this tool. + * + * @return The CombinedClasspath value + */ + protected Path getCombinedClasspath() + { + Path combinedPath = classpath; + if( config.classpath != null ) + { + if( combinedPath == null ) + { + combinedPath = config.classpath; + } + else + { + combinedPath.append( config.classpath ); + } + } + + return combinedPath; + } + + /** + * Get the basename terminator. + * + * @return The Config value + */ + protected EjbJar.Config getConfig() + { + return config; + } + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + DescriptorHandler handler = new DescriptorHandler( getTask(), srcDir ); + + registerKnownDTDs( handler ); + + // register any DTDs supplied by the user + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + /** + * Get the desitination directory. + * + * @return The DestDir value + */ + protected File getDestDir() + { + return destDir; + } + + + /** + * Using the EJB descriptor file name passed from the ejbjar + * task, this method returns the "basename" which will be used to name the + * completed JAR file. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @return The "basename" which will be used to name the completed JAR file + */ + protected String getJarBaseName( String descriptorFileName ) + { + + String baseName = ""; + + // Work out what the base name is + if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.BASEJARNAME ) ) + { + String canonicalDescriptor = descriptorFileName.replace( '\\', '/' ); + int index = canonicalDescriptor.lastIndexOf( '/' ); + if( index != -1 ) + { + baseName = descriptorFileName.substring( 0, index + 1 ); + } + baseName += config.baseJarName; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DESCRIPTOR ) ) + { + int lastSeparatorIndex = descriptorFileName.lastIndexOf( File.separator ); + int endBaseName = -1; + if( lastSeparatorIndex != -1 ) + { + endBaseName = descriptorFileName.indexOf( config.baseNameTerminator, + lastSeparatorIndex ); + } + else + { + endBaseName = descriptorFileName.indexOf( config.baseNameTerminator ); + } + + if( endBaseName != -1 ) + { + baseName = descriptorFileName.substring( 0, endBaseName ); + } + baseName = descriptorFileName.substring( 0, endBaseName ); + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DIRECTORY ) ) + { + int lastSeparatorIndex = descriptorFileName.lastIndexOf( File.separator ); + String dirName = descriptorFileName.substring( 0, lastSeparatorIndex ); + int dirSeparatorIndex = dirName.lastIndexOf( File.separator ); + if( dirSeparatorIndex != -1 ) + { + dirName = dirName.substring( dirSeparatorIndex + 1 ); + } + + baseName = dirName; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.EJB_NAME ) ) + { + baseName = handler.getEjbName(); + } + return baseName; + } + + protected Location getLocation() + { + return getTask().getLocation(); + } + + /** + * Returns the Public ID of the DTD specified in the EJB descriptor. Not + * every vendor-specific DeploymentTool will need to reference + * this value or may want to determine this value in a vendor-specific way. + * + * @return Public ID of the DTD specified in the EJB descriptor. + */ + protected String getPublicId() + { + return handler.getPublicId(); + } + + /** + * Get the task for this tool. + * + * @return The Task value + */ + protected Task getTask() + { + return task; + } + + /** + * Utility method that encapsulates the logic of adding a file entry to a + * .jar file. Used by execute() to add entries to the jar file as it is + * constructed. + * + * @param jStream A JarOutputStream into which to write the jar entry. + * @param inputFile A File from which to read the contents the file being + * added. + * @param logicalFilename A String representing the name, including all + * relevant path information, that should be stored for the entry being + * added. + * @exception BuildException Description of Exception + */ + protected void addFileToJar( JarOutputStream jStream, + File inputFile, + String logicalFilename ) + throws BuildException + { + FileInputStream iStream = null; + try + { + if( !addedfiles.contains( logicalFilename ) ) + { + iStream = new FileInputStream( inputFile ); + // Create the zip entry and add it to the jar file + ZipEntry zipEntry = new ZipEntry( logicalFilename.replace( '\\', '/' ) ); + jStream.putNextEntry( zipEntry ); + + // Create the file input stream, and buffer everything over + // to the jar output stream + byte[] byteBuffer = new byte[2 * 1024]; + int count = 0; + do + { + jStream.write( byteBuffer, 0, count ); + count = iStream.read( byteBuffer, 0, byteBuffer.length ); + }while ( count != -1 ); + + //add it to list of files in jar + addedfiles.add( logicalFilename ); + } + } + catch( IOException ioe ) + { + log( "WARNING: IOException while adding entry " + + logicalFilename + " to jarfile from " + inputFile.getPath() + " " + + ioe.getClass().getName() + "-" + ioe.getMessage(), Project.MSG_WARN ); + } + finally + { + // Close up the file input stream for the class file + if( iStream != null ) + { + try + { + iStream.close(); + } + catch( IOException closeException ) + {} + } + } + } + + /** + * Adds any classes the user specifies using support nested elements + * to the ejbFiles Hashtable. + * + * @param ejbFiles Hashtable of EJB classes (and other) files that will be + * added to the completed JAR file + */ + protected void addSupportClasses( Hashtable ejbFiles ) + { + // add in support classes if any + Project project = task.getProject(); + for( Iterator i = config.supportFileSets.iterator(); i.hasNext(); ) + { + FileSet supportFileSet = ( FileSet )i.next(); + File supportBaseDir = supportFileSet.getDir( project ); + DirectoryScanner supportScanner = supportFileSet.getDirectoryScanner( project ); + supportScanner.scan(); + String[] supportFiles = supportScanner.getIncludedFiles(); + for( int j = 0; j < supportFiles.length; ++j ) + { + ejbFiles.put( supportFiles[j], new File( supportBaseDir, supportFiles[j] ) ); + } + } + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + // nothing to add for generic tool. + }// end of writeJar + + + /** + * Add all available classes, that depend on Remote, Home, Bean, PK + * + * @param checkEntries files, that are extracted from the deployment + * descriptor + * @exception BuildException Description of Exception + */ + protected void checkAndAddDependants( Hashtable checkEntries ) + throws BuildException + { + Dependencies visitor = new Dependencies(); + Set set = new TreeSet(); + Set newSet = new HashSet(); + final String base = config.srcDir.getAbsolutePath() + File.separator; + + Iterator i = checkEntries.keySet().iterator(); + while( i.hasNext() ) + { + String entryName = ( String )i.next(); + if( entryName.endsWith( ".class" ) ) + newSet.add( entryName.substring( 0, entryName.length() - ".class".length() ).replace( File.separatorChar, '/' ) ); + } + set.addAll( newSet ); + + do + { + i = newSet.iterator(); + while( i.hasNext() ) + { + String fileName = base + ( ( String )i.next() ).replace( '/', File.separatorChar ) + ".class"; + + try + { + JavaClass javaClass = new ClassParser( fileName ).parse(); + javaClass.accept( visitor ); + } + catch( IOException e ) + { + log( "exception: " + e.getMessage(), Project.MSG_INFO ); + } + } + newSet.clear(); + newSet.addAll( visitor.getDependencies() ); + visitor.clearDependencies(); + + Dependencies.applyFilter( newSet, + new Filter() + { + public boolean accept( Object object ) + { + String fileName = base + ( ( String )object ).replace( '/', File.separatorChar ) + ".class"; + return new File( fileName ).exists(); + } + } ); + newSet.removeAll( set ); + set.addAll( newSet ); + }while ( newSet.size() > 0 ); + + i = set.iterator(); + while( i.hasNext() ) + { + String next = ( ( String )i.next() ).replace( '/', File.separatorChar ); + checkEntries.put( next + ".class", new File( base + next + ".class" ) ); + log( "dependent class: " + next + ".class" + " - " + base + next + ".class", Project.MSG_VERBOSE ); + } + } + + /** + * This method is called as the first step in the processDescriptor method + * to allow vendor-specific subclasses to validate the task configuration + * prior to processing the descriptor. If the configuration is invalid, a + * BuildException should be thrown. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @exception BuildException Description of Exception + * @thows BuildException Thrown if the configuration is invalid + */ + protected void checkConfiguration( String descriptorFileName, + SAXParser saxParser ) + throws BuildException + { + + /* + * For the GenericDeploymentTool, do nothing. Vendor specific + * subclasses should throw a BuildException if the configuration is + * invalid for their server. + */ + } + + protected void log( String message, int level ) + { + getTask().log( message, level ); + } + + /** + * This method checks the timestamp on each file listed in the + * ejbFiles and compares them to the timestamp on the jarFile + * . If the jarFile's timestamp is more recent than each + * EJB file, true is returned. Otherwise, false + * is returned. TODO: find a way to check the manifest-file, that is + * found by naming convention + * + * @param ejbFiles Hashtable of EJB classes (and other) files that will be + * added to the completed JAR file + * @param jarFile JAR file which will contain all of the EJB classes (and + * other) files + * @return boolean indicating whether or not the jarFile is up + * to date + */ + protected boolean needToRebuild( Hashtable ejbFiles, File jarFile ) + { + if( jarFile.exists() ) + { + long lastBuild = jarFile.lastModified(); + + if( config.manifest != null && config.manifest.exists() && + config.manifest.lastModified() > lastBuild ) + { + log( "Build needed because manifest " + config.manifest + " is out of date", + Project.MSG_VERBOSE ); + return true; + } + + Iterator fileIter = ejbFiles.values().iterator(); + + // Loop through the files seeing if any has been touched + // more recently than the destination jar. + while( fileIter.hasNext() ) + { + File currentFile = ( File )fileIter.next(); + if( lastBuild < currentFile.lastModified() ) + { + log( "Build needed because " + currentFile.getPath() + " is out of date", + Project.MSG_VERBOSE ); + return true; + } + } + return false; + } + + return true; + } + + /** + * This method returns a list of EJB files found when the specified EJB + * descriptor is parsed and processed. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @return Hashtable of EJB class (and other) files to be added to the + * completed JAR file + * @throws SAXException Any SAX exception, possibly wrapping another + * exception + * @throws IOException An IOException from the parser, possibly from a the + * byte stream or character stream + */ + protected Hashtable parseEjbFiles( String descriptorFileName, SAXParser saxParser ) + throws IOException, SAXException + { + FileInputStream descriptorStream = null; + Hashtable ejbFiles = null; + + try + { + + /* + * Parse the ejb deployment descriptor. While it may not + * look like much, we use a SAXParser and an inner class to + * get hold of all the classfile names for the descriptor. + */ + descriptorStream = new FileInputStream( new File( config.descriptorDir, descriptorFileName ) ); + saxParser.parse( new InputSource( descriptorStream ), handler ); + + ejbFiles = handler.getFiles(); + + } + finally + { + if( descriptorStream != null ) + { + try + { + descriptorStream.close(); + } + catch( IOException closeException ) + {} + } + } + + return ejbFiles; + } + + /** + * Register the locations of all known DTDs. vendor-specific subclasses + * should override this method to define the vendor-specific locations of + * the EJB DTDs + * + * @param handler Description of Parameter + */ + protected void registerKnownDTDs( DescriptorHandler handler ) + { + // none to register for generic + } + + /** + * Returns true, if the meta-inf dir is being explicitly set, false + * otherwise. + * + * @return Description of the Returned Value + */ + protected boolean usingBaseJarName() + { + return config.baseJarName != null; + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarfile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarfile, Hashtable files, + String publicId ) + throws BuildException + { + + JarOutputStream jarStream = null; + try + { + // clean the addedfiles Vector + addedfiles = new ArrayList(); + + /* + * If the jarfile already exists then whack it and recreate it. + * Should probably think of a more elegant way to handle this + * so that in case of errors we don't leave people worse off + * than when we started =) + */ + if( jarfile.exists() ) + { + jarfile.delete(); + } + jarfile.getParentFile().mkdirs(); + jarfile.createNewFile(); + + InputStream in = null; + Manifest manifest = null; + try + { + File manifestFile = new File( getConfig().descriptorDir, baseName + "-manifest.mf" ); + if( manifestFile.exists() ) + { + in = new FileInputStream( manifestFile ); + } + else if( config.manifest != null ) + { + in = new FileInputStream( config.manifest ); + if( in == null ) + { + throw new BuildException( "Could not find manifest file: " + config.manifest, + getLocation() ); + } + } + else + { + String defaultManifest = "/org/apache/tools/ant/defaultManifest.mf"; + in = this.getClass().getResourceAsStream( defaultManifest ); + if( in == null ) + { + throw new BuildException( "Could not find default manifest: " + defaultManifest, + getLocation() ); + } + } + + manifest = new Manifest( in ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest", e, getLocation() ); + } + finally + { + if( in != null ) + { + in.close(); + } + } + + // Create the streams necessary to write the jarfile + + jarStream = new JarOutputStream( new FileOutputStream( jarfile ), manifest ); + jarStream.setMethod( JarOutputStream.DEFLATED ); + + // Loop through all the class files found and add them to the jar + for( Iterator entryIterator = files.keySet().iterator(); entryIterator.hasNext(); ) + { + String entryName = ( String )entryIterator.next(); + File entryFile = ( File )files.get( entryName ); + + log( "adding file '" + entryName + "'", + Project.MSG_VERBOSE ); + + addFileToJar( jarStream, entryFile, entryName ); + + // See if there are any inner classes for this class and add them in if there are + InnerClassFilenameFilter flt = new InnerClassFilenameFilter( entryFile.getName() ); + File entryDir = entryFile.getParentFile(); + String[] innerfiles = entryDir.list( flt ); + for( int i = 0, n = innerfiles.length; i < n; i++ ) + { + + //get and clean up innerclass name + int entryIndex = entryName.lastIndexOf( entryFile.getName() ) - 1; + if( entryIndex < 0 ) + { + entryName = innerfiles[i]; + } + else + { + entryName = entryName.substring( 0, entryIndex ) + File.separatorChar + innerfiles[i]; + } + // link the file + entryFile = new File( config.srcDir, entryName ); + + log( "adding innerclass file '" + entryName + "'", + Project.MSG_VERBOSE ); + + addFileToJar( jarStream, entryFile, entryName ); + + } + } + } + catch( IOException ioe ) + { + String msg = "IOException while processing ejb-jar file '" + + jarfile.toString() + + "'. Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + finally + { + if( jarStream != null ) + { + try + { + jarStream.close(); + } + catch( IOException closeException ) + {} + } + } + } + + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( destDir, baseName + genericJarSuffix ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java new file mode 100644 index 000000000..0dfdd0041 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.IOException; +import java.util.Hashtable; +import javax.xml.parsers.SAXParser; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.xml.sax.SAXException; + +/** + * This class is used to generate iPlanet Application Server (iAS) 6.0 stubs and + * skeletons and build an EJB Jar file. It is designed to be used with the Ant + * ejbjar task. If only stubs and skeletons need to be generated + * (in other words, if no JAR file needs to be created), refer to the iplanet-ejbc + * task and the IPlanetEjbcTask class.

      + * + * The following attributes may be specified by the user: + *

        + *
      • destdir -- The base directory into which the generated JAR + * files will be written. Each JAR file is written in directories which + * correspond to their location within the "descriptordir" namespace. This is + * a required attribute. + *
      • classpath -- The classpath used when generating EJB stubs and + * skeletons. This is an optional attribute (if omitted, the classpath + * specified in the "ejbjar" parent task will be used). If specified, the + * classpath elements will be prepended to the classpath specified in the + * parent "ejbjar" task. Note that nested "classpath" elements may also be + * used. + *
      • keepgenerated -- Indicates whether or not the Java source files + * which are generated by ejbc will be saved or automatically deleted. If + * "yes", the source files will be retained. This is an optional attribute (if + * omitted, it defaults to "no"). + *
      • debug -- Indicates whether or not the ejbc utility should log + * additional debugging statements to the standard output. If "yes", the + * additional debugging statements will be generated (if omitted, it defaults + * to "no"). + *
      • iashome -- May be used to specify the "home" directory for this + * iPlanet Application server installation. This is used to find the ejbc + * utility if it isn't included in the user's system path. This is an optional + * attribute (if specified, it should refer to the [install-location]/iplanet/ias6/ias + * directory). If omitted, the ejbc utility + * must be on the user's system path. + *
      • suffix -- String value appended to the JAR filename when + * creating each JAR. This attribute is not required (if omitted, it defaults + * to ".jar"). + *
      + *

      + * + * For each EJB descriptor found in the "ejbjar" parent task, this deployment + * tool will locate the three classes that comprise the EJB. If these class + * files cannot be located in the specified srcdir directory, the + * task will fail. The task will also attempt to locate the EJB stubs and + * skeletons in this directory. If found, the timestamps on the stubs and + * skeletons will be checked to ensure they are up to date. Only if these files + * cannot be found or if they are out of date will ejbc be called. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetEjbc + */ +public class IPlanetDeploymentTool extends GenericDeploymentTool +{ + + /* + * Regardless of the name of the iAS-specific EJB descriptor file, it will + * written in the completed JAR file as "ias-ejb-jar.xml". This is the + * naming convention implemented by iAS. + */ + private final static String IAS_DD = "ias-ejb-jar.xml"; + private String jarSuffix = ".jar"; + private boolean keepgenerated = false; + private boolean debug = false; + + /* + * Filenames of the standard EJB descriptor (which is passed to this class + * from the parent "ejbjar" task) and the iAS-specific EJB descriptor + * (whose name is determined by this class). Both filenames are relative + * to the directory specified by the "srcdir" attribute in the ejbjar task. + */ + private String descriptorName; + + /* + * The displayName variable stores the value of the "display-name" element + * from the standard EJB descriptor. As a future enhancement to this task, + * we may determine the name of the EJB JAR file using this display-name, + * but this has not be implemented yet. + */ + private String displayName; + private String iasDescriptorName; + + /* + * Attributes set by the Ant build file + */ + private File iashome; + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debug A boolean indicating if debugging output should be generated + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Since iAS doesn't generate a "generic" JAR as part of its processing, + * this attribute is ignored and a warning message is displayed to the user. + * + * @param inString the string to use as the suffix. This parameter is + * ignored. + */ + public void setGenericJarSuffix( String inString ) + { + log( "Since a generic JAR file is not created during processing, the " + + "iPlanet Deployment Tool does not support the " + + "\"genericjarsuffix\" attribute. It will be ignored.", + Project.MSG_WARN ); + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iashome The home directory for the user's iAS installation. + */ + public void setIashome( File iashome ) + { + this.iashome = iashome; + } + + /** + * Setter method used to specify whether the Java source files generated by + * the ejbc utility should be saved or automatically deleted. + * + * @param keepgenerated boolean which, if true, indicates that + * Java source files generated by ejbc for the stubs and skeletons + * should be kept. + */ + public void setKeepgenerated( boolean keepgenerated ) + { + this.keepgenerated = keepgenerated; + } + + /** + * Setter method used to specify the filename suffix (for example, ".jar") + * for the JAR files to be created. + * + * @param jarSuffix The string to use as the JAR filename suffix. + */ + public void setSuffix( String jarSuffix ) + { + this.jarSuffix = jarSuffix; + } + + public void processDescriptor( String descriptorName, SAXParser saxParser ) + { + this.descriptorName = descriptorName; + + log( "iPlanet Deployment Tool processing: " + descriptorName + " (and " + + getIasDescriptorName() + ")", Project.MSG_VERBOSE ); + + super.processDescriptor( descriptorName, saxParser ); + } + + /** + * The iAS ejbc utility doesn't require the Public ID of the descriptor's + * DTD for it to process correctly--this method always returns null + * . + * + * @return null. + */ + protected String getPublicId() + { + return null; + } + + /** + * Add the iAS-specific EJB descriptor to the list of files which will be + * written to the JAR file. + * + * @param ejbFiles Hashtable of EJB class (and other) files to be added to + * the completed JAR file. + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + ejbFiles.put( META_DIR + IAS_DD, new File( getConfig().descriptorDir, + getIasDescriptorName() ) ); + } + + /** + * Verifies that the user selections are valid. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @throws BuildException If the user selections are invalid. + */ + protected void checkConfiguration( String descriptorFileName, + SAXParser saxParser ) + throws BuildException + { + + int startOfName = descriptorFileName.lastIndexOf( File.separatorChar ) + 1; + String stdXml = descriptorFileName.substring( startOfName ); + if( stdXml.equals( EJB_DD ) && ( getConfig().baseJarName == null ) ) + { + String msg = "No name specified for the completed JAR file. The EJB" + + " descriptor should be prepended with the JAR " + + "name or it should be specified using the " + + "attribute \"basejarname\" in the \"ejbjar\" task."; + throw new BuildException( msg, getLocation() ); + } + + File iasDescriptor = new File( getConfig().descriptorDir, + getIasDescriptorName() ); + if( ( !iasDescriptor.exists() ) || ( !iasDescriptor.isFile() ) ) + { + String msg = "The iAS-specific EJB descriptor (" + + iasDescriptor + ") was not found."; + throw new BuildException( msg, getLocation() ); + } + + if( ( iashome != null ) && ( !iashome.isDirectory() ) ) + { + String msg = "If \"iashome\" is specified, it must be a valid " + + "directory (it was set to " + iashome + ")."; + throw new BuildException( msg, getLocation() ); + } + } + + /** + * This method returns a list of EJB files found when the specified EJB + * descriptor is parsed and processed. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @return Hashtable of EJB class (and other) files to be added to the + * completed JAR file + * @throws IOException An IOException from the parser, possibly from the + * byte stream or character stream + * @throws SAXException Any SAX exception, possibly wrapping another + * exception + */ + protected Hashtable parseEjbFiles( String descriptorFileName, + SAXParser saxParser ) + throws IOException, SAXException + { + + Hashtable files; + + /* + * Build and populate an instance of the ejbc utility + */ + IPlanetEjbc ejbc = new IPlanetEjbc( + new File( getConfig().descriptorDir, + descriptorFileName ), + new File( getConfig().descriptorDir, + getIasDescriptorName() ), + getConfig().srcDir, + getCombinedClasspath().toString(), + saxParser ); + ejbc.setRetainSource( keepgenerated ); + ejbc.setDebugOutput( debug ); + if( iashome != null ) + { + ejbc.setIasHomeDir( iashome ); + } + + /* + * Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed + */ + try + { + ejbc.execute(); + } + catch( IPlanetEjbc.EjbcException e ) + { + throw new BuildException( "An error has occurred while trying to " + + "execute the iAS ejbc utility", e, getLocation() ); + } + + displayName = ejbc.getDisplayName(); + files = ejbc.getEjbFiles(); + + /* + * Add CMP descriptors to the list of EJB files + */ + String[] cmpDescriptors = ejbc.getCmpDescriptors(); + if( cmpDescriptors.length > 0 ) + { + File baseDir = getConfig().descriptorDir; + + int endOfPath = descriptorFileName.lastIndexOf( File.separator ); + String relativePath = descriptorFileName.substring( 0, endOfPath + 1 ); + + for( int i = 0; i < cmpDescriptors.length; i++ ) + { + int endOfCmp = cmpDescriptors[i].lastIndexOf( '/' ); + String cmpDescriptor = cmpDescriptors[i].substring( endOfCmp + 1 ); + + File cmpFile = new File( baseDir, relativePath + cmpDescriptor ); + if( !cmpFile.exists() ) + { + throw new BuildException( "The CMP descriptor file (" + + cmpFile + ") could not be found.", getLocation() ); + } + files.put( cmpDescriptors[i], cmpFile ); + } + } + + return files; + } + + /** + * Get the name of the Jar that will be written. The modification date of + * this jar will be checked against the dependent bean classes. + * + * @param baseName String name of the EJB JAR file to be written (without a + * filename extension). + * @return File representing the JAR file which will be written. + */ + File getVendorOutputJarFile( String baseName ) + { + File jarFile = new File( getDestDir(), baseName + jarSuffix ); + log( "JAR file name: " + jarFile.toString(), Project.MSG_VERBOSE ); + return jarFile; + } + + /** + * Determines the name of the iAS-specific EJB descriptor using the + * specified standard EJB descriptor name. In general, the standard + * descriptor will be named "[basename]-ejb-jar.xml", and this method will + * return "[basename]-ias-ejb-jar.xml". + * + * @return The name of the iAS-specific EJB descriptor file. + */ + private String getIasDescriptorName() + { + + /* + * Only calculate the descriptor name once + */ + if( iasDescriptorName != null ) + { + return iasDescriptorName; + } + + String path = "";// Directory path of the EJB descriptor + String basename;// Filename appearing before name terminator + String remainder;// Filename appearing after the name terminator + + /* + * Find the end of the standard descriptor's relative path + */ + int startOfFileName = descriptorName.lastIndexOf( File.separatorChar ); + if( startOfFileName != -1 ) + { + path = descriptorName.substring( 0, startOfFileName + 1 ); + } + + /* + * Check to see if the standard name is used (there's no basename) + */ + if( descriptorName.substring( startOfFileName + 1 ).equals( EJB_DD ) ) + { + basename = ""; + remainder = EJB_DD; + + } + else + { + int endOfBaseName = descriptorName.indexOf( + getConfig().baseNameTerminator, + startOfFileName ); + /* + * Check for the odd case where the terminator and/or filename + * extension aren't found. These will ensure "ias-" appears at the + * end of the name and before the '.' (if present). + */ + if( endOfBaseName < 0 ) + { + endOfBaseName = descriptorName.lastIndexOf( '.' ) - 1; + if( endOfBaseName < 0 ) + { + endOfBaseName = descriptorName.length() - 1; + } + } + + basename = descriptorName.substring( startOfFileName + 1, + endOfBaseName + 1 ); + remainder = descriptorName.substring( endOfBaseName + 1 ); + } + + iasDescriptorName = path + basename + "ias-" + remainder; + return iasDescriptorName; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java new file mode 100644 index 000000000..8c3b18d60 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java @@ -0,0 +1,1690 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.AttributeList; +import org.xml.sax.HandlerBase; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Utility class to compile EJB stubs and skeletons for the iPlanet Application + * Server (iAS). The class will read a standard EJB descriptor (as well as an + * EJB descriptor specific to iPlanet Application Server) to identify one or + * more EJBs to process. It will search for EJB "source" classes (the remote + * interface, home interface, and EJB implementation class) and the EJB stubs + * and skeletons in the specified destination directory. Only if the stubs and + * skeletons cannot be found or if they're out of date will the iPlanet + * Application Server ejbc utility be run.

      + * + * Because this class (and it's assorted inner classes) may be bundled into the + * iPlanet Application Server distribution at some point (and removed from the + * Ant distribution), the class has been written to be independent of all + * Ant-specific classes. It is also for this reason (and to avoid cluttering the + * Apache Ant source files) that this utility has been packaged into a single + * source file.

      + * + * For more information on Ant Tasks for iPlanet Application Server, see the + * IPlanetDeploymentTool and IPlanetEjbcTask classes. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetDeploymentTool + * @see IPlanetEjbcTask + */ +public class IPlanetEjbc +{ + + /* + * Constants used for the "beantype" attribute + */ + private final static String ENTITY_BEAN = "entity"; + private final static String STATELESS_SESSION = "stateless"; + private final static String STATEFUL_SESSION = "stateful"; + + /* + * Options passed to the iAS ejbc + */ + private boolean retainSource = false; + private boolean debugOutput = false; + private EjbcHandler handler = new EjbcHandler(); + + /* + * This Hashtable maintains a list of EJB class files processed by the ejbc + * utility (both "source" class files as well as stubs and skeletons). The + * key for the Hashtable is a String representing the path to the class file + * (relative to the destination directory). The value for the Hashtable is + * a File object which reference the actual class file. + */ + private Hashtable ejbFiles = new Hashtable(); + + /* + * Classpath used when the iAS ejbc is called + */ + private String classpath; + private String[] classpathElements; + + /* + * Directory where "source" EJB files are stored and where stubs and + * skeletons will also be written. + */ + private File destDirectory; + + /* + * Value of the display-name element read from the standard EJB descriptor + */ + private String displayName; + private File iasDescriptor; + + /* + * iAS installation directory (used if ejbc isn't on user's PATH) + */ + private File iasHomeDir; + + /* + * Parser and handler used to process both EJB descriptor files + */ + private SAXParser parser; + + /* + * Filenames of the standard EJB descriptor and the iAS-specific descriptor + */ + private File stdDescriptor; + + /** + * Constructs an instance which may be used to process EJB descriptors and + * generate EJB stubs and skeletons, if needed. + * + * @param stdDescriptor File referencing a standard EJB descriptor. + * @param iasDescriptor File referencing an iAS-specific EJB descriptor. + * @param destDirectory File referencing the base directory where both EJB + * "source" files are found and where stubs and skeletons will be + * written. + * @param classpath String representation of the classpath to be used by the + * iAS ejbc utility. + * @param parser SAXParser to be used to process both of the EJB + * descriptors. + */ + public IPlanetEjbc( File stdDescriptor, + File iasDescriptor, + File destDirectory, + String classpath, + SAXParser parser ) + { + this.stdDescriptor = stdDescriptor; + this.iasDescriptor = iasDescriptor; + this.destDirectory = destDirectory; + this.classpath = classpath; + this.parser = parser; + + /* + * Parse the classpath into it's individual elements and store the + * results in the "classpathElements" instance variable. + */ + List elements = new ArrayList(); + if( classpath != null ) + { + StringTokenizer st = new StringTokenizer( classpath, + File.pathSeparator ); + while( st.hasMoreTokens() ) + { + elements.add( st.nextToken() ); + } + classpathElements + = ( String[] )elements.toArray( new String[elements.size()] ); + } + } + + /** + * Main application method for the iPlanet Application Server ejbc utility. + * If the application is run with no commandline arguments, a usage + * statement is printed for the user. + * + * @param args The commandline arguments passed to the application. + */ + public static void main( String[] args ) + { + File stdDescriptor; + File iasDescriptor; + File destDirectory = null; + String classpath = null; + SAXParser parser = null; + boolean debug = false; + boolean retainSource = false; + IPlanetEjbc ejbc; + + if( ( args.length < 2 ) || ( args.length > 8 ) ) + { + usage(); + return; + } + + stdDescriptor = new File( args[args.length - 2] ); + iasDescriptor = new File( args[args.length - 1] ); + + for( int i = 0; i < args.length - 2; i++ ) + { + if( args[i].equals( "-classpath" ) ) + { + classpath = args[++i]; + } + else if( args[i].equals( "-d" ) ) + { + destDirectory = new File( args[++i] ); + } + else if( args[i].equals( "-debug" ) ) + { + debug = true; + } + else if( args[i].equals( "-keepsource" ) ) + { + retainSource = true; + } + else + { + usage(); + return; + } + } + + /* + * If the -classpath flag isn't specified, use the system classpath + */ + if( classpath == null ) + { + Properties props = System.getProperties(); + classpath = props.getProperty( "java.class.path" ); + } + + /* + * If the -d flag isn't specified, use the working directory as the + * destination directory + */ + if( destDirectory == null ) + { + Properties props = System.getProperties(); + destDirectory = new File( props.getProperty( "user.dir" ) ); + } + + /* + * Construct a SAXParser used to process the descriptors + */ + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + parserFactory.setValidating( true ); + try + { + parser = parserFactory.newSAXParser(); + } + catch( Exception e ) + { + // SAXException or ParserConfigurationException may be thrown + System.out.println( "An exception was generated while trying to " ); + System.out.println( "create a new SAXParser." ); + e.printStackTrace(); + return; + } + + /* + * Build and populate an instance of the ejbc utility + */ + ejbc = new IPlanetEjbc( stdDescriptor, iasDescriptor, destDirectory, + classpath, parser ); + ejbc.setDebugOutput( debug ); + ejbc.setRetainSource( retainSource ); + + /* + * Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed + */ + try + { + ejbc.execute(); + } + catch( IOException e ) + { + System.out.println( "An IOException has occurred while reading the " + + "XML descriptors (" + e.getMessage() + ")." ); + return; + } + catch( SAXException e ) + { + System.out.println( "A SAXException has occurred while reading the " + + "XML descriptors (" + e.getMessage() + ")." ); + return; + } + catch( IPlanetEjbc.EjbcException e ) + { + System.out.println( "An error has occurred while executing the ejbc " + + "utility (" + e.getMessage() + ")." ); + return; + } + } + + /** + * Print a usage statement. + */ + private static void usage() + { + System.out.println( "java org.apache.tools.ant.taskdefs.optional.ejb.IPlanetEjbc \\" ); + System.out.println( " [OPTIONS] [EJB 1.1 descriptor] [iAS EJB descriptor]" ); + System.out.println( "" ); + System.out.println( "Where OPTIONS are:" ); + System.out.println( " -debug -- for additional debugging output" ); + System.out.println( " -keepsource -- to retain Java source files generated" ); + System.out.println( " -classpath [classpath] -- classpath used for compilation" ); + System.out.println( " -d [destination directory] -- directory for compiled classes" ); + System.out.println( "" ); + System.out.println( "If a classpath is not specified, the system classpath" ); + System.out.println( "will be used. If a destination directory is not specified," ); + System.out.println( "the current working directory will be used (classes will" ); + System.out.println( "still be placed in subfolders which correspond to their" ); + System.out.println( "package name)." ); + System.out.println( "" ); + System.out.println( "The EJB home interface, remote interface, and implementation" ); + System.out.println( "class must be found in the destination directory. In" ); + System.out.println( "addition, the destination will look for the stubs and skeletons" ); + System.out.println( "in the destination directory to ensure they are up to date." ); + } + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debugOutput A boolean indicating if debugging output should be + * generated + */ + public void setDebugOutput( boolean debugOutput ) + { + this.debugOutput = debugOutput; + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iasHomeDir The new IasHomeDir value + */ + public void setIasHomeDir( File iasHomeDir ) + { + this.iasHomeDir = iasHomeDir; + } + + /** + * Sets whether or not the Java source files which are generated by the ejbc + * process should be retained or automatically deleted. + * + * @param retainSource The new RetainSource value + */ + public void setRetainSource( boolean retainSource ) + { + this.retainSource = retainSource; + } + + /** + * Returns the list of CMP descriptors referenced in the EJB descriptors. + * + * @return An array of CMP descriptors. + */ + public String[] getCmpDescriptors() + { + List returnList = new ArrayList(); + + EjbInfo[] ejbs = handler.getEjbs(); + + for( int i = 0; i < ejbs.length; i++ ) + { + List descriptors = ( List )ejbs[i].getCmpDescriptors(); + returnList.addAll( descriptors ); + } + + return ( String[] )returnList.toArray( new String[returnList.size()] ); + } + + /** + * Returns the display-name element read from the standard EJB descriptor. + * + * @return The EJB-JAR display name. + */ + public String getDisplayName() + { + return displayName; + } + + /** + * Returns a Hashtable which contains a list of EJB class files processed by + * the ejbc utility (both "source" class files as well as stubs and + * skeletons). The key for the Hashtable is a String representing the path + * to the class file (relative to the destination directory). The value for + * the Hashtable is a File object which reference the actual class file. + * + * @return The list of EJB files processed by the ejbc utility. + */ + public Hashtable getEjbFiles() + { + return ejbFiles; + } + + /** + * Compiles the stub and skeletons for the specified EJBs, if they need to + * be updated. + * + * @throws EjbcException If the ejbc utility cannot be correctly configured + * or if one or more of the EJB "source" classes cannot be found in the + * destination directory + * @throws IOException If the parser encounters a problem reading the XML + * file + * @throws SAXException If the parser encounters a problem processing the + * XML descriptor (it may wrap another exception) + */ + public void execute() + throws EjbcException, IOException, SAXException + { + + checkConfiguration();// Throws EjbcException if unsuccessful + + EjbInfo[] ejbs = getEjbs();// Returns list of EJBs for processing + + for( int i = 0; i < ejbs.length; i++ ) + { + log( "EJBInfo..." ); + log( ejbs[i].toString() ); + } + + for( int i = 0; i < ejbs.length; i++ ) + { + EjbInfo ejb = ejbs[i]; + + ejb.checkConfiguration( destDirectory );// Throws EjbcException + + if( ejb.mustBeRecompiled( destDirectory ) ) + { + log( ejb.getName() + " must be recompiled using ejbc." ); + + String[] arguments = buildArgumentList( ejb ); + callEjbc( arguments ); + + } + else + { + log( ejb.getName() + " is up to date." ); + } + } + } + + /** + * Registers the location of a local DTD file or resource. By registering a + * local DTD, EJB descriptors can be parsed even when the remote servers + * which contain the "public" DTDs cannot be accessed. + * + * @param publicID The public DTD identifier found in an XML document. + * @param location The file or resource name for the appropriate DTD stored + * on the local machine. + */ + public void registerDTD( String publicID, String location ) + { + handler.registerDTD( publicID, location ); + } + + /** + * Verifies that the user selections are valid. + * + * @throws EjbcException If the user selections are invalid. + */ + protected void checkConfiguration() + throws EjbcException + { + + String msg = ""; + + if( stdDescriptor == null ) + { + msg += "A standard XML descriptor file must be specified. "; + } + if( iasDescriptor == null ) + { + msg += "An iAS-specific XML descriptor file must be specified. "; + } + if( classpath == null ) + { + msg += "A classpath must be specified. "; + } + if( parser == null ) + { + msg += "An XML parser must be specified. "; + } + + if( destDirectory == null ) + { + msg += "A destination directory must be specified. "; + } + else if( !destDirectory.exists() ) + { + msg += "The destination directory specified does not exist. "; + } + else if( !destDirectory.isDirectory() ) + { + msg += "The destination specified is not a directory. "; + } + + if( msg.length() > 0 ) + { + throw new EjbcException( msg ); + } + } + + /** + * Parses the EJB descriptors and returns a list of EJBs which may need to + * be compiled. + * + * @return An array of objects which describe the EJBs to be processed. + * @throws IOException If the parser encounters a problem reading the XML + * files + * @throws SAXException If the parser encounters a problem processing the + * XML descriptor (it may wrap another exception) + */ + private EjbInfo[] getEjbs() + throws IOException, SAXException + { + EjbInfo[] ejbs = null; + + /* + * The EJB information is gathered from the standard XML EJB descriptor + * and the iAS-specific XML EJB descriptor using a SAX parser. + */ + parser.parse( stdDescriptor, handler ); + parser.parse( iasDescriptor, handler ); + ejbs = handler.getEjbs(); + + return ejbs; + } + + /** + * Based on this object's instance variables as well as the EJB to be + * processed, the correct flags and parameters are set for the ejbc + * command-line utility. + * + * @param ejb The EJB for which stubs and skeletons will be compiled. + * @return An array of Strings which are the command-line parameters for for + * the ejbc utility. + */ + private String[] buildArgumentList( EjbInfo ejb ) + { + + List arguments = new ArrayList(); + + /* + * OPTIONAL COMMAND LINE PARAMETERS + */ + if( debugOutput ) + { + arguments.add( "-debug" ); + } + + /* + * No beantype flag is needed for an entity bean + */ + if( ejb.getBeantype().equals( STATELESS_SESSION ) ) + { + arguments.add( "-sl" ); + } + else if( ejb.getBeantype().equals( STATEFUL_SESSION ) ) + { + arguments.add( "-sf" ); + } + + if( ejb.getIiop() ) + { + arguments.add( "-iiop" ); + } + + if( ejb.getCmp() ) + { + arguments.add( "-cmp" ); + } + + if( retainSource ) + { + arguments.add( "-gs" ); + } + + if( ejb.getHasession() ) + { + arguments.add( "-fo" ); + } + + /* + * REQUIRED COMMAND LINE PARAMETERS + */ + arguments.add( "-classpath" ); + arguments.add( classpath ); + + arguments.add( "-d" ); + arguments.add( destDirectory.toString() ); + + arguments.add( ejb.getHome().getQualifiedClassName() ); + arguments.add( ejb.getRemote().getQualifiedClassName() ); + arguments.add( ejb.getImplementation().getQualifiedClassName() ); + + /* + * Convert the List into an Array and return it + */ + return ( String[] )arguments.toArray( new String[arguments.size()] ); + } + + /** + * Executes the iPlanet Application Server ejbc command-line utility. + * + * @param arguments Command line arguments to be passed to the ejbc utility. + */ + private void callEjbc( String[] arguments ) + { + + /* + * Concatenate all of the command line arguments into a single String + */ + StringBuffer args = new StringBuffer(); + for( int i = 0; i < arguments.length; i++ ) + { + args.append( arguments[i] ).append( " " ); + } + + /* + * If an iAS home directory is specified, prepend it to the commmand + */ + String command; + if( iasHomeDir == null ) + { + command = ""; + } + else + { + command = iasHomeDir.toString() + File.separator + "bin" + + File.separator; + } + command += "ejbc "; + + log( command + args ); + + /* + * Use the Runtime object to execute an external command. Use the + * RedirectOutput inner class to direct the standard and error output + * from the command to the JRE's standard output + */ + try + { + Process p = Runtime.getRuntime().exec( command + args ); + RedirectOutput output = new RedirectOutput( p.getInputStream() ); + RedirectOutput error = new RedirectOutput( p.getErrorStream() ); + output.start(); + error.start(); + p.waitFor(); + p.destroy(); + } + catch( IOException e ) + { + log( "An IOException has occurred while trying to execute ejbc." ); + e.printStackTrace(); + } + catch( InterruptedException e ) + { + // Do nothing + } + } + + /** + * Convenience method used to print messages to the user if debugging + * messages are enabled. + * + * @param msg The String to print to standard output. + */ + private void log( String msg ) + { + if( debugOutput ) + { + System.out.println( msg ); + } + } + + + /* + * Inner classes follow + */ + + /** + * This inner class is used to signal any problems during the execution of + * the ejbc compiler. + * + * @author Greg Nelson greg@netscape.com + * + */ + public class EjbcException extends Exception + { + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of the exception which has occurred. + */ + public EjbcException( String msg ) + { + super( msg ); + } + }// End of EjbInfo inner class + + /** + * Convenience class used to represent the fully qualified name of a Java + * class. It provides an easy way to retrieve components of the class name + * in a format that is convenient for building iAS stubs and skeletons. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class Classname + {// Name of the package for this class + private String className;// Fully qualified name of the Java class + private String packageName; + private String qualifiedName;// Name of the class without the package + + /** + * This constructor builds an object which represents the name of a Java + * class. + * + * @param qualifiedName String representing the fully qualified class + * name of the Java class. + */ + public Classname( String qualifiedName ) + { + if( qualifiedName == null ) + { + return; + } + + this.qualifiedName = qualifiedName; + + int index = qualifiedName.lastIndexOf( '.' ); + if( index == -1 ) + { + className = qualifiedName; + packageName = ""; + } + else + { + packageName = qualifiedName.substring( 0, index ); + className = qualifiedName.substring( index + 1 ); + } + } + + /** + * Returns a File which references the class relative to the specified + * directory. Note that the class file may or may not exist. + * + * @param directory A File referencing the base directory containing + * class files. + * @return File referencing this class. + */ + public File getClassFile( File directory ) + { + String pathToFile = qualifiedName.replace( '.', File.separatorChar ) + + ".class"; + return new File( directory, pathToFile ); + } + + /** + * Gets the Java class name without the package structure. + * + * @return String representing the name for the class. + */ + public String getClassName() + { + return className; + } + + /** + * Gets the package name for the Java class. + * + * @return String representing the package name for the class. + */ + public String getPackageName() + { + return packageName; + } + + /** + * Gets the fully qualified name of the Java class. + * + * @return String representing the fully qualified class name. + */ + public String getQualifiedClassName() + { + return qualifiedName; + } + + /** + * Gets the fully qualified name of the Java class with underscores + * separating the components of the class name rather than periods. This + * format is used in naming some of the stub and skeleton classes for + * the iPlanet Application Server. + * + * @return String representing the fully qualified class name using + * underscores instead of periods. + */ + public String getQualifiedWithUnderscores() + { + return qualifiedName.replace( '.', '_' ); + } + + /** + * String representation of this class name. It returns the fully + * qualified class name. + * + * @return String representing the fully qualified class name. + */ + public String toString() + { + return getQualifiedClassName(); + } + }// End of EjbcHandler inner class + + + /** + * This inner class represents an EJB that will be compiled using ejbc. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class EjbInfo + {// EJB's implementation class + private String beantype = "entity";// or "stateful" or "stateless" + private boolean cmp = false;// Does this EJB support CMP? + private boolean iiop = false;// Does this EJB support IIOP? + private boolean hasession = false;// Does this EJB require failover? + private List cmpDescriptors = new ArrayList();// EJB's display name + private Classname home;// EJB's remote interface name + private Classname implementation; + private String name;// EJB's home interface name + private Classname remote;// CMP descriptor list + + /** + * Construct a new EJBInfo object with the given name. + * + * @param name The display name for the EJB. + */ + public EjbInfo( String name ) + { + this.name = name; + } + + public void setBeantype( String beantype ) + { + this.beantype = beantype.toLowerCase(); + } + + public void setCmp( boolean cmp ) + { + this.cmp = cmp; + } + + public void setCmp( String cmp ) + { + setCmp( cmp.equals( "Container" ) ); + } + + public void setHasession( boolean hasession ) + { + this.hasession = hasession; + } + + public void setHasession( String hasession ) + { + setHasession( hasession.equals( "true" ) ); + } + + /* + * Below are getter's and setter's for each of the instance variables. + * Note that (in addition to supporting setters with the same type as + * the instance variable) a setter is provided with takes a String + * argument -- this are provided so the XML document handler can set + * the EJB values using the Strings it parses. + */ + public void setHome( String home ) + { + setHome( new Classname( home ) ); + } + + public void setHome( Classname home ) + { + this.home = home; + } + + public void setIiop( boolean iiop ) + { + this.iiop = iiop; + } + + public void setIiop( String iiop ) + { + setIiop( iiop.equals( "true" ) ); + } + + public void setImplementation( String implementation ) + { + setImplementation( new Classname( implementation ) ); + } + + public void setImplementation( Classname implementation ) + { + this.implementation = implementation; + } + + public void setRemote( String remote ) + { + setRemote( new Classname( remote ) ); + } + + public void setRemote( Classname remote ) + { + this.remote = remote; + } + + public String getBeantype() + { + return beantype; + } + + public boolean getCmp() + { + return cmp; + } + + public List getCmpDescriptors() + { + return cmpDescriptors; + } + + public boolean getHasession() + { + return hasession; + } + + public Classname getHome() + { + return home; + } + + public boolean getIiop() + { + return iiop; + } + + public Classname getImplementation() + { + return implementation; + } + + /** + * Returns the display name of the EJB. If a display name has not been + * set, it returns the EJB implementation classname (if the + * implementation class is not set, it returns "[unnamed]"). + * + * @return The display name for the EJB. + */ + public String getName() + { + if( name == null ) + { + if( implementation == null ) + { + return "[unnamed]"; + } + else + { + return implementation.getClassName(); + } + } + return name; + } + + public Classname getRemote() + { + return remote; + } + + public void addCmpDescriptor( String descriptor ) + { + cmpDescriptors.add( descriptor ); + } + + /** + * Determines if the ejbc utility needs to be run or not. If the stubs + * and skeletons can all be found in the destination directory AND all + * of their timestamps are more recent than the EJB source classes + * (home, remote, and implementation classes), the method returns false + * . Otherwise, the method returns true. + * + * @param destDir The directory where the EJB source classes, stubs and + * skeletons are located. + * @return A boolean indicating whether or not the ejbc utility needs to + * be run to bring the stubs and skeletons up to date. + */ + public boolean mustBeRecompiled( File destDir ) + { + + long sourceModified = sourceClassesModified( destDir ); + + long destModified = destClassesModified( destDir ); + + return ( destModified < sourceModified ); + } + + /** + * Convenience method which creates a String representation of all the + * instance variables of an EjbInfo object. + * + * @return A String representing the EjbInfo instance. + */ + public String toString() + { + String s = "EJB name: " + name + + "\n\r home: " + home + + "\n\r remote: " + remote + + "\n\r impl: " + implementation + + "\n\r beantype: " + beantype + + "\n\r cmp: " + cmp + + "\n\r iiop: " + iiop + + "\n\r hasession: " + hasession; + + Iterator i = cmpDescriptors.iterator(); + while( i.hasNext() ) + { + s += "\n\r CMP Descriptor: " + i.next(); + } + + return s; + } + + /** + * Verifies that the EJB is valid--if it is invalid, an exception is + * thrown + * + * @param buildDir The directory where the EJB remote interface, home + * interface, and implementation class must be found. + * @throws EjbcException If the EJB is invalid. + */ + private void checkConfiguration( File buildDir ) + throws EjbcException + { + + /* + * Check that the specified instance variables are valid + */ + if( home == null ) + { + throw new EjbcException( "A home interface was not found " + + "for the " + name + " EJB." ); + } + if( remote == null ) + { + throw new EjbcException( "A remote interface was not found " + + "for the " + name + " EJB." ); + } + if( implementation == null ) + { + throw new EjbcException( "An EJB implementation class was not " + + "found for the " + name + " EJB." ); + } + + if( ( !beantype.equals( ENTITY_BEAN ) ) + && ( !beantype.equals( STATELESS_SESSION ) ) + && ( !beantype.equals( STATEFUL_SESSION ) ) ) + { + throw new EjbcException( "The beantype found (" + beantype + ") " + + "isn't valid in the " + name + " EJB." ); + } + + if( cmp && ( !beantype.equals( ENTITY_BEAN ) ) ) + { + System.out.println( "CMP stubs and skeletons may not be generated" + + " for a Session Bean -- the \"cmp\" attribute will be" + + " ignoredfor the " + name + " EJB." ); + } + + if( hasession && ( !beantype.equals( STATEFUL_SESSION ) ) ) + { + System.out.println( "Highly available stubs and skeletons may " + + "only be generated for a Stateful Session Bean -- the " + + "\"hasession\" attribute will be ignored for the " + + name + " EJB." ); + } + + /* + * Check that the EJB "source" classes all exist + */ + if( !remote.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The remote interface " + + remote.getQualifiedClassName() + " could not be " + + "found." ); + } + if( !home.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The home interface " + + home.getQualifiedClassName() + " could not be " + + "found." ); + } + if( !implementation.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The EJB implementation class " + + implementation.getQualifiedClassName() + " could " + + "not be found." ); + } + } + + /** + * Builds an array of class names which represent the stubs and + * skeletons which need to be generated for a given EJB. The class names + * are fully qualified. Nine classes are generated for all EJBs while an + * additional six classes are generated for beans requiring RMI/IIOP + * access. + * + * @return An array of Strings representing the fully-qualified class + * names for the stubs and skeletons to be generated. + */ + private String[] classesToGenerate() + { + String[] classnames = ( iiop ) ? new String[15] : new String[9]; + + final String remotePkg = remote.getPackageName() + "."; + final String remoteClass = remote.getClassName(); + final String homePkg = home.getPackageName() + "."; + final String homeClass = home.getClassName(); + final String implPkg = implementation.getPackageName() + "."; + final String implFullClass = implementation.getQualifiedWithUnderscores(); + int index = 0; + + String fullPath; + + classnames[index++] = implPkg + "ejb_fac_" + implFullClass; + classnames[index++] = implPkg + "ejb_home_" + implFullClass; + classnames[index++] = implPkg + "ejb_skel_" + implFullClass; + classnames[index++] = remotePkg + "ejb_kcp_skel_" + remoteClass; + classnames[index++] = homePkg + "ejb_kcp_skel_" + homeClass; + classnames[index++] = remotePkg + "ejb_kcp_stub_" + remoteClass; + classnames[index++] = homePkg + "ejb_kcp_stub_" + homeClass; + classnames[index++] = remotePkg + "ejb_stub_" + remoteClass; + classnames[index++] = homePkg + "ejb_stub_" + homeClass; + + if( !iiop ) + { + return classnames; + } + + classnames[index++] = remotePkg + "_" + remoteClass + "_Stub"; + classnames[index++] = homePkg + "_" + homeClass + "_Stub"; + classnames[index++] = remotePkg + "_ejb_RmiCorbaBridge_" + + remoteClass + "_Tie"; + classnames[index++] = homePkg + "_ejb_RmiCorbaBridge_" + homeClass + + "_Tie"; + classnames[index++] = remotePkg + "ejb_RmiCorbaBridge_" + + remoteClass; + classnames[index++] = homePkg + "ejb_RmiCorbaBridge_" + homeClass; + + return classnames; + } + + /** + * Examines each of the EJB stubs and skeletons in the destination + * directory and returns the modification timestamp for the "oldest" + * class. If one of the stubs or skeletons cannot be found, -1 + * is returned. + * + * @param destDir Description of Parameter + * @return The modification timestamp for the "oldest" EJB stub or + * skeleton. If one of the classes cannot be found, -1 + * is returned. + * @throws BuildException If the canonical path of the destination + * directory cannot be found. + */ + private long destClassesModified( File destDir ) + { + String[] classnames = classesToGenerate();// List of all stubs & skels + long destClassesModified = new Date().getTime();// Earliest mod time + boolean allClassesFound = true;// Has each been found? + + /* + * Loop through each stub/skeleton class that must be generated, and + * determine (if all exist) which file has the most recent timestamp + */ + for( int i = 0; i < classnames.length; i++ ) + { + + String pathToClass = + classnames[i].replace( '.', File.separatorChar ) + ".class"; + File classFile = new File( destDir, pathToClass ); + + /* + * Add each stub/skeleton class to the list of EJB files. Note + * that each class is added even if it doesn't exist now. + */ + ejbFiles.put( pathToClass, classFile ); + + allClassesFound = allClassesFound && classFile.exists(); + + if( allClassesFound ) + { + long fileMod = classFile.lastModified(); + + /* + * Keep track of the oldest modification timestamp + */ + destClassesModified = Math.min( destClassesModified, fileMod ); + } + } + + return ( allClassesFound ) ? destClassesModified : -1; + } + + /** + * Examines each of the EJB source classes (home, remote, and + * implementation) and returns the modification timestamp for the + * "oldest" class. + * + * @param buildDir Description of Parameter + * @return The modification timestamp for the "oldest" EJB source class. + * @throws BuildException If one of the EJB source classes cannot be + * found on the classpath. + */ + private long sourceClassesModified( File buildDir ) + { + long latestModified;// The timestamp of the "newest" class + long modified;// Timestamp for a given class + File remoteFile;// File for the remote interface class + File homeFile;// File for the home interface class + File implFile;// File for the EJB implementation class + + /* + * Check the timestamp on the remote interface + */ + remoteFile = remote.getClassFile( buildDir ); + modified = remoteFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + remote.getQualifiedClassName() + " couldn't " + + "be found on the classpath" ); + return -1; + } + latestModified = modified; + + /* + * Check the timestamp on the home interface + */ + homeFile = home.getClassFile( buildDir ); + modified = homeFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + home.getQualifiedClassName() + " couldn't be " + + "found on the classpath" ); + return -1; + } + latestModified = Math.max( latestModified, modified ); + + /* + * Check the timestamp on the EJB implementation class. + * + * Note that if ONLY the implementation class has changed, it's not + * necessary to rebuild the EJB stubs and skeletons. For this + * reason, we ensure the file exists (using lastModified above), but + * we DON'T compare it's timestamp with the timestamps of the home + * and remote interfaces (because it's irrelevant in determining if + * ejbc must be run) + */ + implFile = implementation.getClassFile( buildDir ); + modified = implFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + implementation.getQualifiedClassName() + + " couldn't be found on the classpath" ); + return -1; + } + + String pathToFile = remote.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, remoteFile ); + + pathToFile = home.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, homeFile ); + + pathToFile = implementation.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, implFile ); + + return latestModified; + } + + }// End of EjbcException inner class + + + /** + * This inner class is an XML document handler that can be used to parse EJB + * descriptors (both the standard EJB descriptor as well as the iAS-specific + * descriptor that stores additional values for iAS). Once the descriptors + * have been processed, the list of EJBs found can be obtained by calling + * the getEjbs() method. + * + * @author Greg Nelson greg@netscape.com + * + * @see EjbInfo + */ + private class EjbcHandler extends HandlerBase + { + + /* + * Two Maps are used to track local DTDs that will be used in case the + * remote copies of these DTDs cannot be accessed. The key for the Map + * is the DTDs public ID and the value is the local location for the DTD + */ + private Map resourceDtds = new HashMap(); + private Map fileDtds = new HashMap(); + + private Map ejbs = new HashMap();// One item within the Map + private boolean iasDescriptor = false;// Is doc iAS or EJB descriptor + + private String currentLoc = "";// List of EJBs found in XML + private EjbInfo currentEjb;// Tracks current element + private String currentText;// Tracks current text data + private String ejbType;// "session" or "entity" + + /** + * Constructs a new instance of the handler and registers local copies + * of the standard EJB 1.1 descriptor DTD as well as iAS's EJB + * descriptor DTD. + */ + public EjbcHandler() + { + final String PUBLICID_EJB11 = + "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + final String PUBLICID_IPLANET_EJB_60 = + "-//Sun Microsystems, Inc.//DTD iAS Enterprise JavaBeans 1.0//EN"; + + final String DEFAULT_IAS60_EJB11_DTD_LOCATION = + "ejb-jar_1_1.dtd"; + final String DEFAULT_IAS60_DTD_LOCATION = + "IASEjb_jar_1_0.dtd"; + + registerDTD( PUBLICID_EJB11, DEFAULT_IAS60_EJB11_DTD_LOCATION ); + registerDTD( PUBLICID_IPLANET_EJB_60, DEFAULT_IAS60_DTD_LOCATION ); + } + + /** + * Returns the value of the display-name element found in the standard + * EJB 1.1 descriptor. + * + * @return String display-name value. + */ + public String getDisplayName() + { + return displayName; + } + + /** + * Returns the list of EJB objects found during the processing of the + * standard EJB 1.1 descriptor and iAS-specific EJB descriptor. + * + * @return An array of EJBs which were found during the descriptor + * parsing. + */ + public EjbInfo[] getEjbs() + { + return ( EjbInfo[] )ejbs.values().toArray( new EjbInfo[ejbs.size()] ); + } + + /** + * Receive notification that character data has been found in the XML + * document + * + * @param ch Array of characters which have been found in the document. + * @param start Starting index of the data found in the document. + * @param len The number of characters found in the document. + * @throws SAXException If the parser cannot process the document. + */ + public void characters( char[] ch, int start, int len ) + throws SAXException + { + + currentText += new String( ch ).substring( start, start + len ); + } + + /** + * Receive notification that the end of an XML element has been found. + * + * @param name String name of the element. + * @throws SAXException If the parser cannot process the document. + */ + public void endElement( String name ) + throws SAXException + { + + /* + * If this is a standard EJB 1.1 descriptor, we are looking for one + * set of data, while if this is an iAS-specific descriptor, we're + * looking for different set of data. Hand the processing off to + * the appropriate method. + */ + if( iasDescriptor ) + { + iasCharacters( currentText ); + } + else + { + stdCharacters( currentText ); + } + + /* + * I need to "pop" the element off the String (currentLoc) which + * always represents my current location in the XML document. + */ + int nameLength = name.length() + 1;// Add one for the "\" + int locLength = currentLoc.length(); + + currentLoc = currentLoc.substring( 0, locLength - nameLength ); + } + + /** + * Registers a local DTD that will be used when parsing an EJB + * descriptor. When the DTD's public identifier is found in an XML + * document, the parser will reference the local DTD rather than the + * remote DTD. This enables XML documents to be processed even when the + * public DTD isn't available. + * + * @param publicID The DTD's public identifier. + * @param location The location of the local DTD copy -- the location + * may either be a resource found on the classpath or a local file. + */ + public void registerDTD( String publicID, String location ) + { + log( "Registering: " + location ); + if( ( publicID == null ) || ( location == null ) ) + { + return; + } + + if( ClassLoader.getSystemResource( location ) != null ) + { + log( "Found resource: " + location ); + resourceDtds.put( publicID, location ); + } + else + { + File dtdFile = new File( location ); + if( dtdFile.exists() && dtdFile.isFile() ) + { + log( "Found file: " + location ); + fileDtds.put( publicID, location ); + } + } + } + + /** + * Resolves an external entity found during XML processing. If a public + * ID is found that has been registered with the handler, an + * InputSource will be returned which refers to the local copy. + * If the public ID hasn't been registered or if an error occurs, the + * superclass implementation is used. + * + * @param publicId The DTD's public identifier. + * @param systemId The location of the DTD, as found in the XML + * document. + * @return Description of the Returned Value + * @exception SAXException Description of Exception + */ + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + InputStream inputStream = null; + + try + { + + /* + * Search the resource Map and (if not found) file Map + */ + String location = ( String )resourceDtds.get( publicId ); + if( location != null ) + { + inputStream + = ClassLoader.getSystemResource( location ).openStream(); + } + else + { + location = ( String )fileDtds.get( publicId ); + if( location != null ) + { + inputStream = new FileInputStream( location ); + } + } + } + catch( IOException e ) + { + return super.resolveEntity( publicId, systemId ); + } + + if( inputStream == null ) + { + return super.resolveEntity( publicId, systemId ); + } + else + { + return new InputSource( inputStream ); + } + } + + /** + * Receive notification that the start of an XML element has been found. + * + * @param name String name of the element found. + * @param atts AttributeList of the attributes included with the element + * (if any). + * @throws SAXException If the parser cannot process the document. + */ + public void startElement( String name, AttributeList atts ) + throws SAXException + { + + /* + * I need to "push" the element onto the String (currentLoc) which + * always represents the current location in the XML document. + */ + currentLoc += "\\" + name; + + /* + * A new element has started, so reset the text being captured + */ + currentText = ""; + + if( currentLoc.equals( "\\ejb-jar" ) ) + { + iasDescriptor = false; + } + else if( currentLoc.equals( "\\ias-ejb-jar" ) ) + { + iasDescriptor = true; + } + + if( ( name.equals( "session" ) ) || ( name.equals( "entity" ) ) ) + { + ejbType = name; + } + } + + /** + * Receive notification that character data has been found in an + * iAS-specific descriptor. We're interested in retrieving data + * indicating whether the bean must support RMI/IIOP access, whether the + * bean must provide highly available stubs and skeletons (in the case + * of stateful session beans), and if this bean uses additional CMP XML + * descriptors (in the case of entity beans with CMP). + * + * @param value String data found in the XML document. + */ + private void iasCharacters( String value ) + { + String base = "\\ias-ejb-jar\\enterprise-beans\\" + ejbType; + + if( currentLoc.equals( base + "\\ejb-name" ) ) + { + currentEjb = ( EjbInfo )ejbs.get( value ); + if( currentEjb == null ) + { + currentEjb = new EjbInfo( value ); + ejbs.put( value, currentEjb ); + } + } + else if( currentLoc.equals( base + "\\iiop" ) ) + { + currentEjb.setIiop( value ); + } + else if( currentLoc.equals( base + "\\failover-required" ) ) + { + currentEjb.setHasession( value ); + } + else if( currentLoc.equals( base + "\\persistence-manager" + + "\\properties-file-location" ) ) + { + currentEjb.addCmpDescriptor( value ); + } + } + + /** + * Receive notification that character data has been found in a standard + * EJB 1.1 descriptor. We're interested in retrieving the home + * interface, remote interface, implementation class, the type of bean, + * and if the bean uses CMP. + * + * @param value String data found in the XML document. + */ + private void stdCharacters( String value ) + { + + if( currentLoc.equals( "\\ejb-jar\\display-name" ) ) + { + displayName = value; + return; + } + + String base = "\\ejb-jar\\enterprise-beans\\" + ejbType; + + if( currentLoc.equals( base + "\\ejb-name" ) ) + { + currentEjb = ( EjbInfo )ejbs.get( value ); + if( currentEjb == null ) + { + currentEjb = new EjbInfo( value ); + ejbs.put( value, currentEjb ); + } + } + else if( currentLoc.equals( base + "\\home" ) ) + { + currentEjb.setHome( value ); + } + else if( currentLoc.equals( base + "\\remote" ) ) + { + currentEjb.setRemote( value ); + } + else if( currentLoc.equals( base + "\\ejb-class" ) ) + { + currentEjb.setImplementation( value ); + } + else if( currentLoc.equals( base + "\\session-type" ) ) + { + currentEjb.setBeantype( value ); + } + else if( currentLoc.equals( base + "\\persistence-type" ) ) + { + currentEjb.setCmp( value ); + } + } + }// End of Classname inner class + + + /** + * Thread class used to redirect output from an InputStream to + * the JRE standard output. This class may be used to redirect output from + * an external process to the standard output. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class RedirectOutput extends Thread + { + InputStream stream;// Stream to read and redirect to standard output + + /** + * Constructs a new instance that will redirect output from the + * specified stream to the standard output. + * + * @param stream InputStream which will be read and redirected to the + * standard output. + */ + public RedirectOutput( InputStream stream ) + { + this.stream = stream; + } + + /** + * Reads text from the input stream and redirects it to standard output + * using a separate thread. + */ + public void run() + { + BufferedReader reader = new BufferedReader( + new InputStreamReader( stream ) ); + String text; + try + { + while( ( text = reader.readLine() ) != null ) + { + System.out.println( text ); + } + } + catch( IOException e ) + { + e.printStackTrace(); + } + finally + { + try + { + reader.close(); + } + catch( IOException e ) + { + // Do nothing + } + } + } + }// End of RedirectOutput inner class + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java new file mode 100644 index 000000000..ce5f22396 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.IOException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.xml.sax.SAXException; + +/** + * Task to compile EJB stubs and skeletons for the iPlanet Application Server. + * The EJBs to be processed are specified by the EJB 1.1 standard XML + * descriptor, and additional attributes are obtained from the iPlanet + * Application Server-specific XML descriptor. Since the XML descriptors can + * include multiple EJBs, this is a convenient way of specifying many EJBs in a + * single Ant task. The following attributes are allowed: + *

        + *
      • ejbdescriptor -- Standard EJB 1.1 XML descriptor (typically + * titled "ejb-jar.xml"). This attribute is required. + *
      • iasdescriptor -- EJB XML descriptor for iPlanet Application + * Server (typically titled "ias-ejb-jar.xml). This attribute is required. + * + *
      • dest -- The is the base directory where the RMI stubs and + * skeletons are written. In addition, the class files for each bean (home + * interface, remote interface, and EJB implementation) must be found in this + * directory. This attribute is required. + *
      • classpath -- The classpath used when generating EJB stubs and + * skeletons. This is an optional attribute (if omitted, the classpath + * specified when Ant was started will be used). Nested "classpath" elements + * may also be used. + *
      • keepgenerated -- Indicates whether or not the Java source files + * which are generated by ejbc will be saved or automatically deleted. If + * "yes", the source files will be retained. This is an optional attribute (if + * omitted, it defaults to "no"). + *
      • debug -- Indicates whether or not the ejbc utility should log + * additional debugging statements to the standard output. If "yes", the + * additional debugging statements will be generated (if omitted, it defaults + * to "no"). + *
      • iashome -- May be used to specify the "home" directory for this + * iPlanet Application Server installation. This is used to find the ejbc + * utility if it isn't included in the user's system path. This is an optional + * attribute (if specified, it should refer to the [install-location]/iplanet/ias6/ias + * directory). If omitted, the ejbc utility + * must be on the user's system path. + *
      + *

      + * + * For each EJB specified, this task will locate the three classes that comprise + * the EJB. If these class files cannot be located in the dest + * directory, the task will fail. The task will also attempt to locate the EJB + * stubs and skeletons in this directory. If found, the timestamps on the stubs + * and skeletons will be checked to ensure they are up to date. Only if these + * files cannot be found or if they are out of date will ejbc be called to + * generate new stubs and skeletons. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetEjbc + */ +public class IPlanetEjbcTask extends Task +{ + private boolean keepgenerated = false; + private boolean debug = false; + private Path classpath; + private File dest; + + /* + * Attributes set by the Ant build file + */ + private File ejbdescriptor; + private File iasdescriptor; + private File iashome; + + /** + * Sets the classpath to be used when compiling the EJB stubs and skeletons. + * + * @param classpath The classpath to be used. + */ + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debug A boolean indicating if debugging output should be generated + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Sets the destination directory where the EJB "source" classes must exist + * and where the stubs and skeletons will be written. The destination + * directory must exist before this task is executed. + * + * @param dest The directory where the compiled classes will be written. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Sets the location of the standard XML EJB descriptor. Typically, this + * file is named "ejb-jar.xml". + * + * @param ejbdescriptor The name and location of the EJB descriptor. + */ + public void setEjbdescriptor( File ejbdescriptor ) + { + this.ejbdescriptor = ejbdescriptor; + } + + /** + * Sets the location of the iAS-specific XML EJB descriptor. Typically, this + * file is named "ias-ejb-jar.xml". + * + * @param iasdescriptor The name and location of the iAS-specific EJB + * descriptor. + */ + public void setIasdescriptor( File iasdescriptor ) + { + this.iasdescriptor = iasdescriptor; + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iashome The home directory for the user's iAS installation. + */ + public void setIashome( File iashome ) + { + this.iashome = iashome; + } + + /** + * Sets whether or not the Java source files which are generated by the ejbc + * process should be retained or automatically deleted. + * + * @param keepgenerated A boolean indicating if the Java source files for + * the stubs and skeletons should be retained. + */ + public void setKeepgenerated( boolean keepgenerated ) + { + this.keepgenerated = keepgenerated; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Does the work. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + checkConfiguration(); + + executeEjbc( getParser() ); + } + + /** + * Returns the CLASSPATH to be used when calling EJBc. If no user CLASSPATH + * is specified, the System classpath is returned instead. + * + * @return Path The classpath to be used for EJBc. + */ + private Path getClasspath() + { + if( classpath == null ) + { + classpath = Path.systemClasspath; + } + + return classpath; + } + + /** + * Returns a SAXParser that may be used to process the XML descriptors. + * + * @return Parser which may be used to process the EJB descriptors. + * @throws BuildException If the parser cannot be created or configured. + */ + private SAXParser getParser() + throws BuildException + { + + SAXParser saxParser = null; + try + { + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + saxParser = saxParserFactory.newSAXParser(); + } + catch( SAXException e ) + { + String msg = "Unable to create a SAXParser: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( ParserConfigurationException e ) + { + String msg = "Unable to create a SAXParser: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + + return saxParser; + } + + /** + * Verifies that the user selections are valid. + * + * @throws BuildException If the user selections are invalid. + */ + private void checkConfiguration() + throws BuildException + { + + if( ejbdescriptor == null ) + { + String msg = "The standard EJB descriptor must be specified using " + + "the \"ejbdescriptor\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !ejbdescriptor.exists() ) || ( !ejbdescriptor.isFile() ) ) + { + String msg = "The standard EJB descriptor (" + ejbdescriptor + + ") was not found or isn't a file."; + throw new BuildException( msg, location ); + } + + if( iasdescriptor == null ) + { + String msg = "The iAS-speific XML descriptor must be specified using" + + " the \"iasdescriptor\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !iasdescriptor.exists() ) || ( !iasdescriptor.isFile() ) ) + { + String msg = "The iAS-specific XML descriptor (" + iasdescriptor + + ") was not found or isn't a file."; + throw new BuildException( msg, location ); + } + + if( dest == null ) + { + String msg = "The destination directory must be specified using " + + "the \"dest\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !dest.exists() ) || ( !dest.isDirectory() ) ) + { + String msg = "The destination directory (" + dest + ") was not " + + "found or isn't a directory."; + throw new BuildException( msg, location ); + } + + if( ( iashome != null ) && ( !iashome.isDirectory() ) ) + { + String msg = "If \"iashome\" is specified, it must be a valid " + + "directory (it was set to " + iashome + ")."; + throw new BuildException( msg, getLocation() ); + } + } + + /** + * Executes the EJBc utility using the SAXParser provided. + * + * @param saxParser SAXParser that may be used to process the EJB + * descriptors + * @throws BuildException If there is an error reading or parsing the XML + * descriptors + */ + private void executeEjbc( SAXParser saxParser ) + throws BuildException + { + IPlanetEjbc ejbc = new IPlanetEjbc( ejbdescriptor, + iasdescriptor, + dest, + getClasspath().toString(), + saxParser ); + ejbc.setRetainSource( keepgenerated ); + ejbc.setDebugOutput( debug ); + if( iashome != null ) + { + ejbc.setIasHomeDir( iashome ); + } + + try + { + ejbc.execute(); + } + catch( IOException e ) + { + String msg = "An IOException occurred while trying to read the XML " + + "descriptor file: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( SAXException e ) + { + String msg = "A SAXException occurred while trying to read the XML " + + "descriptor file: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( IPlanetEjbc.EjbcException e ) + { + String msg = "An exception occurred while trying to run the ejbc " + + "utility: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java new file mode 100644 index 000000000..a5a7d70f0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FilenameFilter; + +public class InnerClassFilenameFilter implements FilenameFilter +{ + private String baseClassName; + + InnerClassFilenameFilter( String baseclass ) + { + int extidx = baseclass.lastIndexOf( ".class" ); + if( extidx == -1 ) + { + extidx = baseclass.length() - 1; + } + baseClassName = baseclass.substring( 0, extidx ); + } + + public boolean accept( File Dir, String filename ) + { + if( ( filename.lastIndexOf( "." ) != filename.lastIndexOf( ".class" ) ) + || ( filename.indexOf( baseClassName + "$" ) != 0 ) ) + { + return false; + } + return true; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java new file mode 100644 index 000000000..06fd4ad05 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.util.Hashtable; +import org.apache.tools.ant.Project; + +/** + * The deployment tool to add the jboss specific deployment descriptor to the + * ejb jar file. Jboss only requires one additional file jboss.xml and does not + * require any additional compilation. + * + * @author Paul Austin + * @version 1.0 + * @see EjbJar#createJboss + */ +public class JbossDeploymentTool extends GenericDeploymentTool +{ + protected final static String JBOSS_DD = "jboss.xml"; + protected final static String JBOSS_CMPD = "jaws.xml"; + + /** + * Instance variable that stores the suffix for the jboss jarfile. + */ + private String jarSuffix = ".jar"; + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + File jbossDD = new File( getConfig().descriptorDir, ddPrefix + JBOSS_DD ); + if( jbossDD.exists() ) + { + ejbFiles.put( META_DIR + JBOSS_DD, jbossDD ); + } + else + { + log( "Unable to locate jboss deployment descriptor. It was expected to be in " + jbossDD.getPath(), Project.MSG_WARN ); + return; + } + + File jbossCMPD = new File( getConfig().descriptorDir, ddPrefix + JBOSS_CMPD ); + if( jbossCMPD.exists() ) + { + ejbFiles.put( META_DIR + JBOSS_CMPD, jbossCMPD ); + } + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java new file mode 100644 index 000000000..e4d43714a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java @@ -0,0 +1,424 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; + +/** + * Execute a Weblogic server. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class WLRun extends Task +{ + protected final static String DEFAULT_WL51_POLICY_FILE = "weblogic.policy"; + protected final static String DEFAULT_WL60_POLICY_FILE = "lib/weblogic.policy"; + protected final static String DEFAULT_PROPERTIES_FILE = "weblogic.properties"; + + private String weblogicMainClass = "weblogic.Server"; + + /** + * Addional arguments to pass to the JVM used to run weblogic + */ + private String additionalArgs = ""; + + /** + * The name of the weblogic server - used to select the server's directory + * in the weblogic home directory. + */ + private String weblogicSystemName = "myserver"; + + /** + * The file containing the weblogic properties for this server. + */ + private String weblogicPropertiesFile = null; + + /** + * additional args to pass to the spawned jvm + */ + private String additionalJvmArgs = ""; + + /** + * The location of the BEA Home under which this server is run. WL6 only + */ + private File beaHome = null; + + /** + * The management username + */ + private String managementUsername = "system"; + + /** + * The management password + */ + private String managementPassword = null; + + /** + * The provate key password - used for SSL + */ + private String pkPassword = null; + + /** + * The classpath to be used when running the Java VM. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private Path classpath; + + /** + * The security policy to use when running the weblogic server + */ + private String securityPolicy; + + /** + * The weblogic classpath to the be used when running weblogic. + */ + private Path weblogicClasspath; + + /** + * The weblogic domain + */ + private String weblogicDomainName; + + /** + * The weblogic system home directory + */ + private File weblogicSystemHome; + + public void setArgs( String args ) + { + additionalArgs = args; + } + + /** + * The location of the BEA Home. + * + * @param beaHome the BEA Home directory. + */ + public void setBEAHome( File beaHome ) + { + this.beaHome = beaHome; + } + + + /** + * Set the classpath to be used for this execution. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + this.classpath = classpath; + } + + /** + * Set the Domain to run in + * + * @param domain the domain + */ + public void setDomain( String domain ) + { + this.weblogicDomainName = domain; + } + + /** + * The location where weblogic lives. + * + * @param weblogicHome the home directory of weblogic. + */ + public void setHome( File weblogicHome ) + { + weblogicSystemHome = weblogicHome; + } + + /** + * Set the additional arguments to pass to the weblogic JVM + * + * @param args the arguments to be passed to the JVM + */ + public void setJvmargs( String args ) + { + this.additionalJvmArgs = args; + } + + /** + * Set the name of the server to run + * + * @param serverName The new Name value + */ + public void setName( String serverName ) + { + this.weblogicSystemName = serverName; + } + + /** + * Set the private key password so the server can decrypt the SSL private + * key file. + * + * @param pkpassword the private key password, + */ + public void setPKPassword( String pkpassword ) + { + this.pkPassword = pkpassword; + } + + + /** + * Set the management password of the server + * + * @param password the management pasword of the server. + */ + public void setPassword( String password ) + { + this.managementPassword = password; + } + + /** + * Set the security policy for this invocation of weblogic. + * + * @param securityPolicy the security policy to use. + */ + public void setPolicy( String securityPolicy ) + { + this.securityPolicy = securityPolicy; + } + + /** + * Set the properties file to use. The location of the properties file is + * relative to the weblogi system home + * + * @param propertiesFilename the properties file name + */ + public void setProperties( String propertiesFilename ) + { + this.weblogicPropertiesFile = propertiesFilename; + } + + /** + * Set the management username to run the server + * + * @param username the management username of the server. + */ + public void setUsername( String username ) + { + this.managementUsername = username; + } + + + public void setWeblogicMainClass( String c ) + { + weblogicMainClass = c; + } + + /** + * Set the weblogic classpath. The weblogic classpath is used by weblogic to + * support dynamic class loading. + * + * @param weblogicClasspath the weblogic classpath + */ + public void setWlclasspath( Path weblogicClasspath ) + { + this.weblogicClasspath = weblogicClasspath; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Get the classpath to the weblogic classpaths + * + * @return Description of the Returned Value + */ + public Path createWLClasspath() + { + if( weblogicClasspath == null ) + { + weblogicClasspath = new Path( project ); + } + return weblogicClasspath.createPath(); + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a helper task. This approach allows the classpath of the helper task to + * be set. Since the weblogic tools require the class files of the project's + * home and remote interfaces to be available in the classpath, this also + * avoids having to start ant with the class path of the project it is + * building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( weblogicSystemHome == null ) + { + throw new BuildException( "weblogic home must be set" ); + } + if( !weblogicSystemHome.isDirectory() ) + { + throw new BuildException( "weblogic home directory " + weblogicSystemHome.getPath() + + " is not valid" ); + } + + if( beaHome != null ) + { + executeWLS6(); + } + else + { + executeWLS(); + } + } + + private void executeWLS() + { + File securityPolicyFile = findSecurityPolicyFile( DEFAULT_WL51_POLICY_FILE ); + File propertiesFile = null; + + if( weblogicPropertiesFile == null ) + { + weblogicPropertiesFile = DEFAULT_PROPERTIES_FILE; + } + propertiesFile = new File( weblogicSystemHome, weblogicPropertiesFile ); + if( !propertiesFile.exists() ) + { + // OK, properties file may be absolute + propertiesFile = project.resolveFile( weblogicPropertiesFile ); + if( !propertiesFile.exists() ) + { + throw new BuildException( "Properties file " + weblogicPropertiesFile + + " not found in weblogic home " + weblogicSystemHome + + " or as absolute file" ); + } + } + + Java weblogicServer = ( Java )project.createTask( "java" ); + weblogicServer.setTaskName( getTaskName() ); + weblogicServer.setFork( true ); + weblogicServer.setClassname( weblogicMainClass ); + + String jvmArgs = additionalJvmArgs; + + if( weblogicClasspath != null ) + { + jvmArgs += " -Dweblogic.class.path=" + weblogicClasspath; + } + + jvmArgs += " -Djava.security.manager -Djava.security.policy==" + securityPolicyFile; + jvmArgs += " -Dweblogic.system.home=" + weblogicSystemHome; + jvmArgs += " -Dweblogic.system.name=" + weblogicSystemName; + jvmArgs += " -Dweblogic.system.propertiesFile=" + weblogicPropertiesFile; + + weblogicServer.createJvmarg().setLine( jvmArgs ); + weblogicServer.createArg().setLine( additionalArgs ); + + if( classpath != null ) + { + weblogicServer.setClasspath( classpath ); + } + if( weblogicServer.executeJava() != 0 ) + { + throw new BuildException( "Execution of weblogic server failed" ); + } + } + + private void executeWLS6() + { + File securityPolicyFile = findSecurityPolicyFile( DEFAULT_WL60_POLICY_FILE ); + if( !beaHome.isDirectory() ) + { + throw new BuildException( "BEA home " + beaHome.getPath() + + " is not valid" ); + } + + File configFile = new File( weblogicSystemHome, "config/" + weblogicDomainName + "/config.xml" ); + if( !configFile.exists() ) + { + throw new BuildException( "Server config file " + configFile + " not found." ); + } + + if( managementPassword == null ) + { + throw new BuildException( "You must supply a management password to start the server" ); + } + + Java weblogicServer = ( Java )project.createTask( "java" ); + weblogicServer.setTaskName( getTaskName() ); + weblogicServer.setFork( true ); + weblogicServer.setDir( weblogicSystemHome ); + weblogicServer.setClassname( weblogicMainClass ); + + String jvmArgs = additionalJvmArgs; + + jvmArgs += " -Dweblogic.Domain=" + weblogicDomainName; + jvmArgs += " -Dweblogic.Name=" + weblogicSystemName; + jvmArgs += " -Dweblogic.system.home=" + weblogicSystemHome; + + jvmArgs += " -Dbea.home=" + beaHome; + jvmArgs += " -Djava.security.policy==" + securityPolicyFile; + + jvmArgs += " -Dweblogic.management.username=" + managementUsername; + jvmArgs += " -Dweblogic.management.password=" + managementPassword; + if( pkPassword != null ) + { + jvmArgs += " -Dweblogic.pkpassword=" + pkPassword; + } + + weblogicServer.createJvmarg().setLine( jvmArgs ); + weblogicServer.createArg().setLine( additionalArgs ); + + if( classpath != null ) + { + weblogicServer.setClasspath( classpath ); + } + + if( weblogicServer.executeJava() != 0 ) + { + throw new BuildException( "Execution of weblogic server failed" ); + } + } + + private File findSecurityPolicyFile( String defaultSecurityPolicy ) + { + String securityPolicy = this.securityPolicy; + if( securityPolicy == null ) + { + securityPolicy = defaultSecurityPolicy; + } + File securityPolicyFile = new File( weblogicSystemHome, securityPolicy ); + // If an explicit securityPolicy file was specified, it maybe an + // absolute path. Use the project to resolve it. + if( this.securityPolicy != null && !securityPolicyFile.exists() ) + { + securityPolicyFile = project.resolveFile( securityPolicy ); + } + // If we still can't find it, complain + if( !securityPolicyFile.exists() ) + { + throw new BuildException( "Security policy " + securityPolicy + + " was not found." ); + } + return securityPolicyFile; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java new file mode 100644 index 000000000..076e057ed --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; + +/** + * Shutdown a Weblogic server. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class WLStop extends Task +{ + + /** + * The delay (in seconds) to wait before shutting down. + */ + private int delay = 0; + + /** + * The location of the BEA Home under which this server is run. WL6 only + */ + private File beaHome = null; + /** + * The classpath to be used. It must contains the weblogic.Admin class. + */ + private Path classpath; + + /** + * The password to use to shutdown the weblogic server. + */ + private String password; + + /** + * The URL which the weblogic server is listening on. + */ + private String serverURL; + + /** + * The weblogic username to use to request the shutdown. + */ + private String username; + + /** + * The location of the BEA Home. + * + * @param beaHome the BEA Home directory. + */ + public void setBEAHome( File beaHome ) + { + this.beaHome = beaHome; + } + + /** + * Set the classpath to be used for this compilation. + * + * @param path The new Classpath value + */ + public void setClasspath( Path path ) + { + this.classpath = path; + } + + + /** + * Set the delay (in seconds) before shutting down the server. + * + * @param s the selay. + */ + public void setDelay( String s ) + { + delay = Integer.parseInt( s ); + } + + /** + * Set the password to use to request shutdown of the server. + * + * @param s the password. + */ + public void setPassword( String s ) + { + this.password = s; + } + + /** + * Set the URL to which the weblogic server is listening. + * + * @param s the url. + */ + public void setUrl( String s ) + { + this.serverURL = s; + } + + /** + * Set the username to use to request shutdown of the server. + * + * @param s the username. + */ + public void setUser( String s ) + { + this.username = s; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * the weblogic admin task This approach allows the classpath of the helper + * task to be set. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( username == null || password == null ) + { + throw new BuildException( "weblogic username and password must both be set" ); + } + + if( serverURL == null ) + { + throw new BuildException( "The url of the weblogic server must be provided." ); + } + + Java weblogicAdmin = ( Java )project.createTask( "java" ); + weblogicAdmin.setFork( true ); + weblogicAdmin.setClassname( "weblogic.Admin" ); + String args; + + if( beaHome == null ) + { + args = serverURL + " SHUTDOWN " + username + " " + password + " " + delay; + } + else + { + args = " -url " + serverURL + + " -username " + username + + " -password " + password + + " SHUTDOWN " + " " + delay; + } + + weblogicAdmin.setArgs( args ); + weblogicAdmin.setClasspath( classpath ); + weblogicAdmin.execute(); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java new file mode 100644 index 000000000..cc7d965ad --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java @@ -0,0 +1,852 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; +import org.xml.sax.InputSource; + +public class WeblogicDeploymentTool extends GenericDeploymentTool +{ + public final static String PUBLICID_EJB11 + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + public final static String PUBLICID_EJB20 + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"; + public final static String PUBLICID_WEBLOGIC_EJB510 + = "-//BEA Systems, Inc.//DTD WebLogic 5.1.0 EJB//EN"; + public final static String PUBLICID_WEBLOGIC_EJB600 + = "-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN"; + + protected final static String DEFAULT_WL51_EJB11_DTD_LOCATION + = "/weblogic/ejb/deployment/xml/ejb-jar.dtd"; + protected final static String DEFAULT_WL60_EJB11_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/ejb11-jar.dtd"; + protected final static String DEFAULT_WL60_EJB20_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/ejb20-jar.dtd"; + + protected final static String DEFAULT_WL51_DTD_LOCATION + = "/weblogic/ejb/deployment/xml/weblogic-ejb-jar.dtd"; + protected final static String DEFAULT_WL60_51_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/weblogic510-ejb-jar.dtd"; + protected final static String DEFAULT_WL60_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/weblogic600-ejb-jar.dtd"; + + protected final static String DEFAULT_COMPILER = "default"; + + protected final static String WL_DD = "weblogic-ejb-jar.xml"; + protected final static String WL_CMP_DD = "weblogic-cmp-rdbms-jar.xml"; + + protected final static String COMPILER_EJB11 = "weblogic.ejbc"; + protected final static String COMPILER_EJB20 = "weblogic.ejbc20"; + + /** + * Instance variable that stores the suffix for the weblogic jarfile. + */ + private String jarSuffix = ".jar"; + + /** + * Instance variable that determines whether generic ejb jars are kept. + */ + private boolean keepgenerated = false; + + /** + * Instance variable that stores the fully qualified classname of the + * weblogic EJBC compiler + */ + private String ejbcClass = null; + + private String additionalArgs = ""; + + private boolean keepGeneric = false; + + private String compiler = null; + + private boolean alwaysRebuild = true; + + /** + * controls whether ejbc is run on the generated jar + */ + private boolean noEJBC = false; + + /** + * Indicates if the old CMP location convention is to be used. + */ + private boolean newCMP = false; + + /** + * The classpath to the weblogic classes. + */ + private Path wlClasspath = null; + + /** + * The weblogic.StdoutSeverityLevel to use when running the JVM that + * executes ejbc. Set to 16 to avoid the warnings about EJB Home and Remotes + * being in the classpath + */ + private Integer jvmDebugLevel = null; + + /** + * Instance variable that stores the location of the ejb 1.1 DTD file. + */ + private String ejb11DTD; + + /** + * Instance variable that stores the location of the weblogic DTD file. + */ + private String weblogicDTD; + + /** + * sets some additional args to send to ejbc. + * + * @param args The new Args value + */ + public void setArgs( String args ) + { + this.additionalArgs = args; + } + + /** + * The compiler (switch -compiler) to use + * + * @param compiler The new Compiler value + */ + public void setCompiler( String compiler ) + { + this.compiler = compiler; + } + + /** + * Setter used to store the location of the Sun's Generic EJB DTD. This can + * be a file on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setEJBdtd( String inString ) + { + this.ejb11DTD = inString; + } + + /** + * Set the classname of the ejbc compiler + * + * @param ejbcClass The new EjbcClass value + */ + public void setEjbcClass( String ejbcClass ) + { + this.ejbcClass = ejbcClass; + } + + /** + * Sets the weblogic.StdoutSeverityLevel to use when running the JVM that + * executes ejbc. Set to 16 to avoid the warnings about EJB Home and Remotes + * being in the classpath + * + * @param jvmDebugLevel The new JvmDebugLevel value + */ + public void setJvmDebugLevel( Integer jvmDebugLevel ) + { + this.jvmDebugLevel = jvmDebugLevel; + } + + /** + * Sets whether -keepgenerated is passed to ejbc (that is, the .java source + * files are kept). + * + * @param inValue either 'true' or 'false' + */ + public void setKeepgenerated( String inValue ) + { + this.keepgenerated = Boolean.valueOf( inValue ).booleanValue(); + } + + /** + * Setter used to store the value of keepGeneric + * + * @param inValue a string, either 'true' or 'false'. + */ + public void setKeepgeneric( boolean inValue ) + { + this.keepGeneric = inValue; + } + + /** + * Set the value of the newCMP scheme. The old CMP scheme locates the + * weblogic CMP descriptor based on the naming convention where the weblogic + * CMP file is expected to be named with the bean name as the prefix. Under + * this scheme the name of the CMP descriptor does not match the name + * actually used in the main weblogic EJB descriptor. Also, descriptors + * which contain multiple CMP references could not be used. + * + * @param newCMP The new NewCMP value + */ + public void setNewCMP( boolean newCMP ) + { + this.newCMP = newCMP; + } + + /** + * Do not EJBC the jar after it has been put together. + * + * @param noEJBC The new NoEJBC value + */ + public void setNoEJBC( boolean noEJBC ) + { + this.noEJBC = noEJBC; + } + + /** + * Set the value of the oldCMP scheme. This is an antonym for newCMP + * + * @param oldCMP The new OldCMP value + */ + public void setOldCMP( boolean oldCMP ) + { + this.newCMP = !oldCMP; + } + + /** + * Set the rebuild flag to false to only update changes in the jar rather + * than rerunning ejbc + * + * @param rebuild The new Rebuild value + */ + public void setRebuild( boolean rebuild ) + { + this.alwaysRebuild = rebuild; + } + + + /** + * Setter used to store the suffix for the generated weblogic jar file. + * + * @param inString the string to use as the suffix. + */ + public void setSuffix( String inString ) + { + this.jarSuffix = inString; + } + + public void setWLClasspath( Path wlClasspath ) + { + this.wlClasspath = wlClasspath; + } + + /** + * Setter used to store the location of the weblogic DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setWLdtd( String inString ) + { + this.weblogicDTD = inString; + } + + + /** + * Setter used to store the location of the ejb-jar DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setWeblogicdtd( String inString ) + { + setEJBdtd( inString ); + } + + /** + * Get the ejbc compiler class + * + * @return The EjbcClass value + */ + public String getEjbcClass() + { + return ejbcClass; + } + + public Integer getJvmDebugLevel() + { + return jvmDebugLevel; + } + + /** + * Get the classpath to the weblogic classpaths + * + * @return Description of the Returned Value + */ + public Path createWLClasspath() + { + if( wlClasspath == null ) + { + wlClasspath = new Path( getTask().getProject() ); + } + return wlClasspath.createPath(); + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + public void validateConfigured() + throws BuildException + { + super.validateConfigured(); + } + + /** + * Helper method invoked by isRebuildRequired to get a ClassLoader for a Jar + * File passed to it. + * + * @param classjar java.io.File representing jar file to get classes from. + * @return The ClassLoaderFromJar value + * @exception IOException Description of Exception + */ + protected ClassLoader getClassLoaderFromJar( File classjar ) + throws IOException + { + Path lookupPath = new Path( getTask().getProject() ); + lookupPath.setLocation( classjar ); + + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + lookupPath.append( classpath ); + } + + return new AntClassLoader( getTask().getProject(), lookupPath ); + } + + protected DescriptorHandler getWeblogicDescriptorHandler( final File srcDir ) + { + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + protected void processElement() + { + if( currentElement.equals( "type-storage" ) ) + { + // Get the filename of vendor specific descriptor + String fileNameWithMETA = currentText; + //trim the META_INF\ off of the file name + String fileName = fileNameWithMETA.substring( META_DIR.length(), + fileNameWithMETA.length() ); + File descriptorFile = new File( srcDir, fileName ); + + ejbFiles.put( fileNameWithMETA, descriptorFile ); + } + } + }; + + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, DEFAULT_WL51_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, DEFAULT_WL60_51_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB600, DEFAULT_WL60_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, weblogicDTD ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB600, weblogicDTD ); + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + + /** + * Helper method to check to see if a weblogic EBJ1.1 jar needs to be + * rebuilt using ejbc. Called from writeJar it sees if the "Bean" classes + * are the only thing that needs to be updated and either updates the Jar + * with the Bean classfile or returns true, saying that the whole weblogic + * jar needs to be regened with ejbc. This allows faster build times for + * working developers.

      + * + * The way weblogic ejbc works is it creates wrappers for the publicly + * defined methods as they are exposed in the remote interface. If the + * actual bean changes without changing the the method signatures then only + * the bean classfile needs to be updated and the rest of the weblogic jar + * file can remain the same. If the Interfaces, ie. the method signatures + * change or if the xml deployment dicriptors changed, the whole jar needs + * to be rebuilt with ejbc. This is not strictly true for the xml files. If + * the JNDI name changes then the jar doesnt have to be rebuild, but if the + * resources references change then it does. At this point the weblogic jar + * gets rebuilt if the xml files change at all. + * + * @param genericJarFile java.io.File The generic jar file. + * @param weblogicJarFile java.io.File The weblogic jar file to check to see + * if it needs to be rebuilt. + * @return The RebuildRequired value + */ + protected boolean isRebuildRequired( File genericJarFile, File weblogicJarFile ) + { + boolean rebuild = false; + + JarFile genericJar = null; + JarFile wlJar = null; + File newWLJarFile = null; + JarOutputStream newJarStream = null; + + try + { + log( "Checking if weblogic Jar needs to be rebuilt for jar " + weblogicJarFile.getName(), + Project.MSG_VERBOSE ); + // Only go forward if the generic and the weblogic file both exist + if( genericJarFile.exists() && genericJarFile.isFile() + && weblogicJarFile.exists() && weblogicJarFile.isFile() ) + { + //open jar files + genericJar = new JarFile( genericJarFile ); + wlJar = new JarFile( weblogicJarFile ); + + Hashtable genericEntries = new Hashtable(); + Hashtable wlEntries = new Hashtable(); + Hashtable replaceEntries = new Hashtable(); + + //get the list of generic jar entries + for( Enumeration e = genericJar.entries(); e.hasMoreElements(); ) + { + JarEntry je = ( JarEntry )e.nextElement(); + genericEntries.put( je.getName().replace( '\\', '/' ), je ); + } + //get the list of weblogic jar entries + for( Enumeration e = wlJar.entries(); e.hasMoreElements(); ) + { + JarEntry je = ( JarEntry )e.nextElement(); + wlEntries.put( je.getName(), je ); + } + + //Cycle Through generic and make sure its in weblogic + ClassLoader genericLoader = getClassLoaderFromJar( genericJarFile ); + for( Enumeration e = genericEntries.keys(); e.hasMoreElements(); ) + { + String filepath = ( String )e.nextElement(); + if( wlEntries.containsKey( filepath ) ) + {// File name/path match + + // Check files see if same + JarEntry genericEntry = ( JarEntry )genericEntries.get( filepath ); + JarEntry wlEntry = ( JarEntry )wlEntries.get( filepath ); + if( ( genericEntry.getCrc() != wlEntry.getCrc() ) || // Crc's Match + ( genericEntry.getSize() != wlEntry.getSize() ) ) + {// Size Match + + if( genericEntry.getName().endsWith( ".class" ) ) + { + //File are different see if its an object or an interface + String classname = genericEntry.getName().replace( File.separatorChar, '.' ); + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + Class genclass = genericLoader.loadClass( classname ); + if( genclass.isInterface() ) + { + //Interface changed rebuild jar. + log( "Interface " + genclass.getName() + " has changed", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + else + { + //Object class Changed update it. + replaceEntries.put( filepath, genericEntry ); + } + } + else + { + // is it the manifest. If so ignore it + if( !genericEntry.getName().equals( "META-INF/MANIFEST.MF" ) ) + { + //File other then class changed rebuild + log( "Non class file " + genericEntry.getName() + " has changed", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + } + } + } + else + {// a file doesnt exist rebuild + + log( "File " + filepath + " not present in weblogic jar", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + } + + if( !rebuild ) + { + log( "No rebuild needed - updating jar", Project.MSG_VERBOSE ); + newWLJarFile = new File( weblogicJarFile.getAbsolutePath() + ".temp" ); + if( newWLJarFile.exists() ) + { + newWLJarFile.delete(); + } + + newJarStream = new JarOutputStream( new FileOutputStream( newWLJarFile ) ); + newJarStream.setLevel( 0 ); + + //Copy files from old weblogic jar + for( Enumeration e = wlEntries.elements(); e.hasMoreElements(); ) + { + byte[] buffer = new byte[1024]; + int bytesRead; + InputStream is; + JarEntry je = ( JarEntry )e.nextElement(); + if( je.getCompressedSize() == -1 || + je.getCompressedSize() == je.getSize() ) + { + newJarStream.setLevel( 0 ); + } + else + { + newJarStream.setLevel( 9 ); + } + + // Update with changed Bean class + if( replaceEntries.containsKey( je.getName() ) ) + { + log( "Updating Bean class from generic Jar " + je.getName(), Project.MSG_VERBOSE ); + // Use the entry from the generic jar + je = ( JarEntry )replaceEntries.get( je.getName() ); + is = genericJar.getInputStream( je ); + } + else + {//use fle from original weblogic jar + + is = wlJar.getInputStream( je ); + } + newJarStream.putNextEntry( new JarEntry( je.getName() ) ); + + while( ( bytesRead = is.read( buffer ) ) != -1 ) + { + newJarStream.write( buffer, 0, bytesRead ); + } + is.close(); + } + } + else + { + log( "Weblogic Jar rebuild needed due to changed interface or XML", Project.MSG_VERBOSE ); + } + } + else + { + rebuild = true; + } + } + catch( ClassNotFoundException cnfe ) + { + String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + + ". Details: " + + cnfe.getMessage(); + throw new BuildException( cnfmsg, cnfe ); + } + catch( IOException ioe ) + { + String msg = "IOException while processing ejb-jar file " + + ". Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + finally + { + // need to close files and perhaps rename output + if( genericJar != null ) + { + try + { + genericJar.close(); + } + catch( IOException closeException ) + {} + } + + if( wlJar != null ) + { + try + { + wlJar.close(); + } + catch( IOException closeException ) + {} + } + + if( newJarStream != null ) + { + try + { + newJarStream.close(); + } + catch( IOException closeException ) + {} + + weblogicJarFile.delete(); + newWLJarFile.renameTo( weblogicJarFile ); + if( !weblogicJarFile.exists() ) + { + rebuild = true; + } + } + } + + return rebuild; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + File weblogicDD = new File( getConfig().descriptorDir, ddPrefix + WL_DD ); + + if( weblogicDD.exists() ) + { + ejbFiles.put( META_DIR + WL_DD, + weblogicDD ); + } + else + { + log( "Unable to locate weblogic deployment descriptor. It was expected to be in " + + weblogicDD.getPath(), Project.MSG_WARN ); + return; + } + + if( !newCMP ) + { + log( "The old method for locating CMP files has been DEPRECATED.", Project.MSG_VERBOSE ); + log( "Please adjust your weblogic descriptor and set newCMP=\"true\" " + + "to use the new CMP descriptor inclusion mechanism. ", Project.MSG_VERBOSE ); + // The the weblogic cmp deployment descriptor + File weblogicCMPDD = new File( getConfig().descriptorDir, ddPrefix + WL_CMP_DD ); + + if( weblogicCMPDD.exists() ) + { + ejbFiles.put( META_DIR + WL_CMP_DD, + weblogicCMPDD ); + } + } + else + { + // now that we have the weblogic descriptor, we parse the file + // to find other descriptors needed to deploy the bean. + // this could be the weblogic-cmp-rdbms.xml or any other O/R + // mapping tool descriptors. + try + { + File ejbDescriptor = ( File )ejbFiles.get( META_DIR + EJB_DD ); + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + SAXParser saxParser = saxParserFactory.newSAXParser(); + DescriptorHandler handler = getWeblogicDescriptorHandler( ejbDescriptor.getParentFile() ); + saxParser.parse( new InputSource + ( new FileInputStream + ( weblogicDD ) ), + handler ); + + Hashtable ht = handler.getFiles(); + Enumeration e = ht.keys(); + while( e.hasMoreElements() ) + { + String key = ( String )e.nextElement(); + ejbFiles.put( key, ht.get( key ) ); + } + } + catch( Exception e ) + { + String msg = "Exception while adding Vendor specific files: " + e.toString(); + throw new BuildException( msg, e ); + } + } + } + + protected void registerKnownDTDs( DescriptorHandler handler ) + { + // register all the known DTDs + handler.registerDTD( PUBLICID_EJB11, DEFAULT_WL51_EJB11_DTD_LOCATION ); + handler.registerDTD( PUBLICID_EJB11, DEFAULT_WL60_EJB11_DTD_LOCATION ); + handler.registerDTD( PUBLICID_EJB11, ejb11DTD ); + handler.registerDTD( PUBLICID_EJB20, DEFAULT_WL60_EJB20_DTD_LOCATION ); + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarFile, Hashtable files, + String publicId ) + throws BuildException + { + // need to create a generic jar first. + File genericJarFile = super.getVendorOutputJarFile( baseName ); + super.writeJar( baseName, genericJarFile, files, publicId ); + + if( alwaysRebuild || isRebuildRequired( genericJarFile, jarFile ) ) + { + buildWeblogicJar( genericJarFile, jarFile, publicId ); + } + if( !keepGeneric ) + { + log( "deleting generic jar " + genericJarFile.toString(), + Project.MSG_VERBOSE ); + genericJarFile.delete(); + } + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } + + /** + * Helper method invoked by execute() for each WebLogic jar to be built. + * Encapsulates the logic of constructing a java task for calling + * weblogic.ejbc and executing it. + * + * @param sourceJar java.io.File representing the source (EJB1.1) jarfile. + * @param destJar java.io.File representing the destination, WebLogic + * jarfile. + * @param publicId Description of Parameter + */ + private void buildWeblogicJar( File sourceJar, File destJar, String publicId ) + { + org.apache.tools.ant.taskdefs.Java javaTask = null; + + if( noEJBC ) + { + try + { + getTask().getProject().copyFile( sourceJar, destJar ); + if( !keepgenerated ) + { + sourceJar.delete(); + } + return; + } + catch( IOException e ) + { + throw new BuildException( "Unable to write EJB jar", e ); + } + } + + String ejbcClassName = ejbcClass; + + try + { + javaTask = ( Java )getTask().getProject().createTask( "java" ); + javaTask.setTaskName( "ejbc" ); + + if( getJvmDebugLevel() != null ) + { + javaTask.createJvmarg().setLine( " -Dweblogic.StdoutSeverityLevel=" + jvmDebugLevel ); + } + + if( ejbcClassName == null ) + { + // try to determine it from publicId + if( PUBLICID_EJB11.equals( publicId ) ) + { + ejbcClassName = COMPILER_EJB11; + } + else if( PUBLICID_EJB20.equals( publicId ) ) + { + ejbcClassName = COMPILER_EJB20; + } + else + { + log( "Unrecognized publicId " + publicId + " - using EJB 1.1 compiler", Project.MSG_WARN ); + ejbcClassName = COMPILER_EJB11; + } + } + + javaTask.setClassname( ejbcClassName ); + javaTask.createArg().setLine( additionalArgs ); + if( keepgenerated ) + { + javaTask.createArg().setValue( "-keepgenerated" ); + } + if( compiler == null ) + { + // try to use the compiler specified by build.compiler. Right now we are just going + // to allow Jikes + String buildCompiler = getTask().getProject().getProperty( "build.compiler" ); + if( buildCompiler != null && buildCompiler.equals( "jikes" ) ) + { + javaTask.createArg().setValue( "-compiler" ); + javaTask.createArg().setValue( "jikes" ); + } + } + else + { + if( !compiler.equals( DEFAULT_COMPILER ) ) + { + javaTask.createArg().setValue( "-compiler" ); + javaTask.createArg().setLine( compiler ); + } + } + javaTask.createArg().setValue( sourceJar.getPath() ); + javaTask.createArg().setValue( destJar.getPath() ); + + Path classpath = wlClasspath; + if( classpath == null ) + { + classpath = getCombinedClasspath(); + } + + javaTask.setFork( true ); + if( classpath != null ) + { + javaTask.setClasspath( classpath ); + } + + log( "Calling " + ejbcClassName + " for " + sourceJar.toString(), + Project.MSG_VERBOSE ); + + if( javaTask.executeJava() != 0 ) + { + throw new BuildException( "Ejbc reported an error" ); + } + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling " + ejbcClassName + ". Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java new file mode 100644 index 000000000..3fab8bd54 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +public class WeblogicTOPLinkDeploymentTool extends WeblogicDeploymentTool +{ + + private final static String TL_DTD_LOC = "http://www.objectpeople.com/tlwl/dtd/toplink-cmp_2_5_1.dtd"; + private String toplinkDTD; + + private String toplinkDescriptor; + + /** + * Setter used to store the name of the toplink descriptor. + * + * @param inString the string to use as the descriptor name. + */ + public void setToplinkdescriptor( String inString ) + { + this.toplinkDescriptor = inString; + } + + /** + * Setter used to store the location of the toplink DTD file. This is + * expected to be an URL (file or otherwise). If running this on NT using a + * file URL, the safest thing would be to not use a drive spec in the URL + * and make sure the file resides on the drive that ANT is running from. + * This will keep the setting in the build XML platform independent. + * + * @param inString the string to use as the DTD location. + */ + public void setToplinkdtd( String inString ) + { + this.toplinkDTD = inString; + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + public void validateConfigured() + throws BuildException + { + super.validateConfigured(); + if( toplinkDescriptor == null ) + { + throw new BuildException( "The toplinkdescriptor attribute must be specified" ); + } + } + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + DescriptorHandler handler = super.getDescriptorHandler( srcDir ); + if( toplinkDTD != null ) + { + handler.registerDTD( "-//The Object People, Inc.//DTD TOPLink for WebLogic CMP 2.5.1//EN", + toplinkDTD ); + } + else + { + handler.registerDTD( "-//The Object People, Inc.//DTD TOPLink for WebLogic CMP 2.5.1//EN", + TL_DTD_LOC ); + } + return handler; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + super.addVendorFiles( ejbFiles, ddPrefix ); + // Then the toplink deployment descriptor + + // Setup a naming standard here?. + + + File toplinkDD = new File( getConfig().descriptorDir, ddPrefix + toplinkDescriptor ); + + if( toplinkDD.exists() ) + { + ejbFiles.put( META_DIR + toplinkDescriptor, + toplinkDD ); + } + else + { + log( "Unable to locate toplink deployment descriptor. It was expected to be in " + + toplinkDD.getPath(), Project.MSG_WARN ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java new file mode 100644 index 000000000..8272c3e20 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java @@ -0,0 +1,1633 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.jar.*; +import javax.xml.parsers.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.taskdefs.*; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.types.*; +import org.xml.sax.*; + + +/** + * Websphere deployment tool that augments the ejbjar task. + * + * @author Maneesh Sahu + */ + +public class WebsphereDeploymentTool extends GenericDeploymentTool +{ + + + + public final static String PUBLICID_EJB11 + + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + + public final static String PUBLICID_EJB20 + + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"; + + protected final static String SCHEMA_DIR = "Schema/"; + + + + protected final static String WAS_EXT = "ibm-ejb-jar-ext.xmi"; + + protected final static String WAS_BND = "ibm-ejb-jar-bnd.xmi"; + + protected final static String WAS_CMP_MAP = "Map.mapxmi"; + + protected final static String WAS_CMP_SCHEMA = "Schema.dbxmi"; + + + + /** + * Instance variable that stores the suffix for the websphere jarfile. + */ + + private String jarSuffix = ".jar"; + + + + /** + * Instance variable that determines whether generic ejb jars are kept. + */ + + + + private boolean keepgenerated = false; + + + + private String additionalArgs = ""; + + + + private boolean keepGeneric = false; + + + + private String compiler = null; + + + + private boolean alwaysRebuild = true; + + + + private boolean ejbdeploy = true; + + + + /** + * Indicates if the old CMP location convention is to be used. + */ + + private boolean newCMP = false; + + + + /** + * The classpath to the websphere classes. + */ + + private Path wasClasspath = null; + + + + /** + * true - Only output error messages, suppress informational messages + */ + + private boolean quiet = true; + + + + /** + * the scratchdir for the ejbdeploy operation + */ + + private String tempdir = "_ejbdeploy_temp"; + + + + /** + * true - Only generate the deployment code, do not run RMIC or Javac + */ + + private boolean codegen; + + + + /** + * The name of the database to create. (For top-down mapping only) + */ + + private String dbName; + + + + /** + * The name of the schema to create. (For top-down mappings only) + */ + + private String dbSchema; + + + + /** + * The DB Vendor name, the EJB is persisted against + */ + + private String dbVendor; + + + + /** + * Instance variable that stores the location of the ejb 1.1 DTD file. + */ + + private String ejb11DTD; + + + + /** + * true - Disable informational messages + */ + + private boolean noinform; + + + + /** + * true - Disable the validation steps + */ + + private boolean novalidate; + + + + /** + * true - Disable warning and informational messages + */ + + private boolean nowarn; + + + + /** + * Additional options for RMIC + */ + + private String rmicOptions; + + + + /** + * true - Enable internal tracing + */ + + private boolean trace; + + + + /** + * true- Use the WebSphere 3.5 compatible mapping rules + */ + + private boolean use35MappingRules; + + + + /** + * sets some additional args to send to ejbdeploy. + * + * @param args The new Args value + */ + + public void setArgs( String args ) + { + + this.additionalArgs = args; + + } + + + + /** + * (true) Only generate the deployment code, do not run RMIC or Javac + * + * @param codegen The new Codegen value + */ + + public void setCodegen( boolean codegen ) + { + + this.codegen = codegen; + + } + + + + /** + * The compiler (switch -compiler) to use + * + * @param compiler The new Compiler value + */ + + public void setCompiler( String compiler ) + { + + this.compiler = compiler; + + } + + + + /** + * Sets the name of the Database to create + * + * @param dbName The new Dbname value + */ + + public void setDbname( String dbName ) + { + + this.dbName = dbName; + + } + + + + /** + * Sets the name of the schema to create + * + * @param dbSchema The new Dbschema value + */ + + public void setDbschema( String dbSchema ) + { + + this.dbSchema = dbSchema; + + } + + + + /** + * Sets the DB Vendor for the Entity Bean mapping + * + * @param dbvendor The new Dbvendor value + */ + + public void setDbvendor( DBVendor dbvendor ) + { + + this.dbVendor = dbvendor.getValue(); + + } + + + + /** + * Setter used to store the location of the Sun's Generic EJB DTD. This can + * be a file on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + + public void setEJBdtd( String inString ) + { + + this.ejb11DTD = inString; + + } + + + + /** + * Decide, wether ejbdeploy should be called or not + * + * @param ejbdeploy + */ + + public void setEjbdeploy( boolean ejbdeploy ) + { + + this.ejbdeploy = ejbdeploy; + + } + + + + /** + * Sets whether -keepgenerated is passed to ejbdeploy (that is, the .java + * source files are kept). + * + * @param inValue either 'true' or 'false' + */ + + public void setKeepgenerated( String inValue ) + { + + this.keepgenerated = Boolean.valueOf( inValue ).booleanValue(); + + } + + + + /** + * Setter used to store the value of keepGeneric + * + * @param inValue a string, either 'true' or 'false'. + */ + + public void setKeepgeneric( boolean inValue ) + { + + this.keepGeneric = inValue; + + } + + + + /** + * Set the value of the newCMP scheme. The old CMP scheme locates the + * websphere CMP descriptor based on the naming convention where the + * websphere CMP file is expected to be named with the bean name as the + * prefix. Under this scheme the name of the CMP descriptor does not match + * the name actually used in the main websphere EJB descriptor. Also, + * descriptors which contain multiple CMP references could not be used. + * + * @param newCMP The new NewCMP value + */ + + public void setNewCMP( boolean newCMP ) + { + + this.newCMP = newCMP; + + } + + + + /** + * (true) Disable informational messages + * + * @param noinfom The new Noinform value + */ + + public void setNoinform( boolean noinfom ) + { + + this.noinform = noinform; + + } + + + + /** + * (true) Disable the validation steps + * + * @param novalidate The new Novalidate value + */ + + public void setNovalidate( boolean novalidate ) + { + + this.novalidate = novalidate; + + } + + + + /** + * (true) Disable warning and informational messages + * + * @param nowarn The new Nowarn value + */ + + public void setNowarn( boolean nowarn ) + { + + this.nowarn = nowarn; + + } + + + + /** + * Set the value of the oldCMP scheme. This is an antonym for newCMP + * + * @param oldCMP The new OldCMP value + */ + + public void setOldCMP( boolean oldCMP ) + { + + this.newCMP = !oldCMP; + + } + + + + /** + * (true) Only output error messages, suppress informational messages + * + * @param quiet The new Quiet value + */ + + public void setQuiet( boolean quiet ) + { + + this.quiet = quiet; + + } + + + + /** + * Set the rebuild flag to false to only update changes in the jar rather + * than rerunning ejbdeploy + * + * @param rebuild The new Rebuild value + */ + + public void setRebuild( boolean rebuild ) + { + + this.alwaysRebuild = rebuild; + + } + + + + + + /** + * Setter used to store the suffix for the generated websphere jar file. + * + * @param inString the string to use as the suffix. + */ + + public void setSuffix( String inString ) + { + + this.jarSuffix = inString; + + } + + + + /** + * Sets the temporary directory for the ejbdeploy task + * + * @param tempdir The new Tempdir value + */ + + public void setTempdir( String tempdir ) + { + + this.tempdir = tempdir; + + } + + + + /** + * (true) Enable internal tracing + * + * @param trace The new Trace value + */ + + public void setTrace( boolean trace ) + { + + this.trace = trace; + + } + + + + /** + * (true) Use the WebSphere 3.5 compatible mapping rules + * + * @param attr The new Use35 value + */ + + public void setUse35( boolean attr ) + { + + use35MappingRules = attr; + + } + + + + public void setWASClasspath( Path wasClasspath ) + { + + this.wasClasspath = wasClasspath; + + } + + + + /** + * Get the classpath to the websphere classpaths + * + * @return Description of the Returned Value + */ + + public Path createWASClasspath() + { + + if( wasClasspath == null ) + { + + wasClasspath = new Path( getTask().getProject() ); + + } + + return wasClasspath.createPath(); + + } + + + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + + public void validateConfigured() + throws BuildException + { + + super.validateConfigured(); + + } + + + + /** + * Helper method invoked by isRebuildRequired to get a ClassLoader for a Jar + * File passed to it. + * + * @param classjar java.io.File representing jar file to get classes from. + * @return The ClassLoaderFromJar value + * @exception IOException Description of Exception + */ + + protected ClassLoader getClassLoaderFromJar( File classjar ) + throws IOException + { + + Path lookupPath = new Path( getTask().getProject() ); + + lookupPath.setLocation( classjar ); + + + + Path classpath = getCombinedClasspath(); + + if( classpath != null ) + { + + lookupPath.append( classpath ); + + } + + + + return new AntClassLoader( getTask().getProject(), lookupPath ); + + } + + + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + + DescriptorHandler handler = new DescriptorHandler( getTask(), srcDir ); + + // register all the DTDs, both the ones that are known and + + + // any supplied by the user + + handler.registerDTD( PUBLICID_EJB11, ejb11DTD ); + + + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + + } + + + + return handler; + + } + + + + /** + * Gets the options for the EJB Deploy operation + * + * @return String + */ + + protected String getOptions() + { + + // Set the options + + + StringBuffer options = new StringBuffer(); + + if( dbVendor != null ) + { + + options.append( " -dbvendor " ).append( dbVendor ); + + } + + if( dbName != null ) + { + + options.append( " -dbname \"" ).append( dbName ).append( "\"" ); + + } + + + + if( dbSchema != null ) + { + + options.append( " -dbschema \"" ).append( dbSchema ).append( "\"" ); + + } + + + + if( codegen ) + { + + options.append( " -codegen" ); + + } + + + + if( quiet ) + { + + options.append( " -quiet" ); + + } + + + + if( novalidate ) + { + + options.append( " -novalidate" ); + + } + + + + if( nowarn ) + { + + options.append( " -nowarn" ); + + } + + + + if( noinform ) + { + + options.append( " -noinform" ); + + } + + + + if( trace ) + { + + options.append( " -trace" ); + + } + + + + if( use35MappingRules ) + { + + options.append( " -35" ); + + } + + + + if( rmicOptions != null ) + { + + options.append( " -rmic \"" ).append( rmicOptions ).append( "\"" ); + + } + + + + return options.toString(); + + } + + + + protected DescriptorHandler getWebsphereDescriptorHandler( final File srcDir ) + { + + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + + protected void processElement() { } + + }; + + + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + + } + + return handler; + + } + + + + + + /** + * Helper method to check to see if a websphere EBJ1.1 jar needs to be + * rebuilt using ejbdeploy. Called from writeJar it sees if the "Bean" + * classes are the only thing that needs to be updated and either updates + * the Jar with the Bean classfile or returns true, saying that the whole + * websphere jar needs to be regened with ejbdeploy. This allows faster + * build times for working developers.

      + * + * The way websphere ejbdeploy works is it creates wrappers for the publicly + * defined methods as they are exposed in the remote interface. If the + * actual bean changes without changing the the method signatures then only + * the bean classfile needs to be updated and the rest of the websphere jar + * file can remain the same. If the Interfaces, ie. the method signatures + * change or if the xml deployment dicriptors changed, the whole jar needs + * to be rebuilt with ejbdeploy. This is not strictly true for the xml + * files. If the JNDI name changes then the jar doesnt have to be rebuild, + * but if the resources references change then it does. At this point the + * websphere jar gets rebuilt if the xml files change at all. + * + * @param genericJarFile java.io.File The generic jar file. + * @param websphereJarFile java.io.File The websphere jar file to check to + * see if it needs to be rebuilt. + * @return The RebuildRequired value + */ + + protected boolean isRebuildRequired( File genericJarFile, File websphereJarFile ) + { + + boolean rebuild = false; + + + + JarFile genericJar = null; + + JarFile wasJar = null; + + File newwasJarFile = null; + + JarOutputStream newJarStream = null; + + + + try + { + + log( "Checking if websphere Jar needs to be rebuilt for jar " + websphereJarFile.getName(), + + Project.MSG_VERBOSE ); + + // Only go forward if the generic and the websphere file both exist + + + if( genericJarFile.exists() && genericJarFile.isFile() + + && websphereJarFile.exists() && websphereJarFile.isFile() ) + { + + //open jar files + + + genericJar = new JarFile( genericJarFile ); + + wasJar = new JarFile( websphereJarFile ); + + + + Hashtable genericEntries = new Hashtable(); + + Hashtable wasEntries = new Hashtable(); + + Hashtable replaceEntries = new Hashtable(); + + + + //get the list of generic jar entries + + for( Enumeration e = genericJar.entries(); e.hasMoreElements(); ) + { + + JarEntry je = ( JarEntry )e.nextElement(); + + genericEntries.put( je.getName().replace( '\\', '/' ), je ); + + } + + //get the list of websphere jar entries + + + for( Enumeration e = wasJar.entries(); e.hasMoreElements(); ) + { + + JarEntry je = ( JarEntry )e.nextElement(); + + wasEntries.put( je.getName(), je ); + + } + + + + //Cycle Through generic and make sure its in websphere + + ClassLoader genericLoader = getClassLoaderFromJar( genericJarFile ); + + for( Enumeration e = genericEntries.keys(); e.hasMoreElements(); ) + { + + String filepath = ( String )e.nextElement(); + + if( wasEntries.containsKey( filepath ) ) + {// File name/path match + + + // Check files see if same + + JarEntry genericEntry = ( JarEntry )genericEntries.get( filepath ); + + JarEntry wasEntry = ( JarEntry )wasEntries.get( filepath ); + + if( ( genericEntry.getCrc() != wasEntry.getCrc() ) || // Crc's Match + + ( genericEntry.getSize() != wasEntry.getSize() ) ) + {// Size Match + + + if( genericEntry.getName().endsWith( ".class" ) ) + { + + //File are different see if its an object or an interface + + + String classname = genericEntry.getName().replace( File.separatorChar, '.' ); + + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + + Class genclass = genericLoader.loadClass( classname ); + + if( genclass.isInterface() ) + { + + //Interface changed rebuild jar. + + + log( "Interface " + genclass.getName() + " has changed", Project.MSG_VERBOSE ); + + rebuild = true; + + break; + + } + + else + { + + //Object class Changed update it. + + + replaceEntries.put( filepath, genericEntry ); + + } + + } + + else + { + + // is it the manifest. If so ignore it + + + if( !genericEntry.getName().equals( "META-INF/MANIFEST.MF" ) ) + { + + //File other then class changed rebuild + + + log( "Non class file " + genericEntry.getName() + " has changed", Project.MSG_VERBOSE ); + + rebuild = true; + + } + + break; + + } + + } + + } + + else + {// a file doesnt exist rebuild + + + log( "File " + filepath + " not present in websphere jar", Project.MSG_VERBOSE ); + + rebuild = true; + + break; + + } + + } + + + + if( !rebuild ) + { + + log( "No rebuild needed - updating jar", Project.MSG_VERBOSE ); + + newwasJarFile = new File( websphereJarFile.getAbsolutePath() + ".temp" ); + + if( newwasJarFile.exists() ) + { + + newwasJarFile.delete(); + + } + + + + newJarStream = new JarOutputStream( new FileOutputStream( newwasJarFile ) ); + + newJarStream.setLevel( 0 ); + + + + //Copy files from old websphere jar + + for( Enumeration e = wasEntries.elements(); e.hasMoreElements(); ) + { + + byte[] buffer = new byte[1024]; + + int bytesRead; + + InputStream is; + + JarEntry je = ( JarEntry )e.nextElement(); + + if( je.getCompressedSize() == -1 || + + je.getCompressedSize() == je.getSize() ) + { + + newJarStream.setLevel( 0 ); + + } + + else + { + + newJarStream.setLevel( 9 ); + + } + + + + // Update with changed Bean class + + if( replaceEntries.containsKey( je.getName() ) ) + { + + log( "Updating Bean class from generic Jar " + je.getName(), + + Project.MSG_VERBOSE ); + + // Use the entry from the generic jar + + + je = ( JarEntry )replaceEntries.get( je.getName() ); + + is = genericJar.getInputStream( je ); + + } + + else + {//use fle from original websphere jar + + + is = wasJar.getInputStream( je ); + + } + + newJarStream.putNextEntry( new JarEntry( je.getName() ) ); + + + + while( ( bytesRead = is.read( buffer ) ) != -1 ) + { + + newJarStream.write( buffer, 0, bytesRead ); + + } + + is.close(); + + } + + } + + else + { + + log( "websphere Jar rebuild needed due to changed interface or XML", Project.MSG_VERBOSE ); + + } + + } + + else + { + + rebuild = true; + + } + + } + + catch( ClassNotFoundException cnfe ) + { + + String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + + + ". Details: " + + + cnfe.getMessage(); + + throw new BuildException( cnfmsg, cnfe ); + + } + + catch( IOException ioe ) + { + + String msg = "IOException while processing ejb-jar file " + + + ". Details: " + + + ioe.getMessage(); + + throw new BuildException( msg, ioe ); + + } + + finally + { + + // need to close files and perhaps rename output + + + if( genericJar != null ) + { + + try + { + + genericJar.close(); + + } + + catch( IOException closeException ) + {} + + } + + + + if( wasJar != null ) + { + + try + { + + wasJar.close(); + + } + + catch( IOException closeException ) + {} + + } + + + + if( newJarStream != null ) + { + + try + { + + newJarStream.close(); + + } + + catch( IOException closeException ) + {} + + + + websphereJarFile.delete(); + + newwasJarFile.renameTo( websphereJarFile ); + + if( !websphereJarFile.exists() ) + { + + rebuild = true; + + } + + } + + } + + + + return rebuild; + + } + + + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param baseName The feature to be added to the VendorFiles attribute + */ + + protected void addVendorFiles( Hashtable ejbFiles, String baseName ) + { + + + + String ddPrefix = ( usingBaseJarName() ? "" : baseName ); + + String dbPrefix = ( dbVendor == null ) ? "" : dbVendor + "-"; + + + + // Get the Extensions document + + File websphereEXT = new File( getConfig().descriptorDir, ddPrefix + WAS_EXT ); + + if( websphereEXT.exists() ) + { + + ejbFiles.put( META_DIR + WAS_EXT, + + websphereEXT ); + + } + else + { + + log( "Unable to locate websphere extensions. It was expected to be in " + + + websphereEXT.getPath(), Project.MSG_VERBOSE ); + + } + + + + File websphereBND = new File( getConfig().descriptorDir, ddPrefix + WAS_BND ); + + if( websphereBND.exists() ) + { + + ejbFiles.put( META_DIR + WAS_BND, + + websphereBND ); + + } + else + { + + log( "Unable to locate websphere bindings. It was expected to be in " + + + websphereBND.getPath(), Project.MSG_VERBOSE ); + + } + + + + if( !newCMP ) + { + + log( "The old method for locating CMP files has been DEPRECATED.", Project.MSG_VERBOSE ); + + log( "Please adjust your websphere descriptor and set newCMP=\"true\" " + + + "to use the new CMP descriptor inclusion mechanism. ", Project.MSG_VERBOSE ); + + } + + else + { + + // We attempt to put in the MAP and Schema files of CMP beans + + + try + { + + // Add the Map file + + + File websphereMAP = new File( getConfig().descriptorDir, + + ddPrefix + dbPrefix + WAS_CMP_MAP ); + + if( websphereMAP.exists() ) + { + + ejbFiles.put( META_DIR + WAS_CMP_MAP, + + websphereMAP ); + + } + else + { + + log( "Unable to locate the websphere Map: " + + + websphereMAP.getPath(), Project.MSG_VERBOSE ); + + } + + File websphereSchema = new File( getConfig().descriptorDir, + + ddPrefix + dbPrefix + WAS_CMP_SCHEMA ); + + if( websphereSchema.exists() ) + { + + ejbFiles.put( META_DIR + SCHEMA_DIR + WAS_CMP_SCHEMA, + + websphereSchema ); + + } + else + { + + log( "Unable to locate the websphere Schema: " + + + websphereSchema.getPath(), Project.MSG_VERBOSE ); + + } + + // Theres nothing else to see here...keep moving sonny + + + } + + catch( Exception e ) + { + + String msg = "Exception while adding Vendor specific files: " + + + e.toString(); + + throw new BuildException( msg, e ); + + } + + } + + } + + + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + + protected void writeJar( String baseName, File jarFile, Hashtable files, String publicId ) + + throws BuildException + { + + if( ejbdeploy ) + { + + // create the -generic.jar, if required + + + File genericJarFile = super.getVendorOutputJarFile( baseName ); + + super.writeJar( baseName, genericJarFile, files, publicId ); + + + + // create the output .jar, if required + + if( alwaysRebuild || isRebuildRequired( genericJarFile, jarFile ) ) + { + + buildWebsphereJar( genericJarFile, jarFile ); + + } + + if( !keepGeneric ) + { + + log( "deleting generic jar " + genericJarFile.toString(), + + Project.MSG_VERBOSE ); + + genericJarFile.delete(); + + } + + } + + else + { + + // create the "undeployed" output .jar, if required + + + super.writeJar( baseName, jarFile, files, publicId ); + + } + + /* + * / need to create a generic jar first. + * File genericJarFile = super.getVendorOutputJarFile(baseName); + * super.writeJar(baseName, genericJarFile, files, publicId); + * if (alwaysRebuild || isRebuildRequired(genericJarFile, jarFile)) { + * buildWebsphereJar(genericJarFile, jarFile); + * } + * if (!keepGeneric) { + * log("deleting generic jar " + genericJarFile.toString(), + * Project.MSG_VERBOSE); + * genericJarFile.delete(); + * } + */ + + } + + + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + + File getVendorOutputJarFile( String baseName ) + { + + return new File( getDestDir(), baseName + jarSuffix ); + + }// end getOptions + + + + /** + * Helper method invoked by execute() for each websphere jar to be built. + * Encapsulates the logic of constructing a java task for calling + * websphere.ejbdeploy and executing it. + * + * @param sourceJar java.io.File representing the source (EJB1.1) jarfile. + * @param destJar java.io.File representing the destination, websphere + * jarfile. + */ + + private void buildWebsphereJar( File sourceJar, File destJar ) + { + + try + { + + if( ejbdeploy ) + { + + String args = + + " " + sourceJar.getPath() + + + " " + tempdir + + + " " + destJar.getPath() + + + " " + getOptions(); + + + + if( getCombinedClasspath() != null && getCombinedClasspath().toString().length() > 0 ) + + args += " -cp " + getCombinedClasspath(); + + + + // Why do my ""'s get stripped away??? + + log( "EJB Deploy Options: " + args, Project.MSG_VERBOSE ); + + + + Java javaTask = ( Java )getTask().getProject().createTask( "java" ); + + // Set the JvmArgs + + + javaTask.createJvmarg().setValue( "-Xms64m" ); + + javaTask.createJvmarg().setValue( "-Xmx128m" ); + + + + // Set the Environment variable + + Environment.Variable var = new Environment.Variable(); + + var.setKey( "websphere.lib.dir" ); + + var.setValue( getTask().getProject().getProperty( "websphere.home" ) + "/lib" ); + + javaTask.addSysproperty( var ); + + + + // Set the working directory + + javaTask.setDir( new File( getTask().getProject().getProperty( "websphere.home" ) ) ); + + + + // Set the Java class name + + javaTask.setTaskName( "ejbdeploy" ); + + javaTask.setClassname( "com.ibm.etools.ejbdeploy.EJBDeploy" ); + + + + Commandline.Argument arguments = javaTask.createArg(); + + arguments.setLine( args ); + + + + Path classpath = wasClasspath; + + if( classpath == null ) + { + + classpath = getCombinedClasspath(); + + } + + + + if( classpath != null ) + { + + javaTask.setClasspath( classpath ); + + javaTask.setFork( true ); + + } + + else + { + + javaTask.setFork( true ); + + } + + + + log( "Calling websphere.ejbdeploy for " + sourceJar.toString(), + + Project.MSG_VERBOSE ); + + + + javaTask.execute(); + + } + + } + + catch( Exception e ) + { + + // Have to catch this because of the semantics of calling main() + + + String msg = "Exception while calling ejbdeploy. Details: " + e.toString(); + + throw new BuildException( msg, e ); + + } + + } + + + /** + * Enumerated attribute with the values for the database vendor types + * + * @author RT + */ + + public static class DBVendor extends EnumeratedAttribute + { + + public String[] getValues() + { + + return new String[]{ + + "SQL92", "SQL99", "DB2UDBWIN_V71", "DB2UDBOS390_V6", "DB2UDBAS400_V4R5", + + "ORACLE_V8", "INFORMIX_V92", "SYBASE_V1192", "MSSQLSERVER_V7", "MYSQL_V323" + + }; + + } + + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java new file mode 100644 index 000000000..ddd1d9e6e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java @@ -0,0 +1,645 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.i18n; +import java.io.*; +import java.util.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.*; +import org.apache.tools.ant.util.*; + +/** + * Translates text embedded in files using Resource Bundle files. + * + * @author Magesh Umasankar + */ +public class Translate extends MatchingTask +{ + /** + * Vector to hold source file sets. + */ + private Vector filesets = new Vector(); + /** + * Holds key value pairs loaded from resource bundle file + */ + private Hashtable resourceMap = new Hashtable(); + /** + * Used to resolve file names. + */ + private FileUtils fileUtils = FileUtils.newFileUtils(); + /** + * Last Modified Timestamp of resource bundle file being used. + */ + private long[] bundleLastModified = new long[7]; + /** + * Has at least one file from the bundle been loaded? + */ + private boolean loaded = false; + + /** + * Family name of resource bundle + */ + private String bundle; + /** + * Locale specific country of the resource bundle + */ + private String bundleCountry; + /** + * Resource Bundle file encoding scheme, defaults to srcEncoding + */ + private String bundleEncoding; + /** + * Locale specific language of the resource bundle + */ + private String bundleLanguage; + /** + * Locale specific variant of the resource bundle + */ + private String bundleVariant; + /** + * Destination file encoding scheme + */ + private String destEncoding; + /** + * Last Modified Timestamp of destination file being used. + */ + private long destLastModified; + /** + * Ending token to identify keys + */ + private String endToken; + /** + * Create new destination file? Defaults to false. + */ + private boolean forceOverwrite; + /** + * Generated locale based on user attributes + */ + private Locale locale; + /** + * Source file encoding scheme + */ + private String srcEncoding; + /** + * Last Modified Timestamp of source file being used. + */ + private long srcLastModified; + /** + * Starting token to identify keys + */ + private String startToken; + /** + * Destination directory + */ + private File toDir; + + /** + * Sets Family name of resource bundle + * + * @param bundle The new Bundle value + */ + public void setBundle( String bundle ) + { + this.bundle = bundle; + } + + /** + * Sets locale specific country of resource bundle + * + * @param bundleCountry The new BundleCountry value + */ + public void setBundleCountry( String bundleCountry ) + { + this.bundleCountry = bundleCountry; + } + + /** + * Sets Resource Bundle file encoding scheme + * + * @param bundleEncoding The new BundleEncoding value + */ + public void setBundleEncoding( String bundleEncoding ) + { + this.bundleEncoding = bundleEncoding; + } + + /** + * Sets locale specific language of resource bundle + * + * @param bundleLanguage The new BundleLanguage value + */ + public void setBundleLanguage( String bundleLanguage ) + { + this.bundleLanguage = bundleLanguage; + } + + /** + * Sets locale specific variant of resource bundle + * + * @param bundleVariant The new BundleVariant value + */ + public void setBundleVariant( String bundleVariant ) + { + this.bundleVariant = bundleVariant; + } + + /** + * Sets destination file encoding scheme. Defaults to source file encoding + * + * @param destEncoding The new DestEncoding value + */ + public void setDestEncoding( String destEncoding ) + { + this.destEncoding = destEncoding; + } + + /** + * Sets ending token to identify keys + * + * @param endToken The new EndToken value + */ + public void setEndToken( String endToken ) + { + this.endToken = endToken; + } + + /** + * Overwrite existing file irrespective of whether it is newer than the + * source file as well as the resource bundle file? Defaults to false. + * + * @param forceOverwrite The new ForceOverwrite value + */ + public void setForceOverwrite( boolean forceOverwrite ) + { + this.forceOverwrite = forceOverwrite; + } + + /** + * Sets source file encoding scheme + * + * @param srcEncoding The new SrcEncoding value + */ + public void setSrcEncoding( String srcEncoding ) + { + this.srcEncoding = srcEncoding; + } + + /** + * Sets starting token to identify keys + * + * @param startToken The new StartToken value + */ + public void setStartToken( String startToken ) + { + this.startToken = startToken; + } + + /** + * Sets Destination directory + * + * @param toDir The new ToDir value + */ + public void setToDir( File toDir ) + { + this.toDir = toDir; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Check attributes values, load resource map and translate + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( bundle == null ) + { + throw new BuildException( "The bundle attribute must be set.", + location ); + } + + if( startToken == null ) + { + throw new BuildException( "The starttoken attribute must be set.", + location ); + } + + if( startToken.length() != 1 ) + { + throw new BuildException( + "The starttoken attribute must be a single character.", + location ); + } + + if( endToken == null ) + { + throw new BuildException( "The endtoken attribute must be set.", + location ); + } + + if( endToken.length() != 1 ) + { + throw new BuildException( + "The endtoken attribute must be a single character.", + location ); + } + + if( bundleLanguage == null ) + { + Locale l = Locale.getDefault(); + bundleLanguage = l.getLanguage(); + } + + if( bundleCountry == null ) + { + bundleCountry = Locale.getDefault().getCountry(); + } + + locale = new Locale( bundleLanguage, bundleCountry ); + + if( bundleVariant == null ) + { + Locale l = new Locale( bundleLanguage, bundleCountry ); + bundleVariant = l.getVariant(); + } + + if( toDir == null ) + { + throw new BuildException( "The todir attribute must be set.", + location ); + } + + if( !toDir.exists() ) + { + toDir.mkdirs(); + } + else + { + if( toDir.isFile() ) + { + throw new BuildException( toDir + " is not a directory" ); + } + } + + if( srcEncoding == null ) + { + srcEncoding = System.getProperty( "file.encoding" ); + } + + if( destEncoding == null ) + { + destEncoding = srcEncoding; + } + + if( bundleEncoding == null ) + { + bundleEncoding = srcEncoding; + } + + loadResourceMaps(); + + translate(); + } + + /** + * Load resourceMap with key value pairs. Values of existing keys are not + * overwritten. Bundle's encoding scheme is used. + * + * @param ins Description of Parameter + * @exception BuildException Description of Exception + */ + private void loadResourceMap( FileInputStream ins ) + throws BuildException + { + try + { + BufferedReader in = null; + InputStreamReader isr = new InputStreamReader( ins, bundleEncoding ); + in = new BufferedReader( isr ); + String line = null; + while( ( line = in.readLine() ) != null ) + { + //So long as the line isn't empty and isn't a comment... + if( line.trim().length() > 1 && + ( '#' != line.charAt( 0 ) || '!' != line.charAt( 0 ) ) ) + { + //Legal Key-Value separators are :, = and white space. + int sepIndex = line.indexOf( '=' ); + if( -1 == sepIndex ) + { + sepIndex = line.indexOf( ':' ); + } + if( -1 == sepIndex ) + { + for( int k = 0; k < line.length(); k++ ) + { + if( Character.isSpaceChar( line.charAt( k ) ) ) + { + sepIndex = k; + break; + } + } + } + //Only if we do have a key is there going to be a value + if( -1 != sepIndex ) + { + String key = line.substring( 0, sepIndex ).trim(); + String value = line.substring( sepIndex + 1 ).trim(); + //Handle line continuations, if any + while( value.endsWith( "\\" ) ) + { + value = value.substring( 0, value.length() - 1 ); + if( ( line = in.readLine() ) != null ) + { + value = value + line.trim(); + } + else + { + break; + } + } + if( key.length() > 0 ) + { + //Has key already been loaded into resourceMap? + if( resourceMap.get( key ) == null ) + { + resourceMap.put( key, value ); + } + } + } + } + } + if( in != null ) + { + in.close(); + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + + /** + * Load resource maps based on resource bundle encoding scheme. The resource + * bundle lookup searches for resource files with various suffixes on the + * basis of (1) the desired locale and (2) the default locale + * (basebundlename), in the following order from lower-level (more specific) + * to parent-level (less specific): basebundlename + "_" + language1 + "_" + + * country1 + "_" + variant1 basebundlename + "_" + language1 + "_" + + * country1 basebundlename + "_" + language1 basebundlename basebundlename + + * "_" + language2 + "_" + country2 + "_" + variant2 basebundlename + "_" + + * language2 + "_" + country2 basebundlename + "_" + language2 To the + * generated name, a ".properties" string is appeneded and once this file is + * located, it is treated just like a properties file but with bundle + * encoding also considered while loading. + * + * @exception BuildException Description of Exception + */ + private void loadResourceMaps() + throws BuildException + { + Locale locale = new Locale( bundleLanguage, + bundleCountry, + bundleVariant ); + String language = locale.getLanguage().length() > 0 ? + "_" + locale.getLanguage() : + ""; + String country = locale.getCountry().length() > 0 ? + "_" + locale.getCountry() : + ""; + String variant = locale.getVariant().length() > 0 ? + "_" + locale.getVariant() : + ""; + String bundleFile = bundle + language + country + variant; + processBundle( bundleFile, 0, false ); + + bundleFile = bundle + language + country; + processBundle( bundleFile, 1, false ); + + bundleFile = bundle + language; + processBundle( bundleFile, 2, false ); + + bundleFile = bundle; + processBundle( bundleFile, 3, false ); + + //Load default locale bundle files + //using default file encoding scheme. + locale = Locale.getDefault(); + + language = locale.getLanguage().length() > 0 ? + "_" + locale.getLanguage() : + ""; + country = locale.getCountry().length() > 0 ? + "_" + locale.getCountry() : + ""; + variant = locale.getVariant().length() > 0 ? + "_" + locale.getVariant() : + ""; + bundleEncoding = System.getProperty( "file.encoding" ); + + bundleFile = bundle + language + country + variant; + processBundle( bundleFile, 4, false ); + + bundleFile = bundle + language + country; + processBundle( bundleFile, 5, false ); + + bundleFile = bundle + language; + processBundle( bundleFile, 6, true ); + } + + /** + * Process each file that makes up this bundle. + * + * @param bundleFile Description of Parameter + * @param i Description of Parameter + * @param checkLoaded Description of Parameter + * @exception BuildException Description of Exception + */ + private void processBundle( String bundleFile, int i, + boolean checkLoaded ) + throws BuildException + { + bundleFile += ".properties"; + FileInputStream ins = null; + try + { + ins = new FileInputStream( bundleFile ); + loaded = true; + bundleLastModified[i] = new File( bundleFile ).lastModified(); + log( "Using " + bundleFile, Project.MSG_DEBUG ); + loadResourceMap( ins ); + } + catch( IOException ioe ) + { + log( bundleFile + " not found.", Project.MSG_DEBUG ); + //if all resource files associated with this bundle + //have been scanned for and still not able to + //find a single resrouce file, throw exception + if( !loaded && checkLoaded ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + } + + /** + * Reads source file line by line using the source encoding and searches for + * keys that are sandwiched between the startToken and endToken. The values + * for these keys are looked up from the hashtable and substituted. If the + * hashtable doesn't contain the key, they key itself is used as the value. + * Detination files and directories are created as needed. The destination + * file is overwritten only if the forceoverwritten attribute is set to true + * if the source file or any associated bundle resource file is newer than + * the destination file. + * + * @exception BuildException Description of Exception + */ + private void translate() + throws BuildException + { + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + for( int j = 0; j < srcFiles.length; j++ ) + { + try + { + File dest = fileUtils.resolveFile( toDir, srcFiles[j] ); + //Make sure parent dirs exist, else, create them. + try + { + File destDir = new File( dest.getParent() ); + if( !destDir.exists() ) + { + destDir.mkdirs(); + } + } + catch( Exception e ) + { + log( "Exception occured while trying to check/create " + + " parent directory. " + e.getMessage(), + Project.MSG_DEBUG ); + } + destLastModified = dest.lastModified(); + srcLastModified = new File( srcFiles[i] ).lastModified(); + //Check to see if dest file has to be recreated + if( forceOverwrite + || destLastModified < srcLastModified + || destLastModified < bundleLastModified[0] + || destLastModified < bundleLastModified[1] + || destLastModified < bundleLastModified[2] + || destLastModified < bundleLastModified[3] + || destLastModified < bundleLastModified[4] + || destLastModified < bundleLastModified[5] + || destLastModified < bundleLastModified[6] ) + { + log( "Processing " + srcFiles[j], + Project.MSG_DEBUG ); + FileOutputStream fos = new FileOutputStream( dest ); + BufferedWriter out = new BufferedWriter( + new OutputStreamWriter( fos, + destEncoding ) ); + FileInputStream fis = new FileInputStream( srcFiles[j] ); + BufferedReader in = new BufferedReader( + new InputStreamReader( fis, + srcEncoding ) ); + String line; + while( ( line = in.readLine() ) != null ) + { + StringBuffer newline = new StringBuffer( line ); + int startIndex = -1; + int endIndex = -1; + outer : + while( true ) + { + startIndex = line.indexOf( startToken, endIndex + 1 ); + if( startIndex < 0 || + startIndex + 1 >= line.length() ) + { + break; + } + endIndex = line.indexOf( endToken, startIndex + 1 ); + if( endIndex < 0 ) + { + break; + } + String matches = line.substring( startIndex + 1, + endIndex ); + //If there is a white space or = or :, then + //it isn't to be treated as a valid key. + for( int k = 0; k < matches.length(); k++ ) + { + char c = matches.charAt( k ); + if( c == ':' || + c == '=' || + Character.isSpaceChar( c ) ) + { + endIndex = endIndex - 1; + continue outer; + } + } + String replace = null; + replace = ( String )resourceMap.get( matches ); + //If the key hasn't been loaded into resourceMap, + //use the key itself as the value also. + if( replace == null ) + { + log( "Warning: The key: " + matches + + " hasn't been defined.", + Project.MSG_DEBUG ); + replace = matches; + } + line = line.substring( 0, startIndex ) + + replace + + line.substring( endIndex + 1 ); + endIndex = startIndex + replace.length() + 1; + if( endIndex + 1 >= line.length() ) + { + break; + } + } + out.write( line ); + out.newLine(); + } + if( in != null ) + { + in.close(); + } + if( out != null ) + { + out.close(); + } + } + else + { + log( "Skipping " + srcFiles[j] + + " as destination file is up to date", + Project.MSG_VERBOSE ); + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java new file mode 100644 index 000000000..1526cd393 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.Project; +import com.ibm.ivj.util.base.ToolData; +import org.apache.tools.ant.BuildException; + + +/** + * This class is the equivalent to org.apache.tools.ant.Main for the VAJ tool + * environment. It's main is called when the user selects Tools->Ant Build from + * the VAJ project menu. Additionally this class provides methods to save build + * info for a project in the repository and load it from the repository + * + * @author RT + * @author: Wolf Siberski + */ +public class VAJAntTool +{ + private final static String TOOL_DATA_KEY = "AntTool"; + + + /** + * Loads the BuildInfo for the specified VAJ project from the tool data for + * this project. If there is no build info stored for that project, a new + * default BuildInfo is returned + * + * @param projectName String project name + * @return BuildInfo buildInfo build info for the specified project + */ + public static VAJBuildInfo loadBuildData( String projectName ) + { + VAJBuildInfo result = null; + try + { + Project project = + VAJLocalUtil.getWorkspace().loadedProjectNamed( projectName ); + if( project.testToolRepositoryData( TOOL_DATA_KEY ) ) + { + ToolData td = project.getToolRepositoryData( TOOL_DATA_KEY ); + String data = ( String )td.getData(); + result = VAJBuildInfo.parse( data ); + } + else + { + result = new VAJBuildInfo(); + } + result.setVAJProjectName( projectName ); + } + catch( Throwable t ) + { + throw new BuildException( "BuildInfo for Project " + + projectName + " could not be loaded" + t ); + } + return result; + } + + + /** + * Starts the application. + * + * @param args an array of command-line arguments. VAJ puts the VAJ project + * name into args[1] when starting the tool from the project context + * menu + */ + public static void main( java.lang.String[] args ) + { + try + { + VAJBuildInfo info; + if( args.length >= 2 && args[1] instanceof String ) + { + String projectName = ( String )args[1]; + info = loadBuildData( projectName ); + } + else + { + info = new VAJBuildInfo(); + } + + VAJAntToolGUI mainFrame = new VAJAntToolGUI( info ); + mainFrame.show(); + } + catch( Throwable t ) + { + // if all error handling fails, output at least + // something on the console + t.printStackTrace(); + } + } + + + /** + * Saves the BuildInfo for a project in the VAJ repository. + * + * @param info BuildInfo build info to save + */ + public static void saveBuildData( VAJBuildInfo info ) + { + String data = info.asDataString(); + try + { + ToolData td = new ToolData( TOOL_DATA_KEY, data ); + VAJLocalUtil.getWorkspace().loadedProjectNamed( + info.getVAJProjectName() ).setToolRepositoryData( td ); + } + catch( Throwable t ) + { + throw new BuildException( "BuildInfo for Project " + + info.getVAJProjectName() + " could not be saved", t ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java new file mode 100644 index 000000000..7aa8916e0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java @@ -0,0 +1,1803 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.Choice; +import java.awt.Dialog; +import java.awt.FileDialog; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Label; +import java.awt.List; +import java.awt.Menu; +import java.awt.MenuBar; +import java.awt.MenuItem; +import java.awt.Panel; +import java.awt.SystemColor; +import java.awt.TextArea; +import java.awt.TextField; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.TextEvent; +import java.awt.event.TextListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.beans.PropertyChangeListener; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Vector; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + +/** + * This is a simple grafical user interface to provide the information needed by + * ANT and to start the build-process within IBM VisualAge for Java.

      + * + * I was using AWT to make it independent from the JDK-version. Please don't ask + * me for a Swing-version:I am very familiar with Swing and I really think that + * it's not necessary for such a simple gui!

      + * + * It is completely developed in VAJ using the visual composition editor. About + * 90% of the code is generated by VAJ, but in fact I did a lot of + * code-beautification ;-).

      + * + * + * + * @author RT + * @version 1.0 h + * @author: Christoph Wilhelms, TUI Infotec GmbH + */ +public class VAJAntToolGUI extends Frame +{ + private final static String lineSeparator = "\r\n"; + /** + * Members + */ + private VAJBuildLogger logger = new VAJBuildLogger(); + private PrivateEventHandler iEventHandler = new PrivateEventHandler(); + + /** + * Members of the main-window + */ + // main model + private VAJBuildInfo iBuildInfo = null; + // Menue + private MenuBar iAntMakeMenuBar = null; + private Menu iFileMenu = null; + private MenuItem iSaveMenuItem = null; + private MenuItem iMenuSeparator = null; + private MenuItem iShowLogMenuItem = null; + private Menu iHelpMenu = null; + private MenuItem iAboutMenuItem = null; + // Container + private Panel iContentsPane = null; + private Panel iOptionenPanel = null; + private Panel iCommandButtonPanel = null; + private FlowLayout iCommandButtonPanelFlowLayout = null; + // Project name + private Label iProjectLabel = null; + private Label iProjectText = null; + // XML-file + private Label iBuildFileLabel = null; + private TextField iBuildFileTextField = null; + private boolean iConnPtoP2Aligning = false; + private Button iBrowseButton = null; + private FileDialog iFileDialog = null; + // Options + private Choice iMessageOutputLevelChoice = null; + private Label iMessageOutputLevelLabel = null; + private Label iTargetLabel = null; + private List iTargetList = null; + // Command-buttons + private Button iBuildButton = null; + private Button iReloadButton = null; + private Button iCloseButton = null; + /** + * log-Window + */ + // Container + private Frame iMessageFrame = null; + private Panel iMessageCommandPanel = null; + private Panel iMessageContentPanel = null; + // Components + private TextArea iMessageTextArea = null; + private Button iMessageOkButton = null; + private Button iMessageClearLogButton = null; + /** + * About-dialog + */ + // Container + private Dialog iAboutDialog = null; + private Panel iAboutDialogContentPanel = null; + private Panel iAboutInfoPanel = null; + private Panel iAboutCommandPanel = null; + // Labels + private Label iAboutTitleLabel = null; + private Label iAboutDevLabel = null; + private Label iAboutContactLabel = null; + // Buttons + private Button iAboutOkButton = null; + + private Button iStopButton = null; + + /** + * AntMake constructor called by VAJAntTool integration. + * + * @param newBuildInfo Description of Parameter + */ + + public VAJAntToolGUI( VAJBuildInfo newBuildInfo ) + { + super(); + setBuildInfo( newBuildInfo ); + initialize(); + } + + /** + * AntMake default-constructor. + */ + private VAJAntToolGUI() + { + super(); + initialize(); + } + + /** + * This method is used to center dialogs. + * + * @param dialog Description of Parameter + */ + public static void centerDialog( Dialog dialog ) + { + dialog.setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( dialog.getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) - ( dialog.getSize().height / 2 ) ); + } + + /** + * Copied from DefaultLogger to provide the same time-format. + * + * @param millis Description of Parameter + * @return Description of the Returned Value + */ + public static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + } + + /** + * Set the BuildInfo to a new value. + * + * @param newValue org.apache.tools.ant.taskdefs.optional.vaj.VAJBuildInfo + */ + private void setBuildInfo( VAJBuildInfo newValue ) + { + if( iBuildInfo != newValue ) + { + try + { + /* + * Stop listening for events from the current object + */ + if( iBuildInfo != null ) + { + iBuildInfo.removePropertyChangeListener( iEventHandler ); + } + iBuildInfo = newValue; + + /* + * Listen for events from the new object + */ + if( iBuildInfo != null ) + { + iBuildInfo.addPropertyChangeListener( iEventHandler ); + } + connectProjectNameToLabel(); + connectBuildFileNameToTextField(); + + // Select the log-level given by BuildInfo + getMessageOutputLevelChoice().select( iBuildInfo.getOutputMessageLevel() ); + fillList(); + // BuildInfo can conly be saved to a VAJ project if tool API is called via the projects context-menu + if( ( iBuildInfo.getVAJProjectName() == null ) || ( iBuildInfo.getVAJProjectName().equals( "" ) ) ) + { + getSaveMenuItem().setEnabled( false ); + } + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + } + + /** + * Return the AboutCommandPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutCommandPanel() + { + if( iAboutCommandPanel == null ) + { + try + { + iAboutCommandPanel = new Panel(); + iAboutCommandPanel.setName( "AboutCommandPanel" ); + iAboutCommandPanel.setLayout( new java.awt.FlowLayout() ); + getAboutCommandPanel().add( getAboutOkButton(), getAboutOkButton().getName() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutCommandPanel; + } + + /** + * Return the AboutContactLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutContactLabel() + { + if( iAboutContactLabel == null ) + { + try + { + iAboutContactLabel = new Label(); + iAboutContactLabel.setName( "AboutContactLabel" ); + iAboutContactLabel.setAlignment( java.awt.Label.CENTER ); + iAboutContactLabel.setText( "contact: wolf.siberski@tui.de or christoph.wilhelms@tui.de" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutContactLabel; + } + + /** + * Return the AboutDevLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutDevLabel() + { + if( iAboutDevLabel == null ) + { + try + { + iAboutDevLabel = new Label(); + iAboutDevLabel.setName( "AboutDevLabel" ); + iAboutDevLabel.setAlignment( java.awt.Label.CENTER ); + iAboutDevLabel.setText( "developed by Wolf Siberski & Christoph Wilhelms" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDevLabel; + } + + /** + * Return the AboutDialog property value. + * + * @return java.awt.Dialog + */ + private Dialog getAboutDialog() + { + if( iAboutDialog == null ) + { + try + { + iAboutDialog = new Dialog( this ); + iAboutDialog.setName( "AboutDialog" ); + iAboutDialog.setResizable( false ); + iAboutDialog.setLayout( new java.awt.BorderLayout() ); + iAboutDialog.setBounds( 550, 14, 383, 142 ); + iAboutDialog.setModal( true ); + iAboutDialog.setTitle( "About..." ); + getAboutDialog().add( getAboutDialogContentPanel(), "Center" ); + iAboutDialog.pack(); + centerDialog( iAboutDialog ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDialog; + } + + /** + * Return the AboutDialogContentPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutDialogContentPanel() + { + if( iAboutDialogContentPanel == null ) + { + try + { + iAboutDialogContentPanel = new Panel(); + iAboutDialogContentPanel.setName( "AboutDialogContentPanel" ); + iAboutDialogContentPanel.setLayout( new java.awt.BorderLayout() ); + getAboutDialogContentPanel().add( getAboutCommandPanel(), "South" ); + getAboutDialogContentPanel().add( getAboutInfoPanel(), "Center" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDialogContentPanel; + } + + /** + * Return the AboutInfoPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutInfoPanel() + { + if( iAboutInfoPanel == null ) + { + try + { + iAboutInfoPanel = new Panel(); + iAboutInfoPanel.setName( "AboutInfoPanel" ); + iAboutInfoPanel.setLayout( new GridBagLayout() ); + + GridBagConstraints constraintsAboutTitleLabel = new GridBagConstraints(); + constraintsAboutTitleLabel.gridx = 0; + constraintsAboutTitleLabel.gridy = 0; + constraintsAboutTitleLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutTitleLabel.weightx = 1.0; + constraintsAboutTitleLabel.weighty = 1.0; + constraintsAboutTitleLabel.insets = new Insets( 4, 0, 4, 0 ); + getAboutInfoPanel().add( getAboutTitleLabel(), constraintsAboutTitleLabel ); + + GridBagConstraints constraintsAboutDevLabel = new GridBagConstraints(); + constraintsAboutDevLabel.gridx = 0; + constraintsAboutDevLabel.gridy = 1; + constraintsAboutDevLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutDevLabel.weightx = 1.0; + constraintsAboutDevLabel.insets = new Insets( 4, 0, 0, 0 ); + getAboutInfoPanel().add( getAboutDevLabel(), constraintsAboutDevLabel ); + + GridBagConstraints constraintsAboutContactLabel = new GridBagConstraints(); + constraintsAboutContactLabel.gridx = 0; + constraintsAboutContactLabel.gridy = 2; + constraintsAboutContactLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutContactLabel.weightx = 1.0; + constraintsAboutContactLabel.insets = new Insets( 2, 0, 4, 0 ); + getAboutInfoPanel().add( getAboutContactLabel(), constraintsAboutContactLabel ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutInfoPanel; + } + + /** + * Return the AboutMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getAboutMenuItem() + { + if( iAboutMenuItem == null ) + { + try + { + iAboutMenuItem = new MenuItem(); + iAboutMenuItem.setLabel( "About..." ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutMenuItem; + } + + /** + * Return the AboutOkButton property value. + * + * @return java.awt.Button + */ + private Button getAboutOkButton() + { + if( iAboutOkButton == null ) + { + try + { + iAboutOkButton = new Button(); + iAboutOkButton.setName( "AboutOkButton" ); + iAboutOkButton.setLabel( "OK" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutOkButton; + } + + /** + * Return the AboutTitleLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutTitleLabel() + { + if( iAboutTitleLabel == null ) + { + try + { + iAboutTitleLabel = new Label(); + iAboutTitleLabel.setName( "AboutTitleLabel" ); + iAboutTitleLabel.setFont( new Font( "Arial", 1, 12 ) ); + iAboutTitleLabel.setAlignment( Label.CENTER ); + iAboutTitleLabel.setText( "Ant VisualAge for Java Tool-Integration" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutTitleLabel; + } + + /** + * Return the AntMakeMenuBar property value. + * + * @return java.awt.MenuBar + */ + private MenuBar getAntMakeMenuBar() + { + if( iAntMakeMenuBar == null ) + { + try + { + iAntMakeMenuBar = new MenuBar(); + iAntMakeMenuBar.add( getFileMenu() ); + iAntMakeMenuBar.add( getHelpMenu() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAntMakeMenuBar; + } + + /** + * Return the BrowseButton property value. + * + * @return Button + */ + private Button getBrowseButton() + { + if( iBrowseButton == null ) + { + try + { + iBrowseButton = new Button(); + iBrowseButton.setName( "BrowseButton" ); + iBrowseButton.setLabel( "..." ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBrowseButton; + } + + /** + * Return the BuildButton property value. + * + * @return java.awt.Button + */ + private Button getBuildButton() + { + if( iBuildButton == null ) + { + try + { + iBuildButton = new Button(); + iBuildButton.setName( "BuildButton" ); + iBuildButton.setLabel( "Execute" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildButton; + } + + /** + * Return the BuildFileLabel property value. + * + * @return java.awt.Label + */ + private Label getBuildFileLabel() + { + if( iBuildFileLabel == null ) + { + try + { + iBuildFileLabel = new Label(); + iBuildFileLabel.setName( "BuildFileLabel" ); + iBuildFileLabel.setText( "Ant-Buildfile:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildFileLabel; + } + + /** + * Return the BuildFileTextField property value. + * + * @return java.awt.TextField + */ + private TextField getBuildFileTextField() + { + if( iBuildFileTextField == null ) + { + try + { + iBuildFileTextField = new TextField(); + iBuildFileTextField.setName( "BuildFileTextField" ); + iBuildFileTextField.setBackground( SystemColor.textHighlightText ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildFileTextField; + } + + /** + * Return the BuildInfo property value. + * + * @return org.apache.tools.ant.taskdefs.optional.ide.VAJBuildInfo + */ + private VAJBuildInfo getBuildInfo() + { + return iBuildInfo; + } + + /** + * Return the CloseButton property value. + * + * @return java.awt.Button + */ + private Button getCloseButton() + { + if( iCloseButton == null ) + { + try + { + iCloseButton = new Button(); + iCloseButton.setName( "CloseButton" ); + iCloseButton.setLabel( "Close" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iCloseButton; + } + + /** + * Return the CommandButtonPanel property value. + * + * @return java.awt.Panel + */ + private Panel getCommandButtonPanel() + { + if( iCommandButtonPanel == null ) + { + try + { + iCommandButtonPanel = new Panel(); + iCommandButtonPanel.setName( "CommandButtonPanel" ); + iCommandButtonPanel.setLayout( getCommandButtonPanelFlowLayout() ); + iCommandButtonPanel.setBackground( SystemColor.control ); + iCommandButtonPanel.add( getReloadButton() ); + iCommandButtonPanel.add( getBuildButton() ); + iCommandButtonPanel.add( getStopButton() ); + iCommandButtonPanel.add( getCloseButton() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iCommandButtonPanel; + } + + /** + * Return the CommandButtonPanelFlowLayout property value. + * + * @return java.awt.FlowLayout + */ + private FlowLayout getCommandButtonPanelFlowLayout() + { + FlowLayout iCommandButtonPanelFlowLayout = null; + try + { + /* + * Create part + */ + iCommandButtonPanelFlowLayout = new FlowLayout(); + iCommandButtonPanelFlowLayout.setAlignment( FlowLayout.RIGHT ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + ; + return iCommandButtonPanelFlowLayout; + } + + /** + * Return the ContentsPane property value. + * + * @return java.awt.Panel + */ + private Panel getContentsPane() + { + if( iContentsPane == null ) + { + try + { + iContentsPane = new Panel(); + iContentsPane.setName( "ContentsPane" ); + iContentsPane.setLayout( new BorderLayout() ); + getContentsPane().add( getCommandButtonPanel(), "South" ); + getContentsPane().add( getOptionenPanel(), "Center" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iContentsPane; + } + + /** + * Return the FileDialog property value. + * + * @return java.awt.FileDialog + */ + private FileDialog getFileDialog() + { + if( iFileDialog == null ) + { + try + { + iFileDialog = new FileDialog( this ); + iFileDialog.setName( "FileDialog" ); + iFileDialog.setLayout( null ); + centerDialog( iFileDialog ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iFileDialog; + } + + /** + * Return the FileMenu property value. + * + * @return java.awt.Menu + */ + private Menu getFileMenu() + { + if( iFileMenu == null ) + { + try + { + iFileMenu = new Menu(); + iFileMenu.setLabel( "File" ); + iFileMenu.add( getSaveMenuItem() ); + iFileMenu.add( getMenuSeparator() ); + iFileMenu.add( getShowLogMenuItem() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iFileMenu; + } + + /** + * Return the HelpMenu property value. + * + * @return java.awt.Menu + */ + private Menu getHelpMenu() + { + if( iHelpMenu == null ) + { + try + { + iHelpMenu = new Menu(); + iHelpMenu.setLabel( "Help" ); + iHelpMenu.add( getAboutMenuItem() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iHelpMenu; + } + + /** + * Return the MenuSeparator1 property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getMenuSeparator() + { + if( iMenuSeparator == null ) + { + try + { + iMenuSeparator = new MenuItem(); + iMenuSeparator.setLabel( "-" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMenuSeparator; + } + + /** + * Return the MessageClearLogButton property value. + * + * @return java.awt.Button + */ + private Button getMessageClearLogButton() + { + if( iMessageClearLogButton == null ) + { + try + { + iMessageClearLogButton = new Button(); + iMessageClearLogButton.setName( "MessageClearLogButton" ); + iMessageClearLogButton.setLabel( "Clear Log" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageClearLogButton; + } + + /** + * Return the MessageCommandPanel property value. + * + * @return java.awt.Panel + */ + private Panel getMessageCommandPanel() + { + if( iMessageCommandPanel == null ) + { + try + { + iMessageCommandPanel = new Panel(); + iMessageCommandPanel.setName( "MessageCommandPanel" ); + iMessageCommandPanel.setLayout( new FlowLayout() ); + getMessageCommandPanel().add( getMessageClearLogButton(), getMessageClearLogButton().getName() ); + getMessageCommandPanel().add( getMessageOkButton(), getMessageOkButton().getName() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageCommandPanel; + } + + /** + * Return the MessageContentPanel property value. + * + * @return java.awt.Panel + */ + private Panel getMessageContentPanel() + { + if( iMessageContentPanel == null ) + { + try + { + iMessageContentPanel = new Panel(); + iMessageContentPanel.setName( "MessageContentPanel" ); + iMessageContentPanel.setLayout( new BorderLayout() ); + iMessageContentPanel.setBackground( SystemColor.control ); + getMessageContentPanel().add( getMessageTextArea(), "Center" ); + getMessageContentPanel().add( getMessageCommandPanel(), "South" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageContentPanel; + } + + /** + * Return the MessageFrame property value. + * + * @return java.awt.Frame + */ + private Frame getMessageFrame() + { + if( iMessageFrame == null ) + { + try + { + iMessageFrame = new Frame(); + iMessageFrame.setName( "MessageFrame" ); + iMessageFrame.setLayout( new BorderLayout() ); + iMessageFrame.setBounds( 0, 0, 750, 250 ); + iMessageFrame.setTitle( "Message Log" ); + iMessageFrame.add( getMessageContentPanel(), "Center" ); + iMessageFrame.setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( iMessageFrame.getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageFrame; + } + + /** + * Return the MessageOkButton property value. + * + * @return java.awt.Button + */ + private Button getMessageOkButton() + { + if( iMessageOkButton == null ) + { + try + { + iMessageOkButton = new Button(); + iMessageOkButton.setName( "MessageOkButton" ); + iMessageOkButton.setLabel( "Close" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOkButton; + } + + /** + * Return the MessageOutputLevelChoice property value. + * + * @return java.awt.Choice + */ + private Choice getMessageOutputLevelChoice() + { + if( iMessageOutputLevelChoice == null ) + { + try + { + iMessageOutputLevelChoice = new Choice(); + iMessageOutputLevelChoice.setName( "MessageOutputLevelChoice" ); + iMessageOutputLevelChoice.add( "Error" ); + iMessageOutputLevelChoice.add( "Warning" ); + iMessageOutputLevelChoice.add( "Info" ); + iMessageOutputLevelChoice.add( "Verbose" ); + iMessageOutputLevelChoice.add( "Debug" ); + iMessageOutputLevelChoice.select( 2 ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOutputLevelChoice; + } + + /** + * Return the MessageOutputLevelLabel property value. + * + * @return java.awt.Label + */ + private Label getMessageOutputLevelLabel() + { + if( iMessageOutputLevelLabel == null ) + { + try + { + iMessageOutputLevelLabel = new Label(); + iMessageOutputLevelLabel.setName( "MessageOutputLevelLabel" ); + iMessageOutputLevelLabel.setText( "Message Level:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOutputLevelLabel; + } + + /** + * Return the MessageTextArea property value. + * + * @return java.awt.TextArea + */ + private TextArea getMessageTextArea() + { + if( iMessageTextArea == null ) + { + try + { + iMessageTextArea = new TextArea(); + iMessageTextArea.setName( "MessageTextArea" ); + iMessageTextArea.setFont( new Font( "monospaced", 0, 12 ) ); + iMessageTextArea.setText( "" ); + iMessageTextArea.setEditable( false ); + iMessageTextArea.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageTextArea; + } + + /** + * Return the Panel1 property value. + * + * @return java.awt.Panel + */ + private Panel getOptionenPanel() + { + if( iOptionenPanel == null ) + { + try + { + iOptionenPanel = new Panel(); + iOptionenPanel.setName( "OptionenPanel" ); + iOptionenPanel.setLayout( new GridBagLayout() ); + iOptionenPanel.setBackground( SystemColor.control ); + + GridBagConstraints constraintsProjectLabel = new GridBagConstraints(); + constraintsProjectLabel.gridx = 0; + constraintsProjectLabel.gridy = 0; + constraintsProjectLabel.anchor = GridBagConstraints.WEST; + constraintsProjectLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getProjectLabel(), constraintsProjectLabel ); + + GridBagConstraints constraintsBuildFileLabel = new GridBagConstraints(); + constraintsBuildFileLabel.gridx = 0; + constraintsBuildFileLabel.gridy = 1; + constraintsBuildFileLabel.anchor = GridBagConstraints.WEST; + constraintsBuildFileLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBuildFileLabel(), constraintsBuildFileLabel ); + + GridBagConstraints constraintsTargetLabel = new GridBagConstraints(); + constraintsTargetLabel.gridx = 0; + constraintsTargetLabel.gridy = 2; + constraintsTargetLabel.anchor = GridBagConstraints.NORTHWEST; + constraintsTargetLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getTargetLabel(), constraintsTargetLabel ); + + GridBagConstraints constraintsProjectText = new GridBagConstraints(); + constraintsProjectText.gridx = 1; + constraintsProjectText.gridy = 0; + constraintsProjectText.gridwidth = 2; + constraintsProjectText.fill = GridBagConstraints.HORIZONTAL; + constraintsProjectText.anchor = GridBagConstraints.WEST; + constraintsProjectText.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getProjectText(), constraintsProjectText ); + + GridBagConstraints constraintsBuildFileTextField = new GridBagConstraints(); + constraintsBuildFileTextField.gridx = 1; + constraintsBuildFileTextField.gridy = 1; + constraintsBuildFileTextField.fill = GridBagConstraints.HORIZONTAL; + constraintsBuildFileTextField.anchor = GridBagConstraints.WEST; + constraintsBuildFileTextField.weightx = 1.0; + constraintsBuildFileTextField.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBuildFileTextField(), constraintsBuildFileTextField ); + + GridBagConstraints constraintsBrowseButton = new GridBagConstraints(); + constraintsBrowseButton.gridx = 2; + constraintsBrowseButton.gridy = 1; + constraintsBrowseButton.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBrowseButton(), constraintsBrowseButton ); + + GridBagConstraints constraintsTargetList = new GridBagConstraints(); + constraintsTargetList.gridx = 1; + constraintsTargetList.gridy = 2; + constraintsTargetList.gridheight = 2; + constraintsTargetList.fill = GridBagConstraints.BOTH; + constraintsTargetList.weightx = 1.0; + constraintsTargetList.weighty = 1.0; + constraintsTargetList.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getTargetList(), constraintsTargetList ); + + GridBagConstraints constraintsMessageOutputLevelLabel = new GridBagConstraints(); + constraintsMessageOutputLevelLabel.gridx = 0; + constraintsMessageOutputLevelLabel.gridy = 4; + constraintsMessageOutputLevelLabel.anchor = GridBagConstraints.WEST; + constraintsMessageOutputLevelLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getMessageOutputLevelLabel(), constraintsMessageOutputLevelLabel ); + + GridBagConstraints constraintsMessageOutputLevelChoice = new GridBagConstraints(); + constraintsMessageOutputLevelChoice.gridx = 1; + constraintsMessageOutputLevelChoice.gridy = 4; + constraintsMessageOutputLevelChoice.fill = GridBagConstraints.HORIZONTAL; + constraintsMessageOutputLevelChoice.anchor = GridBagConstraints.WEST; + constraintsMessageOutputLevelChoice.weightx = 1.0; + constraintsMessageOutputLevelChoice.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getMessageOutputLevelChoice(), constraintsMessageOutputLevelChoice ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iOptionenPanel; + } + + /** + * Return the ProjectLabel property value. + * + * @return java.awt.Label + */ + private Label getProjectLabel() + { + if( iProjectLabel == null ) + { + try + { + iProjectLabel = new Label(); + iProjectLabel.setName( "ProjectLabel" ); + iProjectLabel.setText( "Projectname:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iProjectLabel; + } + + /** + * Return the ProjectText property value. + * + * @return java.awt.Label + */ + private Label getProjectText() + { + if( iProjectText == null ) + { + try + { + iProjectText = new Label(); + iProjectText.setName( "ProjectText" ); + iProjectText.setText( " " ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iProjectText; + } + + /** + * Return the ReloadButton property value. + * + * @return java.awt.Button + */ + private Button getReloadButton() + { + if( iReloadButton == null ) + { + try + { + iReloadButton = new Button(); + iReloadButton.setName( "ReloadButton" ); + iReloadButton.setLabel( "(Re)Load" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iReloadButton; + } + + /** + * Return the SaveMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getSaveMenuItem() + { + if( iSaveMenuItem == null ) + { + try + { + iSaveMenuItem = new MenuItem(); + iSaveMenuItem.setLabel( "Save BuildInfo To Repository" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iSaveMenuItem; + } + + /** + * Return the ShowLogMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getShowLogMenuItem() + { + if( iShowLogMenuItem == null ) + { + try + { + iShowLogMenuItem = new MenuItem(); + iShowLogMenuItem.setLabel( "Log" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iShowLogMenuItem; + } + + /** + * Return the StopButton property value. + * + * @return java.awt.Button + */ + private Button getStopButton() + { + if( iStopButton == null ) + { + try + { + iStopButton = new Button(); + iStopButton.setName( "StopButton" ); + iStopButton.setLabel( "Stop" ); + iStopButton.setEnabled( false ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iStopButton; + } + + /** + * Return the TargetLabel property value. + * + * @return java.awt.Label + */ + private Label getTargetLabel() + { + if( iTargetLabel == null ) + { + try + { + iTargetLabel = new Label(); + iTargetLabel.setName( "TargetLabel" ); + iTargetLabel.setText( "Target:" ); + iTargetLabel.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iTargetLabel; + } + + /** + * Return the TargetList property value. + * + * @return java.awt.List + */ + private List getTargetList() + { + if( iTargetList == null ) + { + try + { + iTargetList = new List(); + iTargetList.setName( "TargetList" ); + iTargetList.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iTargetList; + } + + /** + * connectBuildFileNameToTextField: (BuildInfo.buildFileName <--> + * BuildFileTextField.text) + */ + private void connectBuildFileNameToTextField() + { + /* + * Set the target from the source + */ + try + { + if( iConnPtoP2Aligning == false ) + { + iConnPtoP2Aligning = true; + if( ( getBuildInfo() != null ) ) + { + getBuildFileTextField().setText( getBuildInfo().getBuildFileName() ); + } + iConnPtoP2Aligning = false; + } + } + catch( Throwable iExc ) + { + iConnPtoP2Aligning = false; + handleException( iExc ); + } + } + + /** + * connectProjectNameToLabel: (BuildInfo.vajProjectName <--> + * ProjectText.text) + */ + private void connectProjectNameToLabel() + { + /* + * Set the target from the source + */ + try + { + if( ( getBuildInfo() != null ) ) + { + getProjectText().setText( getBuildInfo().getVAJProjectName() ); + } + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + + /** + * connectTextFieldToBuildFileName: (BuildInfo.buildFileName <--> + * BuildFileTextField.text) + */ + private void connectTextFieldToBuildFileName() + { + /* + * Set the source from the target + */ + try + { + if( iConnPtoP2Aligning == false ) + { + iConnPtoP2Aligning = true; + if( ( getBuildInfo() != null ) ) + { + getBuildInfo().setBuildFileName( getBuildFileTextField().getText() ); + } + iConnPtoP2Aligning = false; + } + } + catch( Throwable iExc ) + { + iConnPtoP2Aligning = false; + handleException( iExc ); + } + } + + /** + * external build of a .jar-file + */ + private void executeTarget() + { + try + { + getMessageFrame().show(); + getBuildInfo().executeProject( logger ); + } + catch( Throwable exc ) + { + logger.logException( exc ); + } + return; + } + + /** + * Fills the taget-list with project-targets + */ + private void fillList() + { + getTargetList().removeAll(); + Vector targets = getBuildInfo().getProjectTargets(); + for( int i = 0; i < targets.size(); i++ ) + { + getTargetList().add( targets.elementAt( i ).toString() ); + } + getTargetList().select( iBuildInfo.getProjectTargets().indexOf( iBuildInfo.getTarget() ) ); + if( getTargetList().getSelectedIndex() >= 0 ) + { + getBuildButton().setEnabled( true ); + } + } + + /** + * Called whenever the part throws an exception. + * + * @param exception Throwable + */ + private void handleException( Throwable exception ) + { + // Write exceptions to the log-window + String trace = StringUtils.getStackTrace( exception ); + + getMessageTextArea().append( lineSeparator + lineSeparator + trace ); + getMessageFrame().show(); + + } + + /** + * Initializes connections + * + * @exception Exception The exception description. + */ + private void initConnections() + throws Exception + { + this.addWindowListener( iEventHandler ); + getBrowseButton().addActionListener( iEventHandler ); + getCloseButton().addActionListener( iEventHandler ); + getBuildButton().addActionListener( iEventHandler ); + getStopButton().addActionListener( iEventHandler ); + getSaveMenuItem().addActionListener( iEventHandler ); + getAboutOkButton().addActionListener( iEventHandler ); + getAboutMenuItem().addActionListener( iEventHandler ); + getMessageOkButton().addActionListener( iEventHandler ); + getMessageClearLogButton().addActionListener( iEventHandler ); + getMessageOkButton().addActionListener( iEventHandler ); + getShowLogMenuItem().addActionListener( iEventHandler ); + getAboutDialog().addWindowListener( iEventHandler ); + getMessageFrame().addWindowListener( iEventHandler ); + getReloadButton().addActionListener( iEventHandler ); + getTargetList().addItemListener( iEventHandler ); + getMessageOutputLevelChoice().addItemListener( iEventHandler ); + getBuildFileTextField().addTextListener( iEventHandler ); + connectProjectNameToLabel(); + connectBuildFileNameToTextField(); + } + + /** + * Initialize the class. + */ + private void initialize() + { + try + { + setName( "AntMake" ); + setMenuBar( getAntMakeMenuBar() ); + setLayout( new java.awt.BorderLayout() ); + setSize( 389, 222 ); + setTitle( "Ant VisualAge for Java Tool-Integration" ); + add( getContentsPane(), "Center" ); + initConnections(); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) - ( getSize().height ) ); + if( ( getTargetList().getItemCount() == 0 ) || ( getTargetList().getSelectedIndex() < 0 ) ) + { + getBuildButton().setEnabled( false ); + } + } + + /** + * Saves the build-informations to repository + */ + private void saveBuildInfo() + { + try + { + VAJAntTool.saveBuildData( getBuildInfo() ); + } + catch( Throwable exc ) + { + // This Exception occurs when you try to write into a versioned project + handleException( exc ); + } + return; + } + + /** + * Eventhandler to handle all AWT-events + * + * @author RT + */ + private class PrivateEventHandler implements ActionListener, ItemListener, TextListener, WindowListener, PropertyChangeListener + { + /** + * ActionListener method + * + * @param e Description of Parameter + */ + public void actionPerformed( ActionEvent e ) + { + try + { + /* + * #### Main App-Frame #### + */ + // browse XML-File with filechooser + if( e.getSource() == VAJAntToolGUI.this.getBrowseButton() ) + { + getFileDialog().setDirectory( getBuildFileTextField().getText().substring( 0, getBuildFileTextField().getText().lastIndexOf( '\\' ) + 1 ) ); + getFileDialog().setFile( "*.xml" ); + getFileDialog().show(); + if( !getFileDialog().getFile().equals( "" ) ) + { + getBuildFileTextField().setText( getFileDialog().getDirectory() + getFileDialog().getFile() ); + } + } + // dispose and exit application + if( e.getSource() == VAJAntToolGUI.this.getCloseButton() ) + { + dispose(); + System.exit( 0 ); + } + // start build-process + if( e.getSource() == VAJAntToolGUI.this.getBuildButton() ) + { + executeTarget(); + } + if( e.getSource() == VAJAntToolGUI.this.getStopButton() ) + { + getBuildInfo().cancelBuild(); + } + if( e.getSource() == VAJAntToolGUI.this.getReloadButton() ) + { + try + { + getBuildInfo().updateTargetList(); + fillList(); + } + catch( Throwable fileNotFound ) + { + handleException( fileNotFound ); + getTargetList().removeAll(); + getBuildButton().setEnabled( false ); + } + } + // MenuItems + if( e.getSource() == VAJAntToolGUI.this.getSaveMenuItem() ) + saveBuildInfo(); + if( e.getSource() == VAJAntToolGUI.this.getAboutMenuItem() ) + getAboutDialog().show(); + if( e.getSource() == VAJAntToolGUI.this.getShowLogMenuItem() ) + getMessageFrame().show(); + /* + * #### About dialog #### + */ + if( e.getSource() == VAJAntToolGUI.this.getAboutOkButton() ) + getAboutDialog().dispose(); + /* + * #### Log frame #### + */ + if( e.getSource() == VAJAntToolGUI.this.getMessageOkButton() ) + getMessageFrame().dispose(); + if( e.getSource() == VAJAntToolGUI.this.getMessageClearLogButton() ) + getMessageTextArea().setText( "" ); + if( e.getSource() == VAJAntToolGUI.this.getMessageOkButton() ) + getMessageFrame().dispose(); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + /** + * ItemListener method + * + * @param e Description of Parameter + */ + public void itemStateChanged( ItemEvent e ) + { + try + { + if( e.getSource() == VAJAntToolGUI.this.getTargetList() ) + getBuildButton().setEnabled( true ); + if( e.getSource() == VAJAntToolGUI.this.getMessageOutputLevelChoice() ) + getBuildInfo().setOutputMessageLevel( getMessageOutputLevelChoice().getSelectedIndex() ); + if( e.getSource() == VAJAntToolGUI.this.getTargetList() ) + getBuildInfo().setTarget( getTargetList().getSelectedItem() ); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + /** + * PropertyChangeListener method + * + * @param evt Description of Parameter + */ + public void propertyChange( java.beans.PropertyChangeEvent evt ) + { + if( evt.getSource() == VAJAntToolGUI.this.getBuildInfo() && ( evt.getPropertyName().equals( "projectName" ) ) ) + connectProjectNameToLabel(); + if( evt.getSource() == VAJAntToolGUI.this.getBuildInfo() && ( evt.getPropertyName().equals( "buildFileName" ) ) ) + connectBuildFileNameToTextField(); + } + + /** + * TextListener method + * + * @param e Description of Parameter + */ + public void textValueChanged( TextEvent e ) + { + if( e.getSource() == VAJAntToolGUI.this.getBuildFileTextField() ) + connectTextFieldToBuildFileName(); + } + + public void windowActivated( WindowEvent e ) { } + + public void windowClosed( WindowEvent e ) { } + + /** + * WindowListener methods + * + * @param e Description of Parameter + */ + public void windowClosing( WindowEvent e ) + { + try + { + if( e.getSource() == VAJAntToolGUI.this ) + { + dispose(); + System.exit( 0 ); + } + if( e.getSource() == VAJAntToolGUI.this.getAboutDialog() ) + getAboutDialog().dispose(); + if( e.getSource() == VAJAntToolGUI.this.getMessageFrame() ) + getMessageFrame().dispose(); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + public void windowDeactivated( WindowEvent e ) { } + + public void windowDeiconified( WindowEvent e ) { } + + public void windowIconified( WindowEvent e ) { } + + public void windowOpened( WindowEvent e ) { } + } + + /** + * This internal BuildLogger, to be honest, is just a BuildListener. It does + * nearly the same as the DefaultLogger, but uses the Loggin-Window for + * output. + * + * @author RT + */ + private class VAJBuildLogger implements BuildListener + { + private long startTime = System.currentTimeMillis(); + + /** + * VAJBuildLogger constructor comment. + */ + public VAJBuildLogger() + { + super(); + } + + /** + * Fired after the last target has finished. This event will still be + * thrown if an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void buildFinished( BuildEvent event ) + { + getStopButton().setEnabled( false ); + getBuildButton().setEnabled( true ); + getBuildButton().requestFocus(); + + Throwable error = event.getException(); + + if( error == null ) + { + getMessageTextArea().append( lineSeparator + "BUILD SUCCESSFUL" ); + } + else + { + logException( error ); + } + + getMessageTextArea().append( lineSeparator + "Total time: " + formatTime( System.currentTimeMillis() - startTime ) ); + } + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + public void buildStarted( BuildEvent event ) + { + getStopButton().setEnabled( true ); + getBuildButton().setEnabled( false ); + getStopButton().requestFocus(); + + startTime = System.currentTimeMillis(); + getMessageTextArea().append( lineSeparator ); + } + + + /** + * Outputs an exception. + * + * @param error Description of Parameter + */ + public void logException( Throwable error ) + { + getMessageTextArea().append( lineSeparator + "BUILD FAILED" + lineSeparator ); + + if( error instanceof BuildException ) + { + getMessageTextArea().append( error.toString() ); + + Throwable nested = ( ( BuildException )error ).getCause(); + if( nested != null ) + { + nested.printStackTrace( System.err ); + } + } + else + { + error.printStackTrace( System.err ); + } + } + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + * @see BuildEvent#getMessage() + * @see BuildEvent#getPriority() + */ + public void messageLogged( BuildEvent event ) + { + if( event.getPriority() <= getBuildInfo().getOutputMessageLevel() ) + { + String msg = ""; + if( event.getTask() != null ) + msg = "[" + event.getTask().getTaskName() + "] "; + getMessageTextArea().append( lineSeparator + msg + event.getMessage() ); + } + } + + /** + * Fired when a target has finished. This event will still be thrown if + * an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void targetFinished( BuildEvent event ) { } + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTarget() + */ + public void targetStarted( BuildEvent event ) + { + if( getBuildInfo().getOutputMessageLevel() <= Project.MSG_INFO ) + { + getMessageTextArea().append( lineSeparator + event.getTarget().getName() + ":" ); + } + } + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void taskFinished( BuildEvent event ) { } + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTask() + */ + public void taskStarted( BuildEvent event ) { } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java new file mode 100644 index 000000000..a504426bb --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java @@ -0,0 +1,585 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Target; + +/** + * This class wraps the Ant project information needed to start Ant from Visual + * Age. It serves the following purposes: - acts as model for AntMakeFrame - + * converts itself to/from String (to store the information as ToolData in the + * VA repository) - wraps Project functions for the GUI (get target list, + * execute target) - manages a seperate thread for Ant project execution this + * allows interrupting a running build from a GUI + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +class VAJBuildInfo implements Runnable +{ + + // name of the VA project this BuildInfo belongs to + private String vajProjectName = ""; + + // name of the Ant build file + private String buildFileName = ""; + + // main targets found in the build file + private Vector projectTargets = new Vector(); + + // target selected for execution + private java.lang.String target = ""; + + // log level + private int outputMessageLevel = Project.MSG_INFO; + + // is true if Project initialization was successful + private transient boolean projectInitialized = false; + + // Support for bound properties + protected transient PropertyChangeSupport propertyChange; + + // thread for Ant build execution + private Thread buildThread; + + // Ant Project created from build file + private transient Project project; + + // the listener used to log output. + private BuildListener projectLogger; + + /** + * Creates a BuildInfo object from a String The String must be in the format + * outputMessageLevel'|'buildFileName'|'defaultTarget'|'(project target'|')* + * + * @param data java.lang.String + * @return org.apache.tools.ant.taskdefs.optional.vaj.BuildInfo + */ + public static VAJBuildInfo parse( String data ) + { + VAJBuildInfo result = new VAJBuildInfo(); + + try + { + StringTokenizer tok = new StringTokenizer( data, "|" ); + result.setOutputMessageLevel( tok.nextToken() ); + result.setBuildFileName( tok.nextToken() ); + result.setTarget( tok.nextToken() ); + while( tok.hasMoreTokens() ) + { + result.projectTargets.addElement( tok.nextToken() ); + } + } + catch( Throwable t ) + { + // if parsing the info fails, just return + // an empty VAJBuildInfo + } + return result; + } + + /** + * Search for the insert position to keep names a sorted list of Strings + * This method has been copied from org.apache.tools.ant.Main + * + * @param names Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private static int findTargetPosition( Vector names, String name ) + { + int res = names.size(); + for( int i = 0; i < names.size() && res == names.size(); i++ ) + { + if( name.compareTo( ( String )names.elementAt( i ) ) < 0 ) + { + res = i; + } + } + return res; + } + + /** + * Sets the build file name + * + * @param newBuildFileName The new BuildFileName value + */ + public void setBuildFileName( String newBuildFileName ) + { + String oldValue = buildFileName; + buildFileName = newBuildFileName; + setProjectInitialized( false ); + firePropertyChange( "buildFileName", oldValue, buildFileName ); + } + + /** + * Sets the log level (value must be one of the constants in Project) + * + * @param newOutputMessageLevel The new OutputMessageLevel value + */ + public void setOutputMessageLevel( int newOutputMessageLevel ) + { + int oldValue = outputMessageLevel; + outputMessageLevel = newOutputMessageLevel; + firePropertyChange( "outputMessageLevel", + new Integer( oldValue ), new Integer( outputMessageLevel ) ); + } + + /** + * Sets the target to execute when executeBuild is called + * + * @param newTarget build target + */ + public void setTarget( String newTarget ) + { + String oldValue = target; + target = newTarget; + firePropertyChange( "target", oldValue, target ); + } + + /** + * Sets the name of the Visual Age for Java project where this BuildInfo + * belongs to + * + * @param newVAJProjectName The new VAJProjectName value + */ + public void setVAJProjectName( String newVAJProjectName ) + { + String oldValue = vajProjectName; + vajProjectName = newVAJProjectName; + firePropertyChange( "VAJProjectName", oldValue, vajProjectName ); + } + + /** + * Returns the build file name. + * + * @return build file name. + */ + public String getBuildFileName() + { + return buildFileName; + } + + /** + * Returns the log level + * + * @return log level. + */ + public int getOutputMessageLevel() + { + return outputMessageLevel; + } + + /** + * return a list of all targets in the current buildfile + * + * @return The ProjectTargets value + */ + public Vector getProjectTargets() + { + return projectTargets; + } + + /** + * returns the selected target. + * + * @return The Target value + */ + public java.lang.String getTarget() + { + return target; + } + + /** + * returns the VA project name + * + * @return The VAJProjectName value + */ + public String getVAJProjectName() + { + return vajProjectName; + } + + /** + * Returns true, if the Ant project is initialized (i.e. buildfile loaded) + * + * @return The ProjectInitialized value + */ + public boolean isProjectInitialized() + { + return projectInitialized; + } + + + /** + * The addPropertyChangeListener method was generated to support the + * propertyChange field. + * + * @param listener The feature to be added to the PropertyChangeListener + * attribute + */ + public synchronized void addPropertyChangeListener( PropertyChangeListener listener ) + { + getPropertyChange().addPropertyChangeListener( listener ); + } + + /** + * Returns the BuildInfo information as String. The BuildInfo can be rebuilt + * from that String by calling parse(). + * + * @return java.lang.String + */ + public String asDataString() + { + String result = getOutputMessageLevel() + "|" + getBuildFileName() + + "|" + getTarget(); + for( Enumeration e = getProjectTargets().elements(); + e.hasMoreElements(); ) + { + result = result + "|" + e.nextElement(); + } + + return result; + } + + + /** + * cancels a build. + */ + public void cancelBuild() + { + buildThread.interrupt(); + } + + /** + * Executes the target set by setTarget(). + * + * @param logger Description of Parameter + */ + public void executeProject( BuildListener logger ) + { + Throwable error; + projectLogger = logger; + try + { + buildThread = new Thread( this ); + buildThread.setPriority( Thread.MIN_PRIORITY ); + buildThread.start(); + } + catch( RuntimeException exc ) + { + error = exc; + throw exc; + } + catch( Error err ) + { + error = err; + throw err; + } + } + + /** + * The firePropertyChange method was generated to support the propertyChange + * field. + * + * @param propertyName Description of Parameter + * @param oldValue Description of Parameter + * @param newValue Description of Parameter + */ + public void firePropertyChange( java.lang.String propertyName, java.lang.Object oldValue, java.lang.Object newValue ) + { + getPropertyChange().firePropertyChange( propertyName, oldValue, newValue ); + } + + /** + * The removePropertyChangeListener method was generated to support the + * propertyChange field. + * + * @param listener Description of Parameter + */ + public synchronized void removePropertyChangeListener( PropertyChangeListener listener ) + { + getPropertyChange().removePropertyChangeListener( listener ); + } + + /** + * Executes a build. This method is executed by the Ant execution thread + */ + public void run() + { + try + { + InterruptedChecker ic = new InterruptedChecker( projectLogger ); + BuildEvent e = new BuildEvent( getProject() ); + try + { + ic.buildStarted( e ); + + if( !isProjectInitialized() ) + { + initProject(); + } + + project.addBuildListener( ic ); + project.executeTarget( target ); + + ic.buildFinished( e ); + } + catch( Throwable t ) + { + e.setException( t ); + ic.buildFinished( e ); + } + finally + { + project.removeBuildListener( ic ); + } + } + catch( Throwable t2 ) + { + System.out.println( "unexpected exception!" ); + t2.printStackTrace(); + } + } + + /** + * reloads the build file and updates the target list + */ + public void updateTargetList() + { + project = new Project(); + initProject(); + projectTargets.removeAllElements(); + Enumeration ptargets = project.getTargets().elements(); + while( ptargets.hasMoreElements() ) + { + Target currentTarget = ( Target )ptargets.nextElement(); + if( currentTarget.getDescription() != null ) + { + String targetName = currentTarget.getName(); + int pos = findTargetPosition( projectTargets, targetName ); + projectTargets.insertElementAt( targetName, pos ); + } + } + } + + /** + * Accessor for the propertyChange field. + * + * @return The PropertyChange value + */ + protected PropertyChangeSupport getPropertyChange() + { + if( propertyChange == null ) + { + propertyChange = new PropertyChangeSupport( this ); + } + return propertyChange; + } + + /** + * Sets the log level (value must be one of the constants in Project) + * + * @param outputMessageLevel log level as String. + */ + private void setOutputMessageLevel( String outputMessageLevel ) + { + int level = Integer.parseInt( outputMessageLevel ); + setOutputMessageLevel( level ); + } + + /** + * sets the initialized flag + * + * @param initialized The new ProjectInitialized value + */ + private void setProjectInitialized( boolean initialized ) + { + Boolean oldValue = new Boolean( projectInitialized ); + projectInitialized = initialized; + firePropertyChange( "projectInitialized", oldValue, new Boolean( projectInitialized ) ); + } + + /** + * Returns the Ant project + * + * @return org.apache.tools.ant.Project + */ + private Project getProject() + { + if( project == null ) + { + project = new Project(); + } + return project; + } + + /** + * Initializes the Ant project. Assumes that the project attribute is + * already set. + */ + private void initProject() + { + try + { + project.init(); + File buildFile = new File( getBuildFileName() ); + project.setUserProperty( "ant.file", buildFile.getAbsolutePath() ); + ProjectHelper.configureProject( project, buildFile ); + setProjectInitialized( true ); + } + catch( RuntimeException exc ) + { + setProjectInitialized( false ); + throw exc; + } + catch( Error err ) + { + setProjectInitialized( false ); + throw err; + } + } + + /** + * This exception is thrown when a build is interrupted + * + * @author RT + */ + public static class BuildInterruptedException extends BuildException + { + public String toString() + { + return "BUILD INTERRUPTED"; + } + } + + /** + * BuildListener which checks for interruption and throws Exception if build + * process is interrupted. This class is a wrapper around a 'real' listener. + * + * @author RT + */ + private class InterruptedChecker implements BuildListener + { + // the real listener + BuildListener wrappedListener; + + /** + * Can only be constructed as wrapper around a real listener + * + * @param listener the real listener + */ + public InterruptedChecker( BuildListener listener ) + { + super(); + wrappedListener = listener; + } + + /** + * Fired after the last target has finished. This event will still be + * thrown if an error occured during the build. + * + * @param event Description of Parameter + */ + public void buildFinished( BuildEvent event ) + { + wrappedListener.buildFinished( event ); + checkInterrupted(); + } + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + public void buildStarted( BuildEvent event ) + { + wrappedListener.buildStarted( event ); + checkInterrupted(); + } + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + */ + public void messageLogged( BuildEvent event ) + { + wrappedListener.messageLogged( event ); + checkInterrupted(); + } + + /** + * Fired when a target has finished. This event will still be thrown if + * an error occured during the build. + * + * @param event Description of Parameter + */ + public void targetFinished( BuildEvent event ) + { + wrappedListener.targetFinished( event ); + checkInterrupted(); + } + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + */ + public void targetStarted( BuildEvent event ) + { + wrappedListener.targetStarted( event ); + checkInterrupted(); + } + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + */ + public void taskFinished( BuildEvent event ) + { + wrappedListener.taskFinished( event ); + checkInterrupted(); + } + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + */ + public void taskStarted( BuildEvent event ) + { + wrappedListener.taskStarted( event ); + checkInterrupted(); + } + + /** + * checks if the thread was interrupted. When an interrupt occured, + * throw an Exception to stop the execution. + */ + protected void checkInterrupted() + { + if( buildThread.isInterrupted() ) + { + throw new BuildInterruptedException(); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java new file mode 100644 index 000000000..4d49c923d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.PatternSet; + +/** + * Export packages from the Visual Age for Java workspace. The packages are + * specified similar to all other MatchingTasks. Since the VA Workspace is not + * file based, this task is simulating a directory hierarchy for the workspace: + * The 'root' contains all project 'dir's, and the projects contain their + * respective package 'dir's. Example:

      <vajexport + * destdir="C:/builddir/source">  <include + * name="/MyVAProject/org/foo/subsystem1/**" />  <exclude + * name="/MyVAProject/org/foo/subsystem1/test/**"/> </vajexport> + *
      exports all packages in the project MyVAProject which start + * with 'org.foo.subsystem1' except of these starting with + * 'org.foo.subsystem1.test'. There are flags to choose which items to export: + * exportSources: export Java sources exportResources: export project resources + * exportClasses: export class files exportDebugInfo: export class files with + * debug info (use with exportClasses) default is exporting Java files and + * resources. + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJExport extends VAJTask +{ + protected boolean exportSources = true; + protected boolean exportResources = true; + protected boolean exportClasses = false; + protected boolean exportDebugInfo = false; + protected boolean useDefaultExcludes = true; + protected boolean overwrite = true; + + protected PatternSet patternSet = new PatternSet(); + //set set... method comments for description + protected File destDir; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Set the destination directory into which the selected items should be + * exported + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. Currently only patterns denoting packages are supported + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + patternSet.setExcludes( excludes ); + } + + /** + * if exportClasses is set, class files are exported + * + * @param doExport The new ExportClasses value + */ + public void setExportClasses( boolean doExport ) + { + exportClasses = doExport; + } + + /** + * if exportDebugInfo is set, the exported class files contain debug info + * + * @param doExport The new ExportDebugInfo value + */ + public void setExportDebugInfo( boolean doExport ) + { + exportDebugInfo = doExport; + } + + /** + * if exportResources is set, resource file will be exported + * + * @param doExport The new ExportResources value + */ + public void setExportResources( boolean doExport ) + { + exportResources = doExport; + } + + /** + * if exportSources is set, java files will be exported + * + * @param doExport The new ExportSources value + */ + public void setExportSources( boolean doExport ) + { + exportSources = doExport; + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space.Currently only patterns denoting packages are supported + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + patternSet.setIncludes( includes ); + } + + /** + * if Overwrite is set, files will be overwritten during export + * + * @param doOverwrite The new Overwrite value + */ + public void setOverwrite( boolean doOverwrite ) + { + overwrite = doOverwrite; + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + return patternSet.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + return patternSet.createInclude(); + } + + /** + * do the export + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a destdir + if( destDir == null ) + { + throw new BuildException( "destdir attribute must be set!" ); + } + + // delegate the export to the VAJUtil object. + getUtil().exportPackages( destDir, + patternSet.getIncludePatterns( getProject() ), + patternSet.getExcludePatterns( getProject() ), + exportClasses, exportDebugInfo, + exportResources, exportSources, + useDefaultExcludes, overwrite ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java new file mode 100644 index 000000000..a78a51efc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; + +/** + * A Remote Access to Tools Servlet to extract package sets from the Workbench + * to the local file system. The following table describes the servlet + * parameters. + * + * + * + * + * + * Parameter + * + * + * + * Values + * + * + * + * Description + * + * + * + * + * + * + * + * dir + * + * + * + * Any valid directory name on the server. + * + * + * + * The directory to export the files to on the machine where the servlet + * is being run. If the directory doesn't exist, it will be created.

      + * + * Relative paths are relative to IBMVJava/ide/tools/com-ibm-ivj-toolserver, + * where IBMVJava is the VisualAge for Java installation directory. + * + * + * + * + * + * + * + * include + * + * + * + * See below. + * + * + * + * The pattern used to indicate which projects and packages to export. + * + * + * + * + * + * + * + * + * exclude + * + * + * + * See below + * + * + * + * The pattern used to indicate which projects and packages not + * to export. + * + * + * + * + * + * + * + * cls + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export class files. Defaults to "no". + * + * + * + * + * + * + * + * src + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export source files. Defaults to "yes". + * + * + * + * + * + * + * + * res + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export resource files associated with the included project(s). Defaults + * to "yes". + * + * + * + * + * + * + * + * dex + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Use the default exclusion patterns. Defaults to "yes". See below for an + * explanation of default excludes. + * + * + * + * + * + * + * + * owr + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Overwrite any existing files. Defaults to "yes". + * + * + * + * + * + *

      + * + * The vajexport servlet uses include and exclude parameters to form the + * criteria for selecting packages to export. The parameter is broken up into + * ProjectName/packageNameSegments, where ProjectName is what you expect, and + * packageNameSegments is a partial (or complete) package name, separated by + * forward slashes, rather than periods. Each packageNameSegment can have + * wildcard characters.

      + * + * + * + * + * + * Wildcard Characters + * + * + * + * Description + * + * + * + * + * + * + * + * * + * + * + * + * Match zero or more characters in that segment. + * + * + * + * + * + * + * + * ? + * + * + * + * Match one character in that segment. + * + * + * + * + * + * + * + * ** + * + * + * + * Matches all characters in zero or more segments. + * + * + * + * + * + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJExportServlet extends VAJToolsServlet +{ + // constants for servlet param names + public final static String WITH_DEBUG_INFO = "deb"; + public final static String OVERWRITE_PARAM = "owr"; + + /** + * Respond to a request to export packages from the Workbench. + */ + protected void executeRequest() + { + getUtil().exportPackages( + new File( getFirstParamValueString( DIR_PARAM ) ), + getParamValues( INCLUDE_PARAM ), + getParamValues( EXCLUDE_PARAM ), + getBooleanParam( CLASSES_PARAM, false ), + getBooleanParam( WITH_DEBUG_INFO, false ), + getBooleanParam( RESOURCES_PARAM, true ), + getBooleanParam( SOURCES_PARAM, true ), + getBooleanParam( DEFAULT_EXCLUDES_PARAM, true ), + getBooleanParam( OVERWRITE_PARAM, true ) + ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java new file mode 100644 index 000000000..36d63bbf8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.lang.reflect.Field; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.types.FileSet; + +/** + * Import source, class files, and resources to the Visual Age for Java + * workspace using FileSets.

      + * + * Example:

      + * <vajimport project="MyVAProject">
      + *   <fileset dir="src">
      + *     <include name="org/foo/subsystem1/**" />
      + *     <exclude name="/org/foo/subsystem1/test/**" />
      + *  </fileset>
      + * </vajexport>
      + * 
      import all source and resource files from the "src" directory which + * start with 'org.foo.subsystem1', except of these starting with + * 'org.foo.subsystem1.test' into the project MyVAProject.

      + * + * If MyVAProject isn't loaded into the Workspace, a new edition is created in + * the repository and automatically loaded into the Workspace. There has to be + * at least one nested FileSet element.

      + * + * There are attributes to choose which items to export: + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * project + * + * + * + * the name of the Project to import to + * + * + * + * Yes + * + * + * + * + * + * + * + * importSources + * + * + * + * import Java sources, defaults to "yes" + * + * + * + * No + * + * + * + * + * + * + * + * importResources + * + * + * + * import resource files (anything that doesn't end with .java or .class), + * defaults to "yes" + * + * + * + * No + * + * + * + * + * + * + * + * importClasses + * + * + * + * import class files, defaults to "no" + * + * + * + * No + * + * + * + * + * + * + * + * @author RT + * @author: Glenn McAllister, inspired by a similar task written by Peter Kelley + */ +public class VAJImport extends VAJTask +{ + protected Vector filesets = new Vector(); + protected boolean importSources = true; + protected boolean importResources = true; + protected boolean importClasses = false; + protected String importProject = null; + protected boolean useDefaultExcludes = true; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Import .class files. + * + * @param importClasses The new ImportClasses value + */ + public void setImportClasses( boolean importClasses ) + { + this.importClasses = importClasses; + } + + /** + * Import resource files (anything that doesn't end in .class or .java) + * + * @param importResources The new ImportResources value + */ + public void setImportResources( boolean importResources ) + { + this.importResources = importResources; + } + + /** + * Import .java files + * + * @param importSources The new ImportSources value + */ + public void setImportSources( boolean importSources ) + { + this.importSources = importSources; + } + + + /** + * The VisualAge for Java Project name to import into. + * + * @param projectName The new Project value + */ + public void setProject( String projectName ) + { + this.importProject = projectName; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Do the import. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( filesets.size() == 0 ) + { + throw new BuildException( "At least one fileset is required!" ); + } + + if( importProject == null || "".equals( importProject ) ) + { + throw new BuildException( "The VisualAge for Java Project name is required!" ); + } + + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + importFileset( ( FileSet )e.nextElement() ); + } + } + + /** + * Import all files from the fileset into the Project in the Workspace. + * + * @param fileset Description of Parameter + */ + protected void importFileset( FileSet fileset ) + { + DirectoryScanner ds = fileset.getDirectoryScanner( this.project ); + if( ds.getIncludedFiles().length == 0 ) + { + return; + } + + String[] includes = null; + String[] excludes = null; + + // Hack to get includes and excludes. We could also use getIncludedFiles, + // but that would result in very long HTTP-requests. + // Therefore we want to send the patterns only to the remote tool server + // and let him figure out the files. + try + { + Class directoryScanner = ds.getClass(); + + Field includesField = directoryScanner.getDeclaredField( "includes" ); + includesField.setAccessible( true ); + includes = ( String[] )includesField.get( ds ); + + Field excludesField = directoryScanner.getDeclaredField( "excludes" ); + excludesField.setAccessible( true ); + excludes = ( String[] )excludesField.get( ds ); + } + catch( NoSuchFieldException nsfe ) + { + throw new BuildException( + "DirectoryScanner.includes or .excludes missing" + nsfe.getMessage() ); + } + catch( IllegalAccessException iae ) + { + throw new BuildException( + "Access to DirectoryScanner.includes or .excludes not allowed" ); + } + + getUtil().importFiles( importProject, ds.getBasedir(), + includes, excludes, + importClasses, importResources, importSources, + useDefaultExcludes ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java new file mode 100644 index 000000000..9ea01067e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; + + +/** + * A Remote Access to Tools Servlet to import a Project from files into the + * Repository. The following table describes the servlet parameters. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      + * Parameter + * + * Description + *
      + * project + * + * The name of the project where you want the imported items to go. + *
      + * dir + * + * The directory you want to import from. + *
      + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJImportServlet extends VAJToolsServlet +{ + /** + * Respond to a request to import files to the Repository + */ + protected void executeRequest() + { + getUtil().importFiles( + getFirstParamValueString( PROJECT_NAME_PARAM ), + new File( getFirstParamValueString( DIR_PARAM ) ), + getParamValues( INCLUDE_PARAM ), + getParamValues( EXCLUDE_PARAM ), + getBooleanParam( CLASSES_PARAM, false ), + getBooleanParam( RESOURCES_PARAM, true ), + getBooleanParam( SOURCES_PARAM, true ), + false// no default excludes, because they + // are already added on client side + // getBooleanParam(DEFAULT_EXCLUDES_PARAM, true) + ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java new file mode 100644 index 000000000..d42912f1f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.util.Vector; + +/** + * Load specific project versions into the Visual Age for Java workspace. Each + * project and version name has to be specified completely. Example: + *

      <vajload>  <project name="MyVAProject" + * version="2.1"/>  <project name="Apache Xerces" version="1.2.0"/> + * </vajload>
      + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJLoad extends VAJTask +{ + Vector projectDescriptions = new Vector(); + + /** + * Add a project description entry on the project list. + * + * @return Description of the Returned Value + */ + public VAJProjectDescription createVAJProject() + { + VAJProjectDescription d = new VAJProjectDescription(); + projectDescriptions.addElement( d ); + return d; + } + + /** + * Load specified projects. + */ + public void execute() + { + getUtil().loadProjects( projectDescriptions ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java new file mode 100644 index 000000000..86c9f8a79 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; + + + + + + +/** + * This is only there for backward compatibility with the default task list and + * will be removed soon + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJLoadProjects extends VAJLoad +{ +} + + + + + + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java new file mode 100644 index 000000000..1fca4d63d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.util.Vector; + +/** + * A Remote Access to Tools Servlet to load a Project from the Repository into + * the Workbench. The following table describes the servlet parameters. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      + * Parameter + * + * Description + *
      + * project + * + * The name of the Project you want to load into the Workbench. + *
      + * version + * + * The version of the package you want to load into the Workbench. + *
      + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJLoadServlet extends VAJToolsServlet +{ + + // constants for servlet param names + public final static String VERSION_PARAM = "version"; + + /** + * Respond to a request to load a project from the Repository into the + * Workbench. + */ + protected void executeRequest() + { + String[] projectNames = getParamValues( PROJECT_NAME_PARAM ); + String[] versionNames = getParamValues( VERSION_PARAM ); + + Vector projectDescriptions = new Vector( projectNames.length ); + for( int i = 0; i < projectNames.length && i < versionNames.length; i++ ) + { + VAJProjectDescription desc = new VAJProjectDescription(); + desc.setName( projectNames[i] ); + desc.setVersion( versionNames[i] ); + projectDescriptions.addElement( desc ); + } + + util.loadProjects( projectDescriptions ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java new file mode 100644 index 000000000..247c2427a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java @@ -0,0 +1,530 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.ExportCodeSpec; +import com.ibm.ivj.util.base.ImportCodeSpec; +import com.ibm.ivj.util.base.IvjException; +import com.ibm.ivj.util.base.Package; +import com.ibm.ivj.util.base.Project; +import com.ibm.ivj.util.base.ProjectEdition; +import com.ibm.ivj.util.base.ToolEnv; +import com.ibm.ivj.util.base.Type; +import com.ibm.ivj.util.base.Workspace; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; + + +/** + * Helper class for VAJ tasks. Holds Workspace singleton and wraps IvjExceptions + * into BuildExceptions + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +abstract class VAJLocalUtil implements VAJUtil +{ + // singleton containing the VAJ workspace + private static Workspace workspace; + + /** + * get a project from the Workspace. + * + * @param importProject Description of Parameter + * @return The VAJProject value + */ + static Project getVAJProject( String importProject ) + { + Project found = null; + Project[] currentProjects = getWorkspace().getProjects(); + + for( int i = 0; i < currentProjects.length; i++ ) + { + Project p = currentProjects[i]; + if( p.getName().equals( importProject ) ) + { + found = p; + break; + } + } + + if( found == null ) + { + try + { + found = getWorkspace().createProject( importProject, true ); + } + catch( IvjException e ) + { + throw createBuildException( "Error while creating Project " + + importProject + ": ", e ); + } + } + + return found; + } + + /** + * returns the current VAJ workspace. + * + * @return com.ibm.ivj.util.base.Workspace + */ + static Workspace getWorkspace() + { + if( workspace == null ) + { + workspace = ToolEnv.connectToWorkspace(); + if( workspace == null ) + { + throw new BuildException( + "Unable to connect to Workspace! " + + "Make sure you are running in VisualAge for Java." ); + } + } + + return workspace; + } + + /** + * Wraps IvjException into a BuildException + * + * @param errMsg Additional error message + * @param e IvjException which is wrapped + * @return org.apache.tools.ant.BuildException + */ + static BuildException createBuildException( + String errMsg, IvjException e ) + { + errMsg = errMsg + "\n" + e.getMessage(); + String[] errors = e.getErrors(); + if( errors != null ) + { + for( int i = 0; i < errors.length; i++ ) + { + errMsg = errMsg + "\n" + errors[i]; + } + } + return new BuildException( errMsg, e ); + } + + + //----------------------------------------------------------- + // export + //----------------------------------------------------------- + + /** + * export packages + * + * @param dest Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + public void exportPackages( + File dest, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, + boolean exportResources, boolean exportSources, + boolean useDefaultExcludes, boolean overwrite ) + { + if( includePatterns == null || includePatterns.length == 0 ) + { + log( "You must specify at least one include attribute. " + + "Not exporting", MSG_ERR ); + } + else + { + try + { + VAJWorkspaceScanner scanner = new VAJWorkspaceScanner(); + scanner.setIncludes( includePatterns ); + scanner.setExcludes( excludePatterns ); + if( useDefaultExcludes ) + { + scanner.addDefaultExcludes(); + } + scanner.scan(); + + Package[] packages = scanner.getIncludedPackages(); + + log( "Exporting " + packages.length + " package(s) to " + + dest, MSG_INFO ); + for( int i = 0; i < packages.length; i++ ) + { + log( " " + packages[i].getName(), MSG_VERBOSE ); + } + + ExportCodeSpec exportSpec = new ExportCodeSpec(); + exportSpec.setPackages( packages ); + exportSpec.includeJava( exportSources ); + exportSpec.includeClass( exportClasses ); + exportSpec.includeResources( exportResources ); + exportSpec.includeClassDebugInfo( exportDebugInfo ); + exportSpec.useSubdirectories( true ); + exportSpec.overwriteFiles( overwrite ); + exportSpec.setExportDirectory( dest.getAbsolutePath() ); + + getWorkspace().exportData( exportSpec ); + } + catch( IvjException ex ) + { + throw createBuildException( "Exporting failed!", ex ); + } + } + } + + + //----------------------------------------------------------- + // import + //----------------------------------------------------------- + + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @exception BuildException Description of Exception + */ + public void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ) + throws BuildException + { + + if( importProject == null || "".equals( importProject ) ) + { + throw new BuildException( "The VisualAge for Java project " + + "name is required!" ); + } + + ImportCodeSpec importSpec = new ImportCodeSpec(); + importSpec.setDefaultProject( getVAJProject( importProject ) ); + + DirectoryScanner ds = new DirectoryScanner(); + ds.setBasedir( srcDir ); + ds.setIncludes( includePatterns ); + ds.setExcludes( excludePatterns ); + if( useDefaultExcludes ) + { + ds.addDefaultExcludes(); + } + ds.scan(); + + Vector classes = new Vector(); + Vector sources = new Vector(); + Vector resources = new Vector(); + + scanForImport( srcDir, ds.getIncludedFiles(), classes, sources, resources ); + + StringBuffer summaryLog = new StringBuffer( "Importing " ); + addFilesToImport( importSpec, importClasses, classes, "Class", summaryLog ); + addFilesToImport( importSpec, importSources, sources, "Java", summaryLog ); + addFilesToImport( importSpec, importResources, resources, "Resource", summaryLog ); + importSpec.setResourcePath( srcDir.getAbsolutePath() ); + + summaryLog.append( " into the project '" ); + summaryLog.append( importProject ); + summaryLog.append( "'." ); + + log( summaryLog.toString(), MSG_INFO ); + + try + { + Type[] importedTypes = getWorkspace().importData( importSpec ); + if( importedTypes == null ) + { + throw new BuildException( "Unable to import into Workspace!" ); + } + else + { + log( importedTypes.length + " types imported", MSG_DEBUG ); + for( int i = 0; i < importedTypes.length; i++ ) + { + log( importedTypes[i].getPackage().getName() + + "." + importedTypes[i].getName() + + " into " + importedTypes[i].getProject().getName(), + MSG_DEBUG ); + } + } + } + catch( IvjException ivje ) + { + throw createBuildException( "Error while importing into workspace: ", + ivje ); + } + } + + + //----------------------------------------------------------- + // load + //----------------------------------------------------------- + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + public void loadProjects( Vector projectDescriptions ) + { + Vector expandedDescs = getExpandedDescriptions( projectDescriptions ); + + // output warnings for projects not found + for( Enumeration e = projectDescriptions.elements(); e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + if( !d.projectFound() ) + { + log( "No Projects match the name " + d.getName(), MSG_WARN ); + } + } + + log( "Loading " + expandedDescs.size() + + " project(s) into workspace", MSG_INFO ); + + for( Enumeration e = expandedDescs.elements(); + e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + + ProjectEdition pe = findProjectEdition( d.getName(), d.getVersion() ); + try + { + log( "Loading '" + d.getName() + "', Version '" + d.getVersion() + + "', into Workspace", MSG_VERBOSE ); + pe.loadIntoWorkspace(); + } + catch( IvjException ex ) + { + throw createBuildException( "Project '" + d.getName() + + "' could not be loaded.", ex ); + } + } + } + + + /** + * return project descriptions containing full project names instead of + * patterns with wildcards. + * + * @param projectDescs Description of Parameter + * @return The ExpandedDescriptions value + */ + private Vector getExpandedDescriptions( Vector projectDescs ) + { + Vector expandedDescs = new Vector( projectDescs.size() ); + try + { + String[] projectNames = + getWorkspace().getRepository().getProjectNames(); + for( int i = 0; i < projectNames.length; i++ ) + { + for( Enumeration e = projectDescs.elements(); + e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + String pattern = d.getName(); + if( VAJWorkspaceScanner.match( pattern, projectNames[i] ) ) + { + d.setProjectFound(); + expandedDescs.addElement( new VAJProjectDescription( + projectNames[i], d.getVersion() ) ); + break; + } + } + } + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + return expandedDescs; + } + + /** + * Adds files to an import specification. Helper method for importFiles() + * + * @param spec import specification + * @param doImport only add files if doImport is true + * @param files the files to add + * @param fileType type of files (Source/Class/Resource) + * @param summaryLog buffer for logging + */ + private void addFilesToImport( + ImportCodeSpec spec, boolean doImport, + Vector files, String fileType, + StringBuffer summaryLog ) + { + + if( doImport ) + { + String[] fileArr = new String[files.size()]; + files.copyInto( fileArr ); + try + { + // here it is assumed that fileType is one of the + // following strings: // "Java", "Class", "Resource" + String methodName = "set" + fileType + "Files"; + Class[] methodParams = new Class[]{fileArr.getClass()}; + java.lang.reflect.Method method = + spec.getClass().getDeclaredMethod( methodName, methodParams ); + method.invoke( spec, new Object[]{fileArr} ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + if( files.size() > 0 ) + { + logFiles( files, fileType ); + summaryLog.append( files.size() ); + summaryLog.append( " " + fileType.toLowerCase() + " file" ); + summaryLog.append( files.size() > 1 ? "s, " : ", " ); + } + } + } + + /** + * returns a list of project names matching the given pattern + * + * @param pattern Description of Parameter + * @return Description of the Returned Value + */ + private Vector findMatchingProjects( String pattern ) + { + String[] projectNames; + try + { + projectNames = getWorkspace().getRepository().getProjectNames(); + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + Vector matchingProjects = new Vector(); + for( int i = 0; i < projectNames.length; i++ ) + { + if( VAJWorkspaceScanner.match( pattern, projectNames[i] ) ) + { + matchingProjects.addElement( projectNames[i] ); + } + } + + return matchingProjects; + } + + /** + * Finds a specific project edition in the repository. + * + * @param name project name + * @param versionName project version name + * @return com.ibm.ivj.util.base.ProjectEdition the specified edition + */ + private ProjectEdition findProjectEdition( + String name, String versionName ) + { + try + { + ProjectEdition[] editions = null; + editions = getWorkspace().getRepository().getProjectEditions( name ); + + if( editions == null ) + { + throw new BuildException( "Project " + name + " doesn't exist" ); + } + + ProjectEdition pe = null; + for( int i = 0; i < editions.length && pe == null; i++ ) + { + if( versionName.equals( editions[i].getVersionName() ) ) + { + pe = editions[i]; + } + } + if( pe == null ) + { + throw new BuildException( "Version " + versionName + + " of Project " + name + " doesn't exist" ); + } + return pe; + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + } + + /** + * Logs a list of file names to the message log + * + * @param fileNames java.util.Vector file names to be logged + * @param fileType Description of Parameter + */ + private void logFiles( Vector fileNames, String fileType ) + { + log( fileType + " files found for import:", MSG_VERBOSE ); + for( Enumeration e = fileNames.elements(); e.hasMoreElements(); ) + { + log( " " + e.nextElement(), MSG_VERBOSE ); + } + } + + + /** + * Sort the files into classes, sources, and resources. + * + * @param dir Description of Parameter + * @param files Description of Parameter + * @param classes Description of Parameter + * @param sources Description of Parameter + * @param resources Description of Parameter + */ + private void scanForImport( + File dir, + String[] files, + Vector classes, + Vector sources, + Vector resources ) + { + for( int i = 0; i < files.length; i++ ) + { + String file = ( new File( dir, files[i] ) ).getAbsolutePath(); + if( file.endsWith( ".java" ) || file.endsWith( ".JAVA" ) ) + { + sources.addElement( file ); + } + else + if( file.endsWith( ".class" ) || file.endsWith( ".CLASS" ) ) + { + classes.addElement( file ); + } + else + { + // for resources VA expects the path relative to the resource path + resources.addElement( files[i] ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java new file mode 100644 index 000000000..9e2105863 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import org.apache.tools.ant.BuildException; + +/** + * Type class. Holds information about a project edition. + * + * @author RT + * @author: Wolf Siberski + */ +public class VAJProjectDescription +{ + private String name; + private boolean projectFound; + private String version; + + public VAJProjectDescription() { } + + public VAJProjectDescription( String n, String v ) + { + name = n; + version = v; + } + + public void setName( String newName ) + { + if( newName == null || newName.equals( "" ) ) + { + throw new BuildException( "name attribute must be set" ); + } + name = newName; + } + + public void setProjectFound() + { + projectFound = true; + } + + public void setVersion( String newVersion ) + { + if( newVersion == null || newVersion.equals( "" ) ) + { + throw new BuildException( "version attribute must be set" ); + } + version = newVersion; + } + + public String getName() + { + return name; + } + + public String getVersion() + { + return version; + } + + public boolean projectFound() + { + return projectFound; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java new file mode 100644 index 000000000..c14253574 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Helper class for VAJ tasks. Holds Workspace singleton and wraps IvjExceptions + * into BuildExceptions + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +class VAJRemoteUtil implements VAJUtil +{ + // calling task + Task caller; + + // VAJ remote tool server + String remoteServer; + + public VAJRemoteUtil( Task caller, String remote ) + { + this.caller = caller; + this.remoteServer = remote; + } + + /** + * export the array of Packages + * + * @param destDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + public void exportPackages( File destDir, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, boolean exportResources, + boolean exportSources, boolean useDefaultExcludes, boolean overwrite ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajexport?" + + VAJExportServlet.WITH_DEBUG_INFO + "=" + exportDebugInfo + "&" + + VAJExportServlet.OVERWRITE_PARAM + "=" + overwrite + "&" + + assembleImportExportParams( destDir, + includePatterns, excludePatterns, + exportClasses, exportResources, + exportSources, useDefaultExcludes ); + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + } + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + */ + public void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajimport?" + + VAJImportServlet.PROJECT_NAME_PARAM + "=" + + importProject + "&" + + assembleImportExportParams( srcDir, + includePatterns, excludePatterns, + importClasses, importResources, + importSources, useDefaultExcludes ); + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + + } + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + public void loadProjects( Vector projectDescriptions ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajload?"; + String delimiter = ""; + for( Enumeration e = projectDescriptions.elements(); e.hasMoreElements(); ) + { + VAJProjectDescription pd = ( VAJProjectDescription )e.nextElement(); + request = request + + delimiter + VAJLoadServlet.PROJECT_NAME_PARAM + + "=" + pd.getName().replace( ' ', '+' ) + + "&" + VAJLoadServlet.VERSION_PARAM + + "=" + pd.getVersion().replace( ' ', '+' ); + //the first param needs no delimiter, but all other + delimiter = "&"; + } + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + } + + /** + * logs a message. + * + * @param msg Description of Parameter + * @param level Description of Parameter + */ + public void log( String msg, int level ) + { + caller.log( msg, level ); + } + + /** + * Assemble string for parameters common for import and export Helper method + * to remove double code. + * + * @param dir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param includeClasses Description of Parameter + * @param includeResources Description of Parameter + * @param includeSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @return Description of the Returned Value + */ + private String assembleImportExportParams( + File dir, + String[] includePatterns, String[] excludePatterns, + boolean includeClasses, boolean includeResources, + boolean includeSources, boolean useDefaultExcludes ) + { + String result = + VAJToolsServlet.DIR_PARAM + "=" + + dir.getAbsolutePath().replace( '\\', '/' ) + "&" + + VAJToolsServlet.CLASSES_PARAM + "=" + includeClasses + "&" + + VAJToolsServlet.RESOURCES_PARAM + "=" + includeResources + "&" + + VAJToolsServlet.SOURCES_PARAM + "=" + includeSources + "&" + + VAJToolsServlet.DEFAULT_EXCLUDES_PARAM + "=" + useDefaultExcludes; + + if( includePatterns != null ) + { + for( int i = 0; i < includePatterns.length; i++ ) + { + result = result + "&" + VAJExportServlet.INCLUDE_PARAM + "=" + + includePatterns[i].replace( ' ', '+' ).replace( '\\', '/' ); + } + } + if( excludePatterns != null ) + { + for( int i = 0; i < excludePatterns.length; i++ ) + { + result = result + "&" + VAJExportServlet.EXCLUDE_PARAM + "=" + + excludePatterns[i].replace( ' ', '+' ).replace( '\\', '/' ); + } + } + + return result; + } + + /** + * Sends a servlet request. + * + * @param request Description of Parameter + */ + private void sendRequest( String request ) + { + boolean requestFailed = false; + try + { + log( "Request: " + request, MSG_DEBUG ); + + //must be HTTP connection + URL requestUrl = new URL( request ); + HttpURLConnection connection = + ( HttpURLConnection )requestUrl.openConnection(); + + InputStream is = null; + // retry three times + for( int i = 0; i < 3; i++ ) + { + try + { + is = connection.getInputStream(); + break; + } + catch( IOException ex ) + { + } + } + if( is == null ) + { + log( "Can't get " + request, MSG_ERR ); + throw new BuildException( "Couldn't execute " + request ); + } + + // log the response + BufferedReader br = new BufferedReader( new InputStreamReader( is ) ); + String line = br.readLine(); + while( line != null ) + { + int level = MSG_ERR; + try + { + // the first char of each line contains the log level + level = Integer.parseInt( line.substring( 0, 1 ) ); + if( level == MSG_ERR ) + { + requestFailed = true; + } + } + catch( Exception e ) + { + log( "Response line doesn't contain log level!", MSG_ERR ); + } + log( line.substring( 2 ), level ); + line = br.readLine(); + } + + } + catch( IOException ex ) + { + log( "Error sending tool request to VAJ" + ex, MSG_ERR ); + throw new BuildException( "Couldn't execute " + request ); + } + if( requestFailed ) + { + throw new BuildException( "VAJ tool request failed" ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java new file mode 100644 index 000000000..3c19052ec --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +/** + * Super class for all VAJ tasks. Contains common attributes (remoteServer) and + * util methods + * + * @author: Wolf Siberski + */ +import org.apache.tools.ant.Task; + + +public class VAJTask extends Task +{ + + // server name / port of VAJ remote tool api server + protected String remoteServer = null; + + // holds the appropriate VAJUtil implementation + private VAJUtil util = null; + + /** + * Set remote server attribute + * + * @param remoteServer The new Remote value + */ + public void setRemote( String remoteServer ) + { + this.remoteServer = remoteServer; + } + + + /** + * returns the VAJUtil implementation + * + * @return The Util value + */ + protected VAJUtil getUtil() + { + if( util == null ) + { + if( remoteServer == null ) + { + util = new VAJLocalToolUtil(); + } + else + { + util = new VAJRemoteUtil( this, remoteServer ); + } + } + return util; + } + + /** + * Adaption of VAJLocalUtil to Task context. + * + * @author RT + */ + class VAJLocalToolUtil extends VAJLocalUtil + { + public void log( String msg, int level ) + { + VAJTask.this.log( msg, level ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java new file mode 100644 index 000000000..c27e163b9 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.StringUtils; + +/** + * Abstract base class to provide common services for the VAJ tool API servlets + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public abstract class VAJToolsServlet extends HttpServlet +{ + + // constants for servlet param names + public final static String DIR_PARAM = "dir"; + public final static String INCLUDE_PARAM = "include"; + public final static String EXCLUDE_PARAM = "exclude"; + public final static String CLASSES_PARAM = "cls"; + public final static String SOURCES_PARAM = "src"; + public final static String RESOURCES_PARAM = "res"; + public final static String DEFAULT_EXCLUDES_PARAM = "dex"; + public final static String PROJECT_NAME_PARAM = "project"; + + // current request + HttpServletRequest request; + + // response to current request + HttpServletResponse response; + + // implementation of VAJUtil used by the servlet + VAJUtil util; + + /** + * Respond to a HTTP request. This method initializes the servlet and + * handles errors. The real work is done in the abstract method + * executeRequest() + * + * @param req Description of Parameter + * @param res Description of Parameter + * @exception ServletException Description of Exception + * @exception IOException Description of Exception + */ + public void doGet( HttpServletRequest req, HttpServletResponse res ) + throws ServletException, IOException + { + try + { + response = res; + request = req; + initRequest(); + executeRequest(); + } + catch( BuildException e ) + { + util.log( "Error occured: " + e.getMessage(), VAJUtil.MSG_ERR ); + } + catch( Exception e ) + { + try + { + if( !( e instanceof BuildException ) ) + { + String trace = StringUtils.getStackTrace( e ); + util.log( "Program error in " + this.getClass().getName() + + ":\n" + trace, VAJUtil.MSG_ERR ); + } + } + catch( Throwable t ) + { + t.printStackTrace(); + } + finally + { + if( !( e instanceof BuildException ) ) + { + throw new ServletException( e.getMessage() ); + } + } + } + } + + /** + * Get the boolean value of a parameter. + * + * @param param Description of Parameter + * @return The BooleanParam value + */ + protected boolean getBooleanParam( String param ) + { + return getBooleanParam( param, false ); + } + + /** + * Get the boolean value of a parameter, with a default value if the + * parameter hasn't been passed to the servlet. + * + * @param param Description of Parameter + * @param defaultValue Description of Parameter + * @return The BooleanParam value + */ + protected boolean getBooleanParam( String param, boolean defaultValue ) + { + String value = getFirstParamValueString( param ); + if( value != null ) + { + return toBoolean( value ); + } + else + { + return defaultValue; + } + } + + /** + * Returns the first encountered value for a parameter. + * + * @param param Description of Parameter + * @return The FirstParamValueString value + */ + protected String getFirstParamValueString( String param ) + { + String[] paramValuesArray = request.getParameterValues( param ); + if( paramValuesArray == null ) + { + return null; + } + return paramValuesArray[0]; + } + + /** + * Returns all values for a parameter. + * + * @param param Description of Parameter + * @return The ParamValues value + */ + protected String[] getParamValues( String param ) + { + return request.getParameterValues( param ); + } + + + /** + * Execute the request by calling the appropriate VAJ tool API methods. This + * method must be implemented by the concrete servlets + */ + protected abstract void executeRequest(); + + /** + * initialize the servlet. + * + * @exception IOException Description of Exception + */ + protected void initRequest() + throws IOException + { + response.setContentType( "text/ascii" ); + if( util == null ) + { + util = new VAJLocalServletUtil(); + } + } + + /** + * A utility method to translate the strings "yes", "true", and "ok" to + * boolean true, and everything else to false. + * + * @param string Description of Parameter + * @return Description of the Returned Value + */ + protected boolean toBoolean( String string ) + { + String lower = string.toLowerCase(); + return ( lower.equals( "yes" ) || lower.equals( "true" ) || lower.equals( "ok" ) ); + } + + /** + * Get the VAJUtil implementation + * + * @return The Util value + */ + VAJUtil getUtil() + { + return util; + } + + /** + * Adaptation of VAJUtil for servlet context. + * + * @author RT + */ + class VAJLocalServletUtil extends VAJLocalUtil + { + public void log( String msg, int level ) + { + try + { + if( msg != null ) + { + msg = msg.replace( '\r', ' ' ); + int i = 0; + while( i < msg.length() ) + { + int nlPos = msg.indexOf( '\n', i ); + if( nlPos == -1 ) + { + nlPos = msg.length(); + } + response.getWriter().println( Integer.toString( level ) + + " " + msg.substring( i, nlPos ) ); + i = nlPos + 1; + } + } + } + catch( IOException e ) + { + throw new BuildException( "logging failed. msg was: " + + e.getMessage() ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java new file mode 100644 index 000000000..203b97252 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; +import java.util.Vector; + +/** + * Helper interface for VAJ tasks. Encapsulates the interface to the VAJ tool + * API. + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +interface VAJUtil +{ + // log levels + public final static int MSG_DEBUG = 4; + public final static int MSG_ERR = 0; + public final static int MSG_INFO = 2; + public final static int MSG_VERBOSE = 3; + public final static int MSG_WARN = 1; + + /** + * export the array of Packages + * + * @param dest Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + void exportPackages( + File dest, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, + boolean exportResources, boolean exportSources, + boolean useDefaultExcludes, boolean overwrite ); + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + */ + void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ); + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + void loadProjects( Vector projectDescriptions ); + + /** + * Logs a message with the specified log level. + * + * @param msg Description of Parameter + * @param level Description of Parameter + */ + void log( String msg, int level ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java new file mode 100644 index 000000000..9624722af --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.IvjException; +import com.ibm.ivj.util.base.Package; +import com.ibm.ivj.util.base.Project; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; + +/** + * Class for scanning a Visual Age for Java workspace for packages matching a + * certain criteria.

      + * + * These criteria consist of a set of include and exclude patterns. With these + * patterns, you can select which packages you want to have included, and which + * packages you want to have excluded. You can add patterns to be excluded by + * default with the addDefaultExcludes method. The patters that are excluded by + * default include + *

        + *
      • IBM*\**
      • + *
      • Java class libraries\**
      • + *
      • Sun class libraries*\**
      • + *
      • JSP Page Compile Generated Code\**
      • + *
      • VisualAge*\**
      • + *
      + *

      + * + * This class works like DirectoryScanner. + * + * @author Wolf Siberski, TUI Infotec (based on Arnout J. Kuipers + * DirectoryScanner) + * @see org.apache.tools.ant.DirectoryScanner + */ +class VAJWorkspaceScanner extends DirectoryScanner +{ + + // Patterns that should be excluded by default. + private final static String[] DEFAULTEXCLUDES = + { + "IBM*/**", + "Java class libraries/**", + "Sun class libraries*/**", + "JSP Page Compile Generated Code/**", + "VisualAge*/**", + }; + + // The packages that where found and matched at least + // one includes, and matched no excludes. + private Vector packagesIncluded = new Vector(); + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @return true when the string matches against the pattern, + * false otherwise. + */ + protected static boolean match( String pattern, String str ) + { + return DirectoryScanner.match( pattern, str ); + } + + /** + * Get the names of the packages that matched at least one of the include + * patterns, and didn't match one of the exclude patterns. + * + * @return the matching packages + */ + public Package[] getIncludedPackages() + { + int count = packagesIncluded.size(); + Package[] packages = new Package[count]; + for( int i = 0; i < count; i++ ) + { + packages[i] = ( Package )packagesIncluded.elementAt( i ); + } + return packages; + } + + /** + * Adds the array with default exclusions to the current exclusions set. + */ + public void addDefaultExcludes() + { + int excludesLength = excludes == null ? 0 : excludes.length; + String[] newExcludes; + newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length]; + if( excludesLength > 0 ) + { + System.arraycopy( excludes, 0, newExcludes, 0, excludesLength ); + } + for( int i = 0; i < DEFAULTEXCLUDES.length; i++ ) + { + newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i]. + replace( '/', File.separatorChar ). + replace( '\\', File.separatorChar ); + } + excludes = newExcludes; + } + + /** + * Finds all Projects specified in include patterns. + * + * @return the projects + */ + public Vector findMatchingProjects() + { + Project[] projects = VAJLocalUtil.getWorkspace().getProjects(); + + Vector matchingProjects = new Vector(); + + boolean allProjectsMatch = false; + for( int i = 0; i < projects.length; i++ ) + { + Project project = projects[i]; + for( int j = 0; j < includes.length && !allProjectsMatch; j++ ) + { + StringTokenizer tok = + new StringTokenizer( includes[j], File.separator ); + String projectNamePattern = tok.nextToken(); + if( projectNamePattern.equals( "**" ) ) + { + // if an include pattern starts with '**', + // all projects match + allProjectsMatch = true; + } + else + if( match( projectNamePattern, project.getName() ) ) + { + matchingProjects.addElement( project ); + break; + } + } + } + + if( allProjectsMatch ) + { + matchingProjects = new Vector(); + for( int i = 0; i < projects.length; i++ ) + { + matchingProjects.addElement( projects[i] ); + } + } + + return matchingProjects; + } + + /** + * Scans the workspace for packages that match at least one include pattern, + * and don't match any exclude patterns. + */ + public void scan() + { + if( includes == null ) + { + // No includes supplied, so set it to 'matches all' + includes = new String[1]; + includes[0] = "**"; + } + if( excludes == null ) + { + excludes = new String[0]; + } + + // only scan projects which are included in at least one include pattern + Vector matchingProjects = findMatchingProjects(); + for( Enumeration e = matchingProjects.elements(); e.hasMoreElements(); ) + { + Project project = ( Project )e.nextElement(); + scanProject( project ); + } + } + + /** + * Scans a project for packages that match at least one include pattern, and + * don't match any exclude patterns. + * + * @param project Description of Parameter + */ + public void scanProject( Project project ) + { + try + { + Package[] packages = project.getPackages(); + if( packages != null ) + { + for( int i = 0; i < packages.length; i++ ) + { + Package item = packages[i]; + // replace '.' by file seperator because the patterns are + // using file seperator syntax (and we can use the match + // methods this way). + String name = + project.getName() + + File.separator + + item.getName().replace( '.', File.separatorChar ); + if( isIncluded( name ) && !isExcluded( name ) ) + { + packagesIncluded.addElement( item ); + } + } + } + } + catch( IvjException e ) + { + throw VAJLocalUtil.createBuildException( "VA Exception occured: ", e ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/default.ini b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/default.ini new file mode 100644 index 000000000..1ccb8944f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/default.ini @@ -0,0 +1,4 @@ +Name=Ant +Version=0.1 +Help-Item=Ant Help,doc/VAJAntTool.html +Menu-Items=Ant Build,org.apache.tools.ant.taskdefs.optional.ide.VAJAntTool,-P; diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java new file mode 100644 index 000000000..912a18bb9 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.javacc; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/** + * Taskdef for the JJTree compiler compiler. + * + * @author thomas.haas@softwired-inc.com + * @author Michael Saunders michael@amtec.com + * + */ +public class JJTree extends Task +{ + + // keys to optional attributes + private final static String BUILD_NODE_FILES = "BUILD_NODE_FILES"; + private final static String MULTI = "MULTI"; + private final static String NODE_DEFAULT_VOID = "NODE_DEFAULT_VOID"; + private final static String NODE_FACTORY = "NODE_FACTORY"; + private final static String NODE_SCOPE_HOOK = "NODE_SCOPE_HOOK"; + private final static String NODE_USES_PARSER = "NODE_USES_PARSER"; + private final static String STATIC = "STATIC"; + private final static String VISITOR = "VISITOR"; + + private final static String NODE_PACKAGE = "NODE_PACKAGE"; + private final static String VISITOR_EXCEPTION = "VISITOR_EXCEPTION"; + private final static String NODE_PREFIX = "NODE_PREFIX"; + + private final Hashtable optionalAttrs = new Hashtable(); + + // required attributes + private File outputDirectory = null; + private File target = null; + private File javaccHome = null; + + private CommandlineJava cmdl = new CommandlineJava(); + + public JJTree() + { + cmdl.setVm( "java" ); + cmdl.setClassname( "COM.sun.labs.jjtree.Main" ); + } + + + public void setBuildnodefiles( boolean buildNodeFiles ) + { + optionalAttrs.put( BUILD_NODE_FILES, new Boolean( buildNodeFiles ) ); + } + + public void setJavacchome( File javaccHome ) + { + this.javaccHome = javaccHome; + } + + public void setMulti( boolean multi ) + { + optionalAttrs.put( MULTI, new Boolean( multi ) ); + } + + public void setNodedefaultvoid( boolean nodeDefaultVoid ) + { + optionalAttrs.put( NODE_DEFAULT_VOID, new Boolean( nodeDefaultVoid ) ); + } + + public void setNodefactory( boolean nodeFactory ) + { + optionalAttrs.put( NODE_FACTORY, new Boolean( nodeFactory ) ); + } + + public void setNodepackage( String nodePackage ) + { + optionalAttrs.put( NODE_PACKAGE, new String( nodePackage ) ); + } + + public void setNodeprefix( String nodePrefix ) + { + optionalAttrs.put( NODE_PREFIX, new String( nodePrefix ) ); + } + + public void setNodescopehook( boolean nodeScopeHook ) + { + optionalAttrs.put( NODE_SCOPE_HOOK, new Boolean( nodeScopeHook ) ); + } + + public void setNodeusesparser( boolean nodeUsesParser ) + { + optionalAttrs.put( NODE_USES_PARSER, new Boolean( nodeUsesParser ) ); + } + + public void setOutputdirectory( File outputDirectory ) + { + this.outputDirectory = outputDirectory; + } + + public void setStatic( boolean staticParser ) + { + optionalAttrs.put( STATIC, new Boolean( staticParser ) ); + } + + public void setTarget( File target ) + { + this.target = target; + } + + public void setVisitor( boolean visitor ) + { + optionalAttrs.put( VISITOR, new Boolean( visitor ) ); + } + + public void setVisitorException( String visitorException ) + { + optionalAttrs.put( VISITOR_EXCEPTION, new String( visitorException ) ); + } + + public void execute() + throws BuildException + { + + // load command line with optional attributes + Enumeration iter = optionalAttrs.keys(); + while( iter.hasMoreElements() ) + { + String name = ( String )iter.nextElement(); + Object value = optionalAttrs.get( name ); + cmdl.createArgument().setValue( "-" + name + ":" + value.toString() ); + } + + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // use the directory containing the target as the output directory + if( outputDirectory == null ) + { + outputDirectory = new File( target.getParent() ); + } + if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "'outputdirectory' " + outputDirectory + " is not a directory." ); + } + // convert backslashes to slashes, otherwise jjtree will put this as + // comments and this seems to confuse javacc + cmdl.createArgument().setValue( + "-OUTPUT_DIRECTORY:" + outputDirectory.getAbsolutePath().replace( '\\', '/' ) ); + + String targetName = target.getName(); + final File javaFile = new File( outputDirectory, + targetName.substring( 0, targetName.indexOf( ".jjt" ) ) + ".jj" ); + if( javaFile.exists() && target.lastModified() < javaFile.lastModified() ) + { + project.log( "Target is already built - skipping (" + target + ")" ); + return; + } + cmdl.createArgument().setValue( target.getAbsolutePath() ); + + if( javaccHome == null || !javaccHome.isDirectory() ) + { + throw new BuildException( "Javacchome not set." ); + } + final Path classpath = cmdl.createClasspath( project ); + classpath.createPathElement().setPath( javaccHome.getAbsolutePath() + + "/JavaCC.zip" ); + classpath.addJavaRuntime(); + + final Commandline.Argument arg = cmdl.createVmArgument(); + arg.setValue( "-mx140M" ); + arg.setValue( "-Dinstall.root=" + javaccHome.getAbsolutePath() ); + + final Execute process = + new Execute( new LogStreamHandler( this, + Project.MSG_INFO, + Project.MSG_INFO ), + null ); + log( cmdl.toString(), Project.MSG_VERBOSE ); + process.setCommandline( cmdl.getCommandline() ); + + try + { + if( process.execute() != 0 ) + { + throw new BuildException( "JJTree failed." ); + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to launch JJTree: " + e ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java new file mode 100644 index 000000000..f85e9089c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.javacc; +import java.io.File; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/** + * Taskdef for the JavaCC compiler compiler. + * + * @author thomas.haas@softwired-inc.com + * @author Michael Saunders michael@amtec.com + * + */ +public class JavaCC extends Task +{ + + // keys to optional attributes + private final static String LOOKAHEAD = "LOOKAHEAD"; + private final static String CHOICE_AMBIGUITY_CHECK = "CHOICE_AMBIGUITY_CHECK"; + private final static String OTHER_AMBIGUITY_CHECK = "OTHER_AMBIGUITY_CHECK"; + + private final static String STATIC = "STATIC"; + private final static String DEBUG_PARSER = "DEBUG_PARSER"; + private final static String DEBUG_LOOKAHEAD = "DEBUG_LOOKAHEAD"; + private final static String DEBUG_TOKEN_MANAGER = "DEBUG_TOKEN_MANAGER"; + private final static String OPTIMIZE_TOKEN_MANAGER = "OPTIMIZE_TOKEN_MANAGER"; + private final static String ERROR_REPORTING = "ERROR_REPORTING"; + private final static String JAVA_UNICODE_ESCAPE = "JAVA_UNICODE_ESCAPE"; + private final static String UNICODE_INPUT = "UNICODE_INPUT"; + private final static String IGNORE_CASE = "IGNORE_CASE"; + private final static String COMMON_TOKEN_ACTION = "COMMON_TOKEN_ACTION"; + private final static String USER_TOKEN_MANAGER = "USER_TOKEN_MANAGER"; + private final static String USER_CHAR_STREAM = "USER_CHAR_STREAM"; + private final static String BUILD_PARSER = "BUILD_PARSER"; + private final static String BUILD_TOKEN_MANAGER = "BUILD_TOKEN_MANAGER"; + private final static String SANITY_CHECK = "SANITY_CHECK"; + private final static String FORCE_LA_CHECK = "FORCE_LA_CHECK"; + private final static String CACHE_TOKENS = "CACHE_TOKENS"; + + private final Hashtable optionalAttrs = new Hashtable(); + + // required attributes + private File outputDirectory = null; + private File target = null; + private File javaccHome = null; + + private CommandlineJava cmdl = new CommandlineJava(); + + public JavaCC() + { + cmdl.setVm( "java" ); + cmdl.setClassname( "COM.sun.labs.javacc.Main" ); + } + + public void setBuildparser( boolean buildParser ) + { + optionalAttrs.put( BUILD_PARSER, new Boolean( buildParser ) ); + } + + public void setBuildtokenmanager( boolean buildTokenManager ) + { + optionalAttrs.put( BUILD_TOKEN_MANAGER, new Boolean( buildTokenManager ) ); + } + + public void setCachetokens( boolean cacheTokens ) + { + optionalAttrs.put( CACHE_TOKENS, new Boolean( cacheTokens ) ); + } + + public void setChoiceambiguitycheck( int choiceAmbiguityCheck ) + { + optionalAttrs.put( CHOICE_AMBIGUITY_CHECK, new Integer( choiceAmbiguityCheck ) ); + } + + public void setCommontokenaction( boolean commonTokenAction ) + { + optionalAttrs.put( COMMON_TOKEN_ACTION, new Boolean( commonTokenAction ) ); + } + + public void setDebuglookahead( boolean debugLookahead ) + { + optionalAttrs.put( DEBUG_LOOKAHEAD, new Boolean( debugLookahead ) ); + } + + public void setDebugparser( boolean debugParser ) + { + optionalAttrs.put( DEBUG_PARSER, new Boolean( debugParser ) ); + } + + public void setDebugtokenmanager( boolean debugTokenManager ) + { + optionalAttrs.put( DEBUG_TOKEN_MANAGER, new Boolean( debugTokenManager ) ); + } + + public void setErrorreporting( boolean errorReporting ) + { + optionalAttrs.put( ERROR_REPORTING, new Boolean( errorReporting ) ); + } + + public void setForcelacheck( boolean forceLACheck ) + { + optionalAttrs.put( FORCE_LA_CHECK, new Boolean( forceLACheck ) ); + } + + public void setIgnorecase( boolean ignoreCase ) + { + optionalAttrs.put( IGNORE_CASE, new Boolean( ignoreCase ) ); + } + + public void setJavacchome( File javaccHome ) + { + this.javaccHome = javaccHome; + } + + public void setJavaunicodeescape( boolean javaUnicodeEscape ) + { + optionalAttrs.put( JAVA_UNICODE_ESCAPE, new Boolean( javaUnicodeEscape ) ); + } + + + public void setLookahead( int lookahead ) + { + optionalAttrs.put( LOOKAHEAD, new Integer( lookahead ) ); + } + + public void setOptimizetokenmanager( boolean optimizeTokenManager ) + { + optionalAttrs.put( OPTIMIZE_TOKEN_MANAGER, new Boolean( optimizeTokenManager ) ); + } + + public void setOtherambiguityCheck( int otherAmbiguityCheck ) + { + optionalAttrs.put( OTHER_AMBIGUITY_CHECK, new Integer( otherAmbiguityCheck ) ); + } + + public void setOutputdirectory( File outputDirectory ) + { + this.outputDirectory = outputDirectory; + } + + public void setSanitycheck( boolean sanityCheck ) + { + optionalAttrs.put( SANITY_CHECK, new Boolean( sanityCheck ) ); + } + + public void setStatic( boolean staticParser ) + { + optionalAttrs.put( STATIC, new Boolean( staticParser ) ); + } + + public void setTarget( File target ) + { + this.target = target; + } + + public void setUnicodeinput( boolean unicodeInput ) + { + optionalAttrs.put( UNICODE_INPUT, new Boolean( unicodeInput ) ); + } + + public void setUsercharstream( boolean userCharStream ) + { + optionalAttrs.put( USER_CHAR_STREAM, new Boolean( userCharStream ) ); + } + + public void setUsertokenmanager( boolean userTokenManager ) + { + optionalAttrs.put( USER_TOKEN_MANAGER, new Boolean( userTokenManager ) ); + } + + public void execute() + throws BuildException + { + + // load command line with optional attributes + Enumeration iter = optionalAttrs.keys(); + while( iter.hasMoreElements() ) + { + String name = ( String )iter.nextElement(); + Object value = optionalAttrs.get( name ); + cmdl.createArgument().setValue( "-" + name + ":" + value.toString() ); + } + + // check the target is a file + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // use the directory containing the target as the output directory + if( outputDirectory == null ) + { + outputDirectory = new File( target.getParent() ); + } + else if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "Outputdir not a directory." ); + } + cmdl.createArgument().setValue( + "-OUTPUT_DIRECTORY:" + outputDirectory.getAbsolutePath() ); + + // determine if the generated java file is up-to-date + final File javaFile = getOutputJavaFile( outputDirectory, target ); + if( javaFile.exists() && target.lastModified() < javaFile.lastModified() ) + { + log( "Target is already built - skipping (" + target + ")", Project.MSG_VERBOSE ); + return; + } + cmdl.createArgument().setValue( target.getAbsolutePath() ); + + if( javaccHome == null || !javaccHome.isDirectory() ) + { + throw new BuildException( "Javacchome not set." ); + } + final Path classpath = cmdl.createClasspath( project ); + classpath.createPathElement().setPath( javaccHome.getAbsolutePath() + + "/JavaCC.zip" ); + classpath.addJavaRuntime(); + + final Commandline.Argument arg = cmdl.createVmArgument(); + arg.setValue( "-mx140M" ); + arg.setValue( "-Dinstall.root=" + javaccHome.getAbsolutePath() ); + + Execute.runCommand( this, cmdl.getCommandline() ); + } + + /** + * Determines the output Java file to be generated by the given grammar + * file. + * + * @param outputdir Description of Parameter + * @param srcfile Description of Parameter + * @return The OutputJavaFile value + */ + private File getOutputJavaFile( File outputdir, File srcfile ) + { + String path = srcfile.getPath(); + + // Extract file's base-name + int startBasename = path.lastIndexOf( File.separator ); + if( startBasename != -1 ) + { + path = path.substring( startBasename + 1 ); + } + + // Replace the file's extension with '.java' + int startExtn = path.lastIndexOf( '.' ); + if( startExtn != -1 ) + { + path = path.substring( 0, startExtn ) + ".java"; + } + else + { + path += ".java"; + } + + // Change the directory + if( outputdir != null ) + { + path = outputdir + File.separator + path; + } + + return new File( path ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java new file mode 100644 index 000000000..c8e6da765 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java @@ -0,0 +1,453 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jdepend; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.PathTokenizer; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteWatchdog; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Ant task to run JDepend tests.

      + * + * JDepend is a tool to generate design quality metrics for each Java package. + * It has been initially created by Mike Clark. JDepend can be found at + * http://www.clarkware.com/software/JDepend.html . The current + * implementation spawn a new Java VM. + * + * @author Jerome Lacoste + * @author Rob Oxspring + */ +public class JDependTask extends Task +{ + + /** + * No problems with this test. + */ + private final static int SUCCESS = 0; + /** + * An error occured. + */ + private final static int ERRORS = 1; + private boolean _haltonerror = false; + private boolean _fork = false; + //private Integer _timeout = null; + + private String _jvm = null; + private String format = "text"; + private Path _compileClasspath; + private File _dir; + + // optional attributes + private File _outputFile; + //private CommandlineJava commandline = new CommandlineJava(); + + // required attributes + private Path _sourcesPath; + + public JDependTask() { } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( _compileClasspath == null ) + { + _compileClasspath = classpath; + } + else + { + _compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * The directory to invoke the VM in. Ignored if no JVM is forked. + * + * @param dir the directory to invoke the JVM from. + * @see #setFork(boolean) + */ + public void setDir( File dir ) + { + _dir = dir; + } + + /** + * Tells whether a JVM should be forked for the task. Default: false. + * + * @param value true if a JVM should be forked, otherwise false + * + */ + public void setFork( boolean value ) + { + _fork = value; + } + + + public void setFormat( FormatAttribute ea ) + { + format = ea.getValue(); + } + + /** + * Halt on Failure? default: false. + * + * @param value The new Haltonerror value + */ + public void setHaltonerror( boolean value ) + { + _haltonerror = value; + } + + /** + * Set a new VM to execute the task. Default is java . Ignored if + * no JVM is forked. + * + * @param value the new VM to use instead of java + * @see #setFork(boolean) + */ + public void setJvm( String value ) + { + _jvm = value; + + } + + /* + * public void setTimeout(Integer value) { + * _timeout = value; + * } + * public Integer getTimeout() { + * return _timeout; + * } + */ + public void setOutputFile( File outputFile ) + { + _outputFile = outputFile; + } + + /** + * Gets the classpath to be used for this compilation. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return _compileClasspath; + } + + public File getDir() + { + return _dir; + } + + public boolean getFork() + { + return _fork; + } + + public boolean getHaltonerror() + { + return _haltonerror; + } + + public File getOutputFile() + { + return _outputFile; + } + + /** + * Gets the sourcepath. + * + * @return The Sourcespath value + */ + public Path getSourcespath() + { + return _sourcesPath; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( _compileClasspath == null ) + { + _compileClasspath = new Path( project ); + } + return _compileClasspath.createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @param commandline Description of Parameter + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg( CommandlineJava commandline ) + { + return commandline.createVmArgument(); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createSourcespath() + { + if( _sourcesPath == null ) + { + _sourcesPath = new Path( project ); + } + return _sourcesPath.createPath(); + } + + public void execute() + throws BuildException + { + + CommandlineJava commandline = new CommandlineJava(); + + if( "text".equals( format ) ) + commandline.setClassname( "jdepend.textui.JDepend" ); + else + if( "xml".equals( format ) ) + commandline.setClassname( "jdepend.xmlui.JDepend" ); + + if( _jvm != null ) + commandline.setVm( _jvm ); + + if( getSourcespath() == null ) + throw new BuildException( "Missing Sourcepath required argument" ); + + // execute the test and get the return code + int exitValue = JDependTask.ERRORS; + boolean wasKilled = false; + if( !getFork() ) + { + exitValue = executeInVM( commandline ); + } + else + { + ExecuteWatchdog watchdog = createWatchdog(); + exitValue = executeAsForked( commandline, watchdog ); + // null watchdog means no timeout, you'd better not check with null + if( watchdog != null ) + { + //info will be used in later version do nothing for now + //wasKilled = watchdog.killedProcess(); + } + } + + // if there is an error/failure and that it should halt, stop everything otherwise + // just log a statement + boolean errorOccurred = exitValue == JDependTask.ERRORS; + + if( errorOccurred ) + { + if( getHaltonerror() ) + throw new BuildException( "JDepend failed", + location ); + else + log( "JDepend FAILED", Project.MSG_ERR ); + } + } + + + /** + * Execute the task by forking a new JVM. The command will block until it + * finishes. To know if the process was destroyed or not, use the + * killedProcess() method of the watchdog class. + * + * @param watchdog the watchdog in charge of cancelling the test if it + * exceeds a certain amount of time. Can be null , in this + * case the test could probably hang forever. + * @param commandline Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + // JL: comment extracted from JUnitTask (and slightly modified) + public int executeAsForked( CommandlineJava commandline, ExecuteWatchdog watchdog ) + throws BuildException + { + // if not set, auto-create the ClassPath from the project + createClasspath(); + + // not sure whether this test is needed but cost nothing to put. + // hope it will be reviewed by anybody competent + if( getClasspath().toString().length() > 0 ) + { + createJvmarg( commandline ).setValue( "-classpath" ); + createJvmarg( commandline ).setValue( getClasspath().toString() ); + } + + if( getOutputFile() != null ) + { + // having a space between the file and its path causes commandline to add quotes " + // around the argument thus making JDepend not taking it into account. Thus we split it in two + commandline.createArgument().setValue( "-file" ); + commandline.createArgument().setValue( _outputFile.getPath() ); + // we have to find a cleaner way to put this output + } + + PathTokenizer sourcesPath = new PathTokenizer( getSourcespath().toString() ); + while( sourcesPath.hasMoreTokens() ) + { + File f = new File( sourcesPath.nextToken() ); + + // not necessary as JDepend would fail, but why loose some time? + if( !f.exists() || !f.isDirectory() ) + throw new BuildException( "\"" + f.getPath() + "\" does not represent a valid directory. JDepend would fail." ); + commandline.createArgument().setValue( f.getPath() ); + } + + Execute execute = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ), watchdog ); + execute.setCommandline( commandline.getCommandline() ); + if( getDir() != null ) + { + execute.setWorkingDirectory( getDir() ); + execute.setAntRun( project ); + } + + if( getOutputFile() != null ) + log( "Output to be stored in " + getOutputFile().getPath() ); + log( "Executing: " + commandline.toString(), Project.MSG_VERBOSE ); + try + { + return execute.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Process fork failed.", e, location ); + } + } + + + // this comment extract from JUnit Task may also apply here + // "in VM is not very nice since it could probably hang the + // whole build. IMHO this method should be avoided and it would be best + // to remove it in future versions. TBD. (SBa)" + + /** + * Execute inside VM. + * + * @param commandline Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public int executeInVM( CommandlineJava commandline ) + throws BuildException + { + jdepend.textui.JDepend jdepend; + + if( "xml".equals( format ) ) + jdepend = new jdepend.xmlui.JDepend(); + else + jdepend = new jdepend.textui.JDepend(); + + if( getOutputFile() != null ) + { + FileWriter fw; + try + { + fw = new FileWriter( getOutputFile().getPath() ); + } + catch( IOException e ) + { + String msg = "JDepend Failed when creating the output file: " + e.getMessage(); + log( msg ); + throw new BuildException( msg ); + } + jdepend.setWriter( new PrintWriter( fw ) ); + log( "Output to be stored in " + getOutputFile().getPath() ); + } + + PathTokenizer sourcesPath = new PathTokenizer( getSourcespath().toString() ); + while( sourcesPath.hasMoreTokens() ) + { + File f = new File( sourcesPath.nextToken() ); + + // not necessary as JDepend would fail, but why loose some time? + if( !f.exists() || !f.isDirectory() ) + { + String msg = "\"" + f.getPath() + "\" does not represent a valid directory. JDepend would fail."; + log( msg ); + throw new BuildException( msg ); + } + try + { + jdepend.addDirectory( f.getPath() ); + } + catch( IOException e ) + { + String msg = "JDepend Failed when adding a source directory: " + e.getMessage(); + log( msg ); + throw new BuildException( msg ); + } + } + jdepend.analyze(); + return SUCCESS; + } + + /** + * @return null if there is a timeout value, otherwise the watchdog + * instance. + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + + return null; + /* + * if (getTimeout() == null){ + * return null; + * } + * return new ExecuteWatchdog(getTimeout().intValue()); + */ + } + + public static class FormatAttribute extends EnumeratedAttribute + { + private String[] formats = new String[]{"xml", "text"}; + + public String[] getValues() + { + return formats; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java new file mode 100644 index 000000000..b1a089810 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Provides a quick and dirty way to determine the true name of a class given + * just an InputStream. Reads in just enough to perform this minimal task only. + * + * @author RT + */ +public class ClassNameReader extends Object +{ + + public static String getClassName( InputStream input ) + throws IOException + { + DataInputStream data = new DataInputStream( input ); + // verify this is a valid class file. + int cookie = data.readInt(); + if( cookie != 0xCAFEBABE ) + { + return null; + } + int version = data.readInt(); + // read the constant pool. + ConstantPool constants = new ConstantPool( data ); + Object[] values = constants.values; + // read access flags and class index. + int accessFlags = data.readUnsignedShort(); + int classIndex = data.readUnsignedShort(); + Integer stringIndex = ( Integer )values[classIndex]; + String className = ( String )values[stringIndex.intValue()]; + return className; + } + +} + +/** + * Reads just enough of a class file to determine the class' full name.

      + * + * Extremely minimal constant pool implementation, mainly to support extracting + * strings from a class file. + * + * @author Patrick C. Beard . + */ +class ConstantPool extends Object +{ + + + final static byte UTF8 = 1, UNUSED = 2, INTEGER = 3, FLOAT = 4, LONG = 5, DOUBLE = 6, + CLASS = 7, STRING = 8, FIELDREF = 9, METHODREF = 10, + INTERFACEMETHODREF = 11, NAMEANDTYPE = 12; + + byte[] types; + + Object[] values; + + ConstantPool( DataInput data ) + throws IOException + { + super(); + + int count = data.readUnsignedShort(); + types = new byte[count]; + values = new Object[count]; + // read in all constant pool entries. + for( int i = 1; i < count; i++ ) + { + byte type = data.readByte(); + types[i] = type; + switch ( type ) + { + case UTF8: + values[i] = data.readUTF(); + break; + case UNUSED: + break; + case INTEGER: + values[i] = new Integer( data.readInt() ); + break; + case FLOAT: + values[i] = new Float( data.readFloat() ); + break; + case LONG: + values[i] = new Long( data.readLong() ); + ++i; + break; + case DOUBLE: + values[i] = new Double( data.readDouble() ); + ++i; + break; + case CLASS: + case STRING: + values[i] = new Integer( data.readUnsignedShort() ); + break; + case FIELDREF: + case METHODREF: + case INTERFACEMETHODREF: + case NAMEANDTYPE: + values[i] = new Integer( data.readInt() ); + break; + } + } + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java new file mode 100644 index 000000000..94d642f0c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; + +/** + * This class defines objects that can link together various jar and zip files. + *

      + * + * It is basically a wrapper for the jlink code written originally by Patrick Beard . The classes + * org.apache.tools.ant.taskdefs.optional.jlink.Jlink and + * org.apache.tools.ant.taskdefs.optional.jlink.ClassNameReader support this + * class.

      + * + * For example: + *

      + * <jlink compress="false" outfile="out.jar"/>
      + *   <mergefiles>
      + *     <pathelement path="${build.dir}/mergefoo.jar"/>
      + *     <pathelement path="${build.dir}/mergebar.jar"/>
      + *   </mergefiles>
      + *   <addfiles>
      + *     <pathelement path="${build.dir}/mac.jar"/>
      + *     <pathelement path="${build.dir}/pc.zip"/>
      + *   </addfiles>
      + * </jlink>
      + * 
      + * + * @author Matthew Kuperus Heun + * + */ +public class JlinkTask extends MatchingTask +{ + + private File outfile = null; + + private Path mergefiles = null; + + private Path addfiles = null; + + private boolean compress = false; + + private String ps = System.getProperty( "path.separator" ); + + /** + * Sets the files to be added into the output. + * + * @param addfiles The new Addfiles value + */ + public void setAddfiles( Path addfiles ) + { + if( this.addfiles == null ) + { + this.addfiles = addfiles; + } + else + { + this.addfiles.append( addfiles ); + } + } + + /** + * Defines whether or not the output should be compacted. + * + * @param compress The new Compress value + */ + public void setCompress( boolean compress ) + { + this.compress = compress; + } + + /** + * Sets the files to be merged into the output. + * + * @param mergefiles The new Mergefiles value + */ + public void setMergefiles( Path mergefiles ) + { + if( this.mergefiles == null ) + { + this.mergefiles = mergefiles; + } + else + { + this.mergefiles.append( mergefiles ); + } + } + + /** + * The output file for this run of jlink. Usually a jar or zip file. + * + * @param outfile The new Outfile value + */ + public void setOutfile( File outfile ) + { + this.outfile = outfile; + } + + /** + * Establishes the object that contains the files to be added to the output. + * + * @return Description of the Returned Value + */ + public Path createAddfiles() + { + if( this.addfiles == null ) + { + this.addfiles = new Path( getProject() ); + } + return this.addfiles.createPath(); + } + + /** + * Establishes the object that contains the files to be merged into the + * output. + * + * @return Description of the Returned Value + */ + public Path createMergefiles() + { + if( this.mergefiles == null ) + { + this.mergefiles = new Path( getProject() ); + } + return this.mergefiles.createPath(); + } + + /** + * Does the adding and merging. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + //Be sure everything has been set. + if( outfile == null ) + { + throw new BuildException( "outfile attribute is required! Please set." ); + } + if( !haveAddFiles() && !haveMergeFiles() ) + { + throw new BuildException( "addfiles or mergefiles required! Please set." ); + } + log( "linking: " + outfile.getPath() ); + log( "compression: " + compress, Project.MSG_VERBOSE ); + jlink linker = new jlink(); + linker.setOutfile( outfile.getPath() ); + linker.setCompression( compress ); + if( haveMergeFiles() ) + { + log( "merge files: " + mergefiles.toString(), Project.MSG_VERBOSE ); + linker.addMergeFiles( mergefiles.list() ); + } + if( haveAddFiles() ) + { + log( "add files: " + addfiles.toString(), Project.MSG_VERBOSE ); + linker.addAddFiles( addfiles.list() ); + } + try + { + linker.link(); + } + catch( Exception ex ) + { + throw new BuildException( ex); + } + } + + private boolean haveAddFiles() + { + return haveEntries( addfiles ); + } + + private boolean haveEntries( Path p ) + { + if( p == null ) + { + return false; + } + if( p.size() > 0 ) + { + return true; + } + return false; + } + + private boolean haveMergeFiles() + { + return haveEntries( mergefiles ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java new file mode 100644 index 000000000..cb9a3fbe8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java @@ -0,0 +1,464 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class jlink extends Object +{ + + private String outfile = null; + + private Vector mergefiles = new Vector( 10 ); + + private Vector addfiles = new Vector( 10 ); + + private boolean compression = false; + + byte[] buffer = new byte[8192]; + + public static void main( String[] args ) + { + // jlink output input1 ... inputN + if( args.length < 2 ) + { + System.out.println( "usage: jlink output input1 ... inputN" ); + System.exit( 1 ); + } + jlink linker = new jlink(); + linker.setOutfile( args[0] ); + //To maintain compatibility with the command-line version, we will only add files to be merged. + for( int i = 1; i < args.length; i++ ) + { + linker.addMergeFile( args[i] ); + } + try + { + linker.link(); + } + catch( Exception ex ) + { + System.err.print( ex.getMessage() ); + } + } + + /** + * Determines whether output will be compressed. + * + * @param compress The new Compression value + */ + public void setCompression( boolean compress ) + { + this.compression = compress; + } + + /** + * The file that will be created by this instance of jlink. + * + * @param outfile The new Outfile value + */ + public void setOutfile( String outfile ) + { + if( outfile == null ) + { + return; + } + this.outfile = outfile; + } + + /** + * Adds a file to be added into the output. + * + * @param addfile The feature to be added to the AddFile attribute + */ + public void addAddFile( String addfile ) + { + if( addfile == null ) + { + return; + } + addfiles.addElement( addfile ); + } + + /** + * Adds several file to be added into the output. + * + * @param addfiles The feature to be added to the AddFiles attribute + */ + public void addAddFiles( String[] addfiles ) + { + if( addfiles == null ) + { + return; + } + for( int i = 0; i < addfiles.length; i++ ) + { + addAddFile( addfiles[i] ); + } + } + + /** + * Adds a file to be merged into the output. + * + * @param mergefile The feature to be added to the MergeFile attribute + */ + public void addMergeFile( String mergefile ) + { + if( mergefile == null ) + { + return; + } + mergefiles.addElement( mergefile ); + } + + /** + * Adds several files to be merged into the output. + * + * @param mergefiles The feature to be added to the MergeFiles attribute + */ + public void addMergeFiles( String[] mergefiles ) + { + if( mergefiles == null ) + { + return; + } + for( int i = 0; i < mergefiles.length; i++ ) + { + addMergeFile( mergefiles[i] ); + } + } + + /** + * Performs the linking of files. Addfiles are added to the output as-is. + * For example, a jar file is added to the output as a jar file. However, + * mergefiles are first examined for their type. If it is a jar or zip file, + * the contents will be extracted from the mergefile and entered into the + * output. If a zip or jar file is encountered in a subdirectory it will be + * added, not merged. If a directory is encountered, it becomes the root + * entry of all the files below it. Thus, you can provide multiple, disjoint + * directories, as addfiles: they will all be added in a rational manner to + * outfile. + * + * @exception Exception Description of Exception + */ + public void link() + throws Exception + { + ZipOutputStream output = new ZipOutputStream( new FileOutputStream( outfile ) ); + if( compression ) + { + output.setMethod( ZipOutputStream.DEFLATED ); + output.setLevel( Deflater.DEFAULT_COMPRESSION ); + } + else + { + output.setMethod( ZipOutputStream.STORED ); + } + Enumeration merges = mergefiles.elements(); + while( merges.hasMoreElements() ) + { + String path = ( String )merges.nextElement(); + File f = new File( path ); + if( f.getName().endsWith( ".jar" ) || f.getName().endsWith( ".zip" ) ) + { + //Do the merge + mergeZipJarContents( output, f ); + } + else + { + //Add this file to the addfiles Vector and add it + //later at the top level of the output file. + addAddFile( path ); + } + } + Enumeration adds = addfiles.elements(); + while( adds.hasMoreElements() ) + { + String name = ( String )adds.nextElement(); + File f = new File( name ); + if( f.isDirectory() ) + { + //System.out.println("in jlink: adding directory contents of " + f.getPath()); + addDirContents( output, f, f.getName() + '/', compression ); + } + else + { + addFile( output, f, "", compression ); + } + } + if( output != null ) + { + try + { + output.close(); + } + catch( IOException ioe ) + {} + } + } + + /* + * Gets the name of an entry in the file. This is the real name + * which for a class is the name of the package with the class + * name appended. + */ + private String getEntryName( File file, String prefix ) + { + String name = file.getName(); + if( !name.endsWith( ".class" ) ) + { + // see if the file is in fact a .class file, and determine its actual name. + try + { + InputStream input = new FileInputStream( file ); + String className = ClassNameReader.getClassName( input ); + input.close(); + if( className != null ) + { + return className.replace( '.', '/' ) + ".class"; + } + } + catch( IOException ioe ) + {} + } + System.out.println( "From " + file.getPath() + " and prefix " + prefix + ", creating entry " + prefix + name ); + return ( prefix + name ); + } + + /* + * Adds contents of a directory to the output. + */ + private void addDirContents( ZipOutputStream output, File dir, String prefix, boolean compress ) + throws IOException + { + String[] contents = dir.list(); + for( int i = 0; i < contents.length; ++i ) + { + String name = contents[i]; + File file = new File( dir, name ); + if( file.isDirectory() ) + { + addDirContents( output, file, prefix + name + '/', compress ); + } + else + { + addFile( output, file, prefix, compress ); + } + } + } + + /* + * Adds a file to the output stream. + */ + private void addFile( ZipOutputStream output, File file, String prefix, boolean compress ) + throws IOException + { + //Make sure file exists + long checksum = 0; + if( !file.exists() ) + { + return; + } + ZipEntry entry = new ZipEntry( getEntryName( file, prefix ) ); + entry.setTime( file.lastModified() ); + entry.setSize( file.length() ); + if( !compress ) + { + entry.setCrc( calcChecksum( file ) ); + } + FileInputStream input = new FileInputStream( file ); + addToOutputStream( output, input, entry ); + } + + /* + * A convenience method that several other methods might call. + */ + private void addToOutputStream( ZipOutputStream output, InputStream input, ZipEntry ze ) + throws IOException + { + try + { + output.putNextEntry( ze ); + } + catch( ZipException zipEx ) + { + //This entry already exists. So, go with the first one. + input.close(); + return; + } + int numBytes = -1; + while( ( numBytes = input.read( buffer ) ) > 0 ) + { + output.write( buffer, 0, numBytes ); + } + output.closeEntry(); + input.close(); + } + + /* + * Necessary in the case where you add a entry that + * is not compressed. + */ + private long calcChecksum( File f ) + throws IOException + { + BufferedInputStream in = new BufferedInputStream( new FileInputStream( f ) ); + return calcChecksum( in, f.length() ); + } + + /* + * Necessary in the case where you add a entry that + * is not compressed. + */ + private long calcChecksum( InputStream in, long size ) + throws IOException + { + CRC32 crc = new CRC32(); + int len = buffer.length; + int count = -1; + int haveRead = 0; + while( ( count = in.read( buffer, 0, len ) ) > 0 ) + { + haveRead += count; + crc.update( buffer, 0, count ); + } + in.close(); + return crc.getValue(); + } + + /* + * Actually performs the merging of f into the output. + * f should be a zip or jar file. + */ + private void mergeZipJarContents( ZipOutputStream output, File f ) + throws IOException + { + //Check to see that the file with name "name" exists. + if( !f.exists() ) + { + return; + } + ZipFile zipf = new ZipFile( f ); + Enumeration entries = zipf.entries(); + while( entries.hasMoreElements() ) + { + ZipEntry inputEntry = ( ZipEntry )entries.nextElement(); + //Ignore manifest entries. They're bound to cause conflicts between + //files that are being merged. User should supply their own + //manifest file when doing the merge. + String inputEntryName = inputEntry.getName(); + int index = inputEntryName.indexOf( "META-INF" ); + if( index < 0 ) + { + //META-INF not found in the name of the entry. Go ahead and process it. + try + { + output.putNextEntry( processEntry( zipf, inputEntry ) ); + } + catch( ZipException ex ) + { + //If we get here, it could be because we are trying to put a + //directory entry that already exists. + //For example, we're trying to write "com", but a previous + //entry from another mergefile was called "com". + //In that case, just ignore the error and go on to the + //next entry. + String mess = ex.getMessage(); + if( mess.indexOf( "duplicate" ) >= 0 ) + { + //It was the duplicate entry. + continue; + } + else + { + //I hate to admit it, but we don't know what happened here. Throw the Exception. + throw ex; + } + } + InputStream in = zipf.getInputStream( inputEntry ); + int len = buffer.length; + int count = -1; + while( ( count = in.read( buffer, 0, len ) ) > 0 ) + { + output.write( buffer, 0, count ); + } + in.close(); + output.closeEntry(); + } + } + zipf.close(); + } + + /* + * A method that does the work on a given entry in a mergefile. + * The big deal is to set the right parameters in the ZipEntry + * on the output stream. + */ + private ZipEntry processEntry( ZipFile zip, ZipEntry inputEntry ) + throws IOException + { + /* + * First, some notes. + * On MRJ 2.2.2, getting the size, compressed size, and CRC32 from the + * ZipInputStream does not work for compressed (deflated) files. Those calls return -1. + * For uncompressed (stored) files, those calls do work. + * However, using ZipFile.getEntries() works for both compressed and + * uncompressed files. + * Now, from some simple testing I did, it seems that the value of CRC-32 is + * independent of the compression setting. So, it should be easy to pass this + * information on to the output entry. + */ + String name = inputEntry.getName(); + if( !( inputEntry.isDirectory() || name.endsWith( ".class" ) ) ) + { + try + { + InputStream input = zip.getInputStream( zip.getEntry( name ) ); + String className = ClassNameReader.getClassName( input ); + input.close(); + if( className != null ) + { + name = className.replace( '.', '/' ) + ".class"; + } + } + catch( IOException ioe ) + {} + } + ZipEntry outputEntry = new ZipEntry( name ); + outputEntry.setTime( inputEntry.getTime() ); + outputEntry.setExtra( inputEntry.getExtra() ); + outputEntry.setComment( inputEntry.getComment() ); + outputEntry.setTime( inputEntry.getTime() ); + if( compression ) + { + outputEntry.setMethod( ZipEntry.DEFLATED ); + //Note, don't need to specify size or crc for compressed files. + } + else + { + outputEntry.setMethod( ZipEntry.STORED ); + outputEntry.setCrc( inputEntry.getCrc() ); + outputEntry.setSize( inputEntry.getSize() ); + } + return outputEntry; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java new file mode 100644 index 000000000..2d91db4ca --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java @@ -0,0 +1,491 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp; +import java.io.File; +import java.util.Date; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.optional.jsp.compilers.CompilerAdapter; +import org.apache.tools.ant.taskdefs.optional.jsp.compilers.CompilerAdapterFactory; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Ant task to run the jsp compiler.

      + * + * This task takes the given jsp files and compiles them into java files. It is + * then up to the user to compile the java files into classes.

      + * + * The task requires the srcdir and destdir attributes to be set. This Task is a + * MatchingTask, so the files to be compiled can be specified using + * includes/excludes attributes or nested include/exclude elements. Optional + * attributes are verbose (set the verbosity level passed to jasper), package + * (name of the destination package for generated java classes and classpath + * (the classpath to use when running the jsp compiler).

      + * + * This task supports the nested elements classpath (A Path) and classpathref (A + * Reference) which can be used in preference to the attribute classpath, if the + * jsp compiler is not already in the ant classpath.

      + * + *

      Notes

      + * + * At present, this task only supports the jasper compiler. In future, other + * compilers will be supported by setting the jsp.compiler property.

      + * + *

      Usage

      + * <jspc srcdir="${basedir}/src/war"
      + *       destdir="${basedir}/gensrc"
      + *       package="com.i3sp.jsp"
      + *       verbose="9">
      + *   <include name="**\/*.jsp" />
      + * </jspc>
      + * 
      + * + * @author Matthew Watson

      + * + * Large Amount of cutting and pasting from the Javac task... + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + * @version $Revision$ $Date$ + */ +public class JspC extends MatchingTask +{ + + private final static String FAIL_MSG + = "Compile failed, messages should have been provided."; + private int verbose = 0; + protected Vector compileList = new Vector(); + protected boolean failOnError = true; + /* + * ------------------------------------------------------------ + */ + private Path classpath; + private File destDir; + private String iepluginid; + private boolean mapped; + private String packageName; + private Path src; + + /** + * -uribase

      The uri directory compilations should be relative to + * (Default is "/") + */ + + private File uribase; + + /** + * -uriroot The root directory that uri files should be resolved + * against, + */ + private File uriroot; + + + /* + * ------------------------------------------------------------ + */ + /** + * Set the classpath to be used for this compilation + * + * @param cp The new Classpath value + */ + public void setClasspath( Path cp ) + { + if( classpath == null ) + classpath = cp; + else + classpath.append( cp ); + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the destination directory into which the JSP source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Throw a BuildException if compilation fails + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Set the ieplugin id + * + * @param iepluginid_ The new Ieplugin value + */ + public void setIeplugin( String iepluginid_ ) + { + iepluginid = iepluginid_; + } + + /** + * set the mapped flag + * + * @param mapped_ The new Mapped value + */ + public void setMapped( boolean mapped_ ) + { + mapped = mapped_; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the name of the package the compiled jsp files should be in + * + * @param pkg The new Package value + */ + public void setPackage( String pkg ) + { + this.packageName = pkg; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the source dirs to find the source JSP files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( Path srcDir ) + { + if( src == null ) + { + src = srcDir; + } + else + { + src.append( srcDir ); + } + } + + /** + * -uribase. the uri context of relative URI references in the JSP pages. If + * it does not exist then it is derived from the location of the file + * relative to the declared or derived value of -uriroot. + * + * @param uribase The new Uribase value + */ + public void setUribase( File uribase ) + { + this.uribase = uribase; + } + + /** + * -uriroot The root directory that uri files should be resolved + * against, (Default is the directory jspc is invoked from) + * + * @param uriroot The new Uribase value + */ + public void setUriroot( File uriroot ) + { + this.uriroot = uriroot; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the verbose level of the compiler + * + * @param i The new Verbose value + */ + public void setVerbose( int i ) + { + verbose = i; + } + + public Path getClasspath() + { + return classpath; + } + + /* + * ------------------------------------------------------------ + */ + public Vector getCompileList() + { + return compileList; + } + + public File getDestdir() + { + return destDir; + } + + /** + * Gets the failonerror flag. + * + * @return The Failonerror value + */ + public boolean getFailonerror() + { + return failOnError; + } + + /* + * ------------------------------------------------------------ + */ + public String getIeplugin() + { + return iepluginid; + } + + public String getPackage() + { + return packageName; + } + + public Path getSrcDir() + { + return src; + } + + public File getUribase() + { + return uriroot; + } + + public File getUriroot() + { + return uriroot; + } + + public int getVerbose() + { + return verbose; + } + + /* + * ------------------------------------------------------------ + */ + public boolean isMapped() + { + return mapped; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + classpath = new Path( project ); + return classpath.createPath(); + } + + /* + * ------------------------------------------------------------ + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + if( src == null ) + { + throw new BuildException( "srcdir attribute must be set!", + location ); + } + String[] list = src.list(); + if( list.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", + location ); + } + + if( destDir != null && !destDir.isDirectory() ) + { + throw new + BuildException( "destination directory \"" + destDir + + "\" does not exist or is not a directory", + location ); + } + + // calculate where the files will end up: + File dest = null; + if( packageName == null ) + dest = destDir; + else + { + String path = destDir.getPath() + File.separatorChar + + packageName.replace( '.', File.separatorChar ); + dest = new File( path ); + } + + // scan source directories and dest directory to build up both copy + // lists and compile lists + resetFileLists(); + int filecount = 0; + for( int i = 0; i < list.length; i++ ) + { + File srcDir = ( File )project.resolveFile( list[i] ); + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir \"" + srcDir.getPath() + + "\" does not exist!", location ); + } + + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + filecount = files.length; + scanDir( srcDir, dest, files ); + } + + // compile the source files + + String compiler = project.getProperty( "jsp.compiler" ); + if( compiler == null ) + { + compiler = "jasper"; + } + log( "compiling " + compileList.size() + " files", Project.MSG_VERBOSE ); + + if( compileList.size() > 0 ) + { + + CompilerAdapter adapter = + CompilerAdapterFactory.getCompiler( compiler, this ); + log( "Compiling " + compileList.size() + + " source file" + + ( compileList.size() == 1 ? "" : "s" ) + + ( destDir != null ? " to " + destDir : "" ) ); + + // now we need to populate the compiler adapter + adapter.setJspc( this ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + if( failOnError ) + { + throw new BuildException( FAIL_MSG, location ); + } + else + { + log( FAIL_MSG, Project.MSG_ERR ); + } + } + } + else + { + if( filecount == 0 ) + { + log( "there were no files to compile", Project.MSG_INFO ); + } + else + { + log( "all files are up to date", Project.MSG_VERBOSE ); + } + } + } + + /* + * ------------------------------------------------------------ + */ + /** + * Clear the list of files to be compiled and copied.. + */ + protected void resetFileLists() + { + compileList.removeAllElements(); + } + + /* + * ------------------------------------------------------------ + */ + /** + * Scans the directory looking for source files to be compiled. The results + * are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, File destDir, String files[] ) + { + + long now = ( new Date() ).getTime(); + + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".jsp" ) ) + { + // drop leading path (if any) + int fileStart = + files[i].lastIndexOf( File.separatorChar ) + 1; + File javaFile = new File( destDir, files[i].substring( fileStart, + files[i].indexOf( ".jsp" ) ) + ".java" ); + + if( srcFile.lastModified() > now ) + { + log( "Warning: file modified in the future: " + + files[i], Project.MSG_WARN ); + } + + if( !javaFile.exists() || + srcFile.lastModified() > javaFile.lastModified() ) + { + if( !javaFile.exists() ) + { + log( "Compiling " + srcFile.getPath() + + " because java file " + + javaFile.getPath() + " does not exist", + Project.MSG_DEBUG ); + } + else + { + log( "Compiling " + srcFile.getPath() + + " because it is out of date with respect to " + + javaFile.getPath(), Project.MSG_DEBUG ); + } + compileList.addElement( srcFile.getAbsolutePath() ); + } + } + } + } + /* + * ------------------------------------------------------------ + */ +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java new file mode 100644 index 000000000..20d7a9176 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp;//java imports +import java.io.File; +import java.util.Date; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java;//apache/ant imports +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; + +/** + * Class to precompile JSP's using weblogic's jsp compiler (weblogic.jspc) + * + * @author Avik Sengupta + * http://www.webteksoftware.com Tested only on Weblogic 4.5.1 - NT4.0 and + * Solaris 5.7 required attributes src : root of source tree for JSP, ie, + * the document root for your weblogic server dest : root of destination + * directory, what you have set as WorkingDir in the weblogic properties + * package : start package name under which your JSP's would be compiled + * other attributes classpath A classpath should be set which contains the + * weblogic classes as well as all application classes referenced by the + * JSP. The system classpath is also appended when the jspc is called, so + * you may choose to put everything in the classpath while calling Ant. + * However, since presumably the JSP's will reference classes being build + * by Ant, it would be better to explicitly add the classpath in the task + * The task checks timestamps on the JSP's and the generated classes, and + * compiles only those files that have changed. It follows the weblogic + * naming convention of putting classes in _dirName/_fileName.class for + * dirname/fileName.jsp Limitation: It compiles the files thru the + * Classic compiler only. Limitation: Since it is my experience that + * weblogic jspc throws out of memory error on being given too many files + * at one go, it is called multiple times with one jsp file each.
      + * example
      + * <target name="jspcompile" depends="compile">
      + *   <wljspc src="c:\\weblogic\\myserver\\public_html" dest="c:\\weblogic\\myserver\\serverclasses" package="myapp.jsp">
      + *   <classpath>
      + *          <pathelement location="${weblogic.classpath}" />
      + *           <pathelement path="${compile.dest}" />
      + *      </classpath>
      + *
      + *   </wljspc>
      + * </target>
      + * 
      + */ + +public class WLJspc extends MatchingTask +{//classpath used to compile the jsp files. + //private String compilerPath; //fully qualified name for the compiler executable + + private String pathToPackage = ""; + private Vector filesToDo = new Vector();//package under which resultant classes will reside + private Path compileClasspath; + //TODO Test on other versions of weblogic + //TODO add more attributes to the task, to take care of all jspc options + //TODO Test on Unix + + private File destinationDirectory;// root of source files tree + private String destinationPackage;//root of compiled files tree + private File sourceDirectory; + + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Set the directory containing the source jsp's + * + * @param dirName the directory containg the source jsp's + */ + public void setDest( File dirName ) + { + + destinationDirectory = dirName; + } + + /** + * Set the package under which the compiled classes go + * + * @param packageName the package name for the clases + */ + public void setPackage( String packageName ) + { + + destinationPackage = packageName; + } + + /** + * Set the directory containing the source jsp's + * + * @param dirName the directory containg the source jsp's + */ + public void setSrc( File dirName ) + { + + sourceDirectory = dirName; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath; + } + + public void execute() + throws BuildException + { + if( !destinationDirectory.isDirectory() ) + { + throw new BuildException( "destination directory " + destinationDirectory.getPath() + + " is not valid" ); + } + + if( !sourceDirectory.isDirectory() ) + { + throw new BuildException( "src directory " + sourceDirectory.getPath() + + " is not valid" ); + } + + if( destinationPackage == null ) + { + throw new BuildException( "package attribute must be present.", location ); + } + + String systemClassPath = System.getProperty( "java.class.path" ); + + pathToPackage = this.destinationPackage.replace( '.', File.separatorChar ); + // get all the files in the sourceDirectory + DirectoryScanner ds = super.getDirectoryScanner( sourceDirectory ); + + //use the systemclasspath as well, to include the ant jar + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + + compileClasspath.append( Path.systemClasspath ); + String[] files = ds.getIncludedFiles(); + + //Weblogic.jspc calls System.exit() ... have to fork + // Therefore, takes loads of time + // Can pass directories at a time (*.jsp) but easily runs out of memory on hefty dirs + // (even on a Sun) + Java helperTask = ( Java )project.createTask( "java" ); + helperTask.setFork( true ); + helperTask.setClassname( "weblogic.jspc" ); + helperTask.setTaskName( getTaskName() ); + String[] args = new String[12]; + + File jspFile = null; + String parents = ""; + String arg = ""; + int j = 0; + //XXX this array stuff is a remnant of prev trials.. gotta remove. + args[j++] = "-d"; + args[j++] = destinationDirectory.getAbsolutePath().trim(); + args[j++] = "-docroot"; + args[j++] = sourceDirectory.getAbsolutePath().trim(); + args[j++] = "-keepgenerated";//TODO: Parameterise ?? + //Call compiler as class... dont want to fork again + //Use classic compiler -- can be parameterised? + args[j++] = "-compilerclass"; + args[j++] = "sun.tools.javac.Main"; + //Weblogic jspc does not seem to work unless u explicitly set this... + // Does not take the classpath from the env.... + // Am i missing something about the Java task?? + args[j++] = "-classpath"; + args[j++] = compileClasspath.toString(); + + this.scanDir( files ); + log( "Compiling " + filesToDo.size() + " JSP files" ); + + for( int i = 0; i < filesToDo.size(); i++ ) + { + //XXX + // All this to get package according to weblogic standards + // Can be written better... this is too hacky! + // Careful.. similar code in scanDir , but slightly different!! + jspFile = new File( ( String )filesToDo.elementAt( i ) ); + args[j] = "-package"; + parents = jspFile.getParent(); + if( ( parents != null ) && ( !( "" ).equals( parents ) ) ) + { + parents = this.replaceString( parents, File.separator, "_." ); + args[j + 1] = destinationPackage + "." + "_" + parents; + } + else + { + args[j + 1] = destinationPackage; + } + + args[j + 2] = sourceDirectory + File.separator + ( String )filesToDo.elementAt( i ); + arg = ""; + + for( int x = 0; x < 12; x++ ) + { + arg += " " + args[x]; + } + + System.out.println( "arg = " + arg ); + + helperTask.clearArgs(); + helperTask.setArgs( arg ); + helperTask.setClasspath( compileClasspath ); + if( helperTask.executeJava() != 0 ) + { + log( files[i] + " failed to compile", Project.MSG_WARN ); + } + } + } + + + protected String replaceString( String inpString, String escapeChars, String replaceChars ) + { + String localString = ""; + int numTokens = 0; + StringTokenizer st = new StringTokenizer( inpString, escapeChars, true ); + numTokens = st.countTokens(); + for( int i = 0; i < numTokens; i++ ) + { + String test = st.nextToken(); + test = ( test.equals( escapeChars ) ? replaceChars : test ); + localString += test; + } + return localString; + } + + + protected void scanDir( String files[] ) + { + + long now = ( new Date() ).getTime(); + File jspFile = null; + String parents = null; + String pack = ""; + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( this.sourceDirectory, files[i] ); + //XXX + // All this to convert source to destination directory according to weblogic standards + // Can be written better... this is too hacky! + jspFile = new File( files[i] ); + parents = jspFile.getParent(); + int loc = 0; + + if( ( parents != null ) && ( !( "" ).equals( parents ) ) ) + { + parents = this.replaceString( parents, File.separator, "_/" ); + pack = pathToPackage + File.separator + "_" + parents; + } + else + { + pack = pathToPackage; + } + + String filePath = pack + File.separator + "_"; + int startingIndex + = files[i].lastIndexOf( File.separator ) != -1 ? files[i].lastIndexOf( File.separator ) + 1 : 0; + int endingIndex = files[i].indexOf( ".jsp" ); + if( endingIndex == -1 ) + { + break; + } + + filePath += files[i].substring( startingIndex, endingIndex ); + filePath += ".class"; + File classFile = new File( this.destinationDirectory, filePath ); + + if( srcFile.lastModified() > now ) + { + log( "Warning: file modified in the future: " + + files[i], Project.MSG_WARN ); + } + if( srcFile.lastModified() > classFile.lastModified() ) + { + //log("Files are" + srcFile.getAbsolutePath()+" " +classFile.getAbsolutePath()); + filesToDo.addElement( files[i] ); + log( "Recompiling File " + files[i], Project.MSG_VERBOSE ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java new file mode 100644 index 000000000..62465e8d8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; + +/** + * The interface that all jsp compiler adapters must adher to.

      + * + * A compiler adapter is an adapter that interprets the jspc's parameters in + * preperation to be passed off to the compier this adapter represents. As all + * the necessary values are stored in the Jspc task itself, the only thing all + * adapters need is the jsp task, the execute command and a parameterless + * constructor (for reflection).

      + * + * @author Jay Dickon Glanville + * jayglanville@home.com + * @author Matthew Watson mattw@i3sp.com + */ + +public interface CompilerAdapter +{ + + /** + * Sets the compiler attributes, which are stored in the Jspc task. + * + * @param attributes The new Jspc value + */ + void setJspc( JspC attributes ); + + /** + * Executes the task. + * + * @return has the compilation been successful + * @exception BuildException Description of Exception + */ + boolean execute() + throws BuildException; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java new file mode 100644 index 000000000..2e648a5ac --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Creates the necessary compiler adapter, given basic criteria. + * + * @author J D Glanville + * @author Matthew Watson mattw@i3sp.com + */ +public class CompilerAdapterFactory +{ + + /** + * This is a singlton -- can't create instances!! + */ + private CompilerAdapterFactory() { } + + /** + * Based on the parameter passed in, this method creates the necessary + * factory desired. The current mapping for compiler names are as follows: + * + *
        + *
      • jasper = jasper compiler (the default) + *
      • a fully quallified classname = the name of a jsp compiler + * adapter + *
      + * + * + * @param compilerType either the name of the desired compiler, or the full + * classname of the compiler's adapter. + * @param task a task to log through. + * @return The Compiler value + * @throws BuildException if the compiler type could not be resolved into a + * compiler adapter. + */ + public static CompilerAdapter getCompiler( String compilerType, Task task ) + throws BuildException + { + /* + * If I've done things right, this should be the extent of the + * conditional statements required. + */ + if( compilerType.equalsIgnoreCase( "jasper" ) ) + { + return new JasperC(); + } + return resolveClassName( compilerType ); + } + + /** + * Tries to resolve the given classname into a compiler adapter. Throws a + * fit if it can't. + * + * @param className The fully qualified classname to be created. + * @return Description of the Returned Value + * @throws BuildException This is the fit that is thrown if className isn't + * an instance of CompilerAdapter. + */ + private static CompilerAdapter resolveClassName( String className ) + throws BuildException + { + try + { + Class c = Class.forName( className ); + Object o = c.newInstance(); + return ( CompilerAdapter )o; + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( className + " can\'t be found.", cnfe ); + } + catch( ClassCastException cce ) + { + throw new BuildException( className + " isn\'t the classname of " + + "a compiler adapter.", cce ); + } + catch( Throwable t ) + { + // for all other possibilities + throw new BuildException( className + " caused an interesting " + + "exception.", t ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java new file mode 100644 index 000000000..6c54419d4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; +import org.apache.tools.ant.types.Commandline; + +/** + * This is the default implementation for the CompilerAdapter interface. This is + * currently very light on the ground since only one compiler type is supported. + * + * @author Matthew Watson mattw@i3sp.com + */ +public abstract class DefaultCompilerAdapter + implements CompilerAdapter +{ + /* + * ------------------------------------------------------------ + */ + private static String lSep = System.getProperty( "line.separator" ); + /* + * ------------------------------------------------------------ + */ + protected JspC attributes; + + public void setJspc( JspC attributes ) + { + this.attributes = attributes; + } + + public JspC getJspc() + { + return attributes; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param jspc Description of Parameter + * @param compileList Description of Parameter + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( JspC jspc, + Vector compileList, + Commandline cmd ) + { + jspc.log( "Compilation args: " + cmd.toString(), Project.MSG_VERBOSE ); + + StringBuffer niceSourceList = new StringBuffer( "File" ); + if( compileList.size() != 1 ) + { + niceSourceList.append( "s" ); + } + niceSourceList.append( " to be compiled:" ); + + niceSourceList.append( lSep ); + + Enumeration enum = compileList.elements(); + while( enum.hasMoreElements() ) + { + String arg = ( String )enum.nextElement(); + cmd.createArgument().setValue( arg ); + niceSourceList.append( " " + arg + lSep ); + } + + jspc.log( niceSourceList.toString(), Project.MSG_VERBOSE ); + } + /* + * ------------------------------------------------------------ + */ +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java new file mode 100644 index 000000000..4f0985bbf --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the jasper compiler. This is a cut-and-paste of the + * original Jspc task. + * + * @author Matthew Watson mattw@i3sp.com + */ +public class JasperC extends DefaultCompilerAdapter +{ + /* + * ------------------------------------------------------------ + */ + public boolean execute() + throws BuildException + { + getJspc().log( "Using jasper compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupJasperCommand(); + + try + { + // Create an instance of the compiler, redirecting output to + // the project log + Java java = ( Java )( getJspc().getProject() ).createTask( "java" ); + if( getJspc().getClasspath() != null ) + java.setClasspath( getJspc().getClasspath() ); + java.setClassname( "org.apache.jasper.JspC" ); + String args[] = cmd.getArguments(); + for( int i = 0; i < args.length; i++ ) + java.createArg().setValue( args[i] ); + java.setFailonerror( true ); + java.execute(); + return true; + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error running jsp compiler: ", + ex, getJspc().getLocation() ); + } + } + } + + /* + * ------------------------------------------------------------ + */ + private Commandline setupJasperCommand() + { + Commandline cmd = new Commandline(); + JspC jspc = getJspc(); + if( jspc.getDestdir() != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( jspc.getDestdir() ); + } + if( jspc.getPackage() != null ) + { + cmd.createArgument().setValue( "-p" ); + cmd.createArgument().setValue( jspc.getPackage() ); + } + if( jspc.getVerbose() != 0 ) + { + cmd.createArgument().setValue( "-v" + jspc.getVerbose() ); + } + if( jspc.isMapped() ) + { + cmd.createArgument().setValue( "-mapped" ); + } + if( jspc.getIeplugin() != null ) + { + cmd.createArgument().setValue( "-ieplugin" ); + cmd.createArgument().setValue( jspc.getIeplugin() ); + } + if( jspc.getUriroot() != null ) + { + cmd.createArgument().setValue( "-uriroot" ); + cmd.createArgument().setValue( jspc.getUriroot().toString() ); + } + if( jspc.getUribase() != null ) + { + cmd.createArgument().setValue( "-uribase" ); + cmd.createArgument().setValue( jspc.getUribase().toString() ); + } + logAndAddFilesToCompile( getJspc(), getJspc().getCompileList(), cmd ); + return cmd; + } + /* + * ------------------------------------------------------------ + */ +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java new file mode 100644 index 000000000..0d1e7e9d7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + + + +/** + * Transform a JUnit xml report. The default transformation generates an html + * report in either framed or non-framed style. The non-framed style is + * convenient to have a concise report via mail, the framed report is much more + * convenient if you want to browse into different packages or testcases since + * it is a Javadoc like report. + * + * @author Stephane Bailliez + */ +public class AggregateTransformer +{ + + public final static String FRAMES = "frames"; + + public final static String NOFRAMES = "noframes"; + + /** + * XML Parser factory + */ + protected final static DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); + + /** + * the xml document to process + */ + protected Document document; + + /** + * the format to use for the report. Must be FRAMES or NOFRAMES + * + */ + protected String format; + + /** + * the style directory. XSLs should be read from here if necessary + */ + protected File styleDir; + + /** + * Task + */ + protected Task task; + + /** + * the destination directory, this is the root from where html should be + * generated + */ + protected File toDir; + + public AggregateTransformer( Task task ) + { + this.task = task; + } + + /** + * set the extension of the output files + * + * @param ext The new Extension value + */ + public void setExtension( String ext ) + { + task.log( "extension is not used anymore", Project.MSG_WARN ); + } + + public void setFormat( Format format ) + { + this.format = format.getValue(); + } + + /** + * set the style directory. It is optional and will override the default xsl + * used. + * + * @param styledir the directory containing the xsl files if the user would + * like to override with its own style. + */ + public void setStyledir( File styledir ) + { + this.styleDir = styledir; + } + + /** + * set the destination directory + * + * @param todir The new Todir value + */ + public void setTodir( File todir ) + { + this.toDir = todir; + } + + public void setXmlDocument( Document doc ) + { + this.document = doc; + } + + public void transform() + throws BuildException + { + checkOptions(); + final long t0 = System.currentTimeMillis(); + try + { + Element root = document.getDocumentElement(); + XalanExecutor executor = XalanExecutor.newInstance( this ); + executor.execute(); + } + catch( Exception e ) + { + throw new BuildException( "Errors while applying transformations", e ); + } + final long dt = System.currentTimeMillis() - t0; + task.log( "Transform time: " + dt + "ms" ); + } + + /** + * Set the xml file to be processed. This is a helper if you want to set the + * file directly. Much more for testing purposes. + * + * @param xmlfile xml file to be processed + * @exception BuildException Description of Exception + */ + protected void setXmlfile( File xmlfile ) + throws BuildException + { + try + { + DocumentBuilder builder = dbfactory.newDocumentBuilder(); + InputStream in = new FileInputStream( xmlfile ); + try + { + Document doc = builder.parse( in ); + setXmlDocument( doc ); + } + finally + { + in.close(); + } + } + catch( Exception e ) + { + throw new BuildException( "Error while parsing document: " + xmlfile, e ); + } + } + + /** + * Get the systemid of the appropriate stylesheet based on its name and + * styledir. If no styledir is defined it will load it as a java resource in + * the xsl child package, otherwise it will get it from the given directory. + * + * @return The StylesheetSystemId value + * @throws IOException thrown if the requested stylesheet does not exist. + */ + protected String getStylesheetSystemId() + throws IOException + { + String xslname = "junit-frames.xsl"; + if( NOFRAMES.equals( format ) ) + { + xslname = "junit-noframes.xsl"; + } + URL url = null; + if( styleDir == null ) + { + url = getClass().getResource( "xsl/" + xslname ); + if( url == null ) + { + throw new FileNotFoundException( "Could not find jar resource " + xslname ); + } + } + else + { + File file = new File( styleDir, xslname ); + if( !file.exists() ) + { + throw new FileNotFoundException( "Could not find file '" + file + "'" ); + } + url = new URL( "file", "", file.getAbsolutePath() ); + } + return url.toExternalForm(); + } + + /** + * check for invalid options + * + * @exception BuildException Description of Exception + */ + protected void checkOptions() + throws BuildException + { + // set the destination directory relative from the project if needed. + if( toDir == null ) + { + toDir = task.getProject().resolveFile( "." ); + } + else if( !toDir.isAbsolute() ) + { + toDir = task.getProject().resolveFile( toDir.getPath() ); + } + } + + public static class Format extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{FRAMES, NOFRAMES}; + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java new file mode 100644 index 000000000..b2173d3e8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.util.Vector; + +/** + * Baseclass for BatchTest and JUnitTest. + * + * @author Stefan Bodewig + * @author Stephane Bailliez + */ +public abstract class BaseTest +{ + protected boolean haltOnError = false; + protected boolean haltOnFail = false; + protected boolean filtertrace = true; + protected boolean fork = false; + protected String ifProperty = null; + protected String unlessProperty = null; + protected Vector formatters = new Vector(); + /** + * destination directory + */ + protected File destDir = null; + protected String errorProperty; + + protected String failureProperty; + + public void setErrorProperty( String errorProperty ) + { + this.errorProperty = errorProperty; + } + + public void setFailureProperty( String failureProperty ) + { + this.failureProperty = failureProperty; + } + + public void setFiltertrace( boolean value ) + { + filtertrace = value; + } + + public void setFork( boolean value ) + { + fork = value; + } + + public void setHaltonerror( boolean value ) + { + haltOnError = value; + } + + public void setHaltonfailure( boolean value ) + { + haltOnFail = value; + } + + public void setIf( String propertyName ) + { + ifProperty = propertyName; + } + + /** + * Sets the destination directory. + * + * @param destDir The new Todir value + */ + public void setTodir( File destDir ) + { + this.destDir = destDir; + } + + public void setUnless( String propertyName ) + { + unlessProperty = propertyName; + } + + public java.lang.String getErrorProperty() + { + return errorProperty; + } + + public java.lang.String getFailureProperty() + { + return failureProperty; + } + + public boolean getFiltertrace() + { + return filtertrace; + } + + public boolean getFork() + { + return fork; + } + + public boolean getHaltonerror() + { + return haltOnError; + } + + public boolean getHaltonfailure() + { + return haltOnFail; + } + + /** + * @return the destination directory as an absolute path if it exists + * otherwise return null + */ + public String getTodir() + { + if( destDir != null ) + { + return destDir.getAbsolutePath(); + } + return null; + } + + public void addFormatter( FormatterElement elem ) + { + formatters.addElement( elem ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java new file mode 100644 index 000000000..115e2c1a3 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; + +/** + *

      + * + * Create then run JUnitTest's based on the list of files given by + * the fileset attribute.

      + * + * Every .java or .class file in the fileset is + * assumed to be a testcase. A JUnitTest is created for each of + * these named classes with basic setup inherited from the parent BatchTest + * . + * + * @author Jeff Martin + * @author Stefan Bodewig + * @author Stephane Bailliez + * @see JUnitTest + */ +public final class BatchTest extends BaseTest +{ + + /** + * the list of filesets containing the testcase filename rules + */ + private Vector filesets = new Vector(); + + /** + * the reference to the project + */ + private Project project; + + /** + * create a new batchtest instance + * + * @param project the project it depends on. + */ + public BatchTest( Project project ) + { + this.project = project; + } + + /** + * Convenient method to convert a pathname without extension to a fully + * qualified classname. For example org/apache/Whatever will be + * converted to org.apache.Whatever + * + * @param filename the filename to "convert" to a classname. + * @return the classname matching the filename. + */ + public final static String javaToClass( String filename ) + { + return filename.replace( File.separatorChar, '.' ); + } + + /** + * Return all JUnitTest instances obtain by applying the fileset + * rules. + * + * @return an enumeration of all elements of this batchtest that are a + * JUnitTest instance. + */ + public final Enumeration elements() + { + JUnitTest[] tests = createAllJUnitTest(); + return Enumerations.fromArray( tests ); + } + + /** + * Add a new fileset instance to this batchtest. Whatever the fileset is, + * only filename that are .java or .class will be + * considered as 'candidates'. + * + * @param fs the new fileset containing the rules to get the testcases. + */ + public void addFileSet( FileSet fs ) + { + filesets.addElement( fs ); + } + + /** + * Convenient method to merge the JUnitTest s of this batchtest to + * a Vector . + * + * @param v the vector to which should be added all individual tests of this + * batch test. + */ + final void addTestsTo( Vector v ) + { + JUnitTest[] tests = createAllJUnitTest(); + v.ensureCapacity( v.size() + tests.length ); + for( int i = 0; i < tests.length; i++ ) + { + v.addElement( tests[i] ); + } + } + + /** + * Iterate over all filesets and return the filename of all files that end + * with .java or .class . This is to avoid wrapping a + * JUnitTest over an xml file for example. A Testcase is obviously a + * java file (compiled or not). + * + * @return an array of filenames without their extension. As they should + * normally be taken from their root, filenames should match their + * fully qualified class name (If it is not the case it will fail when + * running the test). For the class org/apache/Whatever.class + * it will return org/apache/Whatever . + */ + private String[] getFilenames() + { + Vector v = new Vector(); + final int size = this.filesets.size(); + for( int j = 0; j < size; j++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( j ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + for( int k = 0; k < f.length; k++ ) + { + String pathname = f[k]; + if( pathname.endsWith( ".java" ) ) + { + v.addElement( pathname.substring( 0, pathname.length() - ".java".length() ) ); + } + else if( pathname.endsWith( ".class" ) ) + { + v.addElement( pathname.substring( 0, pathname.length() - ".class".length() ) ); + } + } + } + + String[] files = new String[v.size()]; + v.copyInto( files ); + return files; + } + + /** + * Create all JUnitTest s based on the filesets. Each instance is + * configured to match this instance properties. + * + * @return the array of all JUnitTest s that belongs to this batch. + */ + private JUnitTest[] createAllJUnitTest() + { + String[] filenames = getFilenames(); + JUnitTest[] tests = new JUnitTest[filenames.length]; + for( int i = 0; i < tests.length; i++ ) + { + String classname = javaToClass( filenames[i] ); + tests[i] = createJUnitTest( classname ); + } + return tests; + } + + /** + * Create a JUnitTest that has the same property as this + * BatchTest instance. + * + * @param classname the name of the class that should be run as a + * JUnitTest . It must be a fully qualified name. + * @return the JUnitTest over the given classname. + */ + private JUnitTest createJUnitTest( String classname ) + { + JUnitTest test = new JUnitTest(); + test.setName( classname ); + test.setHaltonerror( this.haltOnError ); + test.setHaltonfailure( this.haltOnFail ); + test.setFiltertrace( this.filtertrace ); + test.setFork( this.fork ); + test.setIf( this.ifProperty ); + test.setUnless( this.unlessProperty ); + test.setTodir( this.destDir ); + test.setFailureProperty( failureProperty ); + test.setErrorProperty( errorProperty ); + Enumeration list = this.formatters.elements(); + while( list.hasMoreElements() ) + { + test.addFormatter( ( FormatterElement )list.nextElement() ); + } + return test; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java new file mode 100644 index 000000000..81ee9ac8f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import org.apache.tools.ant.BuildException; + +/** + * Prints plain text output of the test to a specified Writer. Inspired by the + * PlainJUnitResultFormatter. + * + * @author Robert Watkins + * @see FormatterElement + * @see PlainJUnitResultFormatter + */ +public class BriefJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private java.text.NumberFormat m_numberFormat = java.text.NumberFormat.getInstance(); + + /** + * Output suite has written to System.out + */ + private String systemOutput = null; + + /** + * Output suite has written to System.err + */ + private String systemError = null; + + /** + * Where to write the log to. + */ + private java.io.OutputStream m_out; + + /** + * Used for writing the results. + */ + private java.io.PrintWriter m_output; + + /** + * Used for writing formatted results to. + */ + private java.io.PrintWriter m_resultWriter; + + /** + * Used as part of formatting the results. + */ + private java.io.StringWriter m_results; + + public BriefJUnitResultFormatter() + { + m_results = new java.io.StringWriter(); + m_resultWriter = new java.io.PrintWriter( m_results ); + } + + /** + * Sets the stream the formatter is supposed to write its results to. + * + * @param out The new Output value + */ + public void setOutput( java.io.OutputStream out ) + { + m_out = out; + m_output = new java.io.PrintWriter( out ); + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * A test caused an error. + * + * @param test The feature to be added to the Error attribute + * @param error The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable error ) + { + formatError( "\tCaused an ERROR", test, error ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( "\tFAILED", test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * A test ended. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Testsuite: " ); + sb.append( suite.getName() ); + sb.append( newLine ); + sb.append( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( m_numberFormat.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + sb.append( newLine ); + + // append the err and output streams to the log + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "------------- Standard Output ---------------" ) + .append( newLine ) + .append( systemOutput ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "------------- Standard Error -----------------" ) + .append( newLine ) + .append( systemError ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( output() != null ) + { + try + { + output().write( sb.toString() ); + resultWriter().close(); + output().write( m_results.toString() ); + output().flush(); + } + finally + { + if( m_out != ( Object )System.out && + m_out != ( Object )System.err ) + { + try + { + m_out.close(); + } + catch( java.io.IOException e ) + {} + } + } + } + } + + /** + * A test started. + * + * @param test Description of Parameter + */ + public void startTest( Test test ) { } + + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void startTestSuite( JUnitTest suite ) + throws BuildException { } + + /** + * Format an error and print it. + * + * @param type Description of Parameter + * @param test Description of Parameter + * @param error Description of Parameter + */ + protected synchronized void formatError( String type, Test test, + Throwable error ) + { + if( test != null ) + { + endTest( test ); + } + + resultWriter().println( formatTest( test ) + type ); + resultWriter().println( error.getMessage() ); + String strace = JUnitTestRunner.getFilteredTrace( error ); + resultWriter().println( strace ); + resultWriter().println( "" ); + } + + /** + * Format the test for printing.. + * + * @param test Description of Parameter + * @return Description of the Returned Value + */ + protected String formatTest( Test test ) + { + if( test == null ) + { + return "Null Test: "; + } + else + { + return "Testcase: " + test.toString() + ":"; + } + } + + protected java.io.PrintWriter output() + { + return m_output; + } + + protected java.io.PrintWriter resultWriter() + { + return m_resultWriter; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java new file mode 100644 index 000000000..3fab8d7be --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Vector; +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; + +/** + * Some utilities that might be useful when manipulating DOM trees. + * + * @author Stephane Bailliez + */ +public final class DOMUtil +{ + + /** + * unused constructor + */ + private DOMUtil() { } + + + /** + * Iterate over the children of a given node and return the first node that + * has a specific name. + * + * @param parent the node to search child from. Can be null . + * @param tagname the child name we are looking for. Cannot be null + * . + * @return the first child that matches the given name or null if + * the parent is null or if a child does not match the given + * name. + */ + public static Element getChildByTagName( Node parent, String tagname ) + { + if( parent == null ) + { + return null; + } + NodeList childList = parent.getChildNodes(); + final int len = childList.getLength(); + for( int i = 0; i < len; i++ ) + { + Node child = childList.item( i ); + if( child != null && child.getNodeType() == Node.ELEMENT_NODE && + child.getNodeName().equals( tagname ) ) + { + return ( Element )child; + } + } + return null; + } + + /** + * return the attribute value of an element. + * + * @param node the node to get the attribute from. + * @param name the name of the attribute we are looking for the value. + * @return the value of the requested attribute or null if the + * attribute was not found or if node is not an Element + * . + */ + public static String getNodeAttribute( Node node, String name ) + { + if( node instanceof Element ) + { + Element element = ( Element )node; + return element.getAttribute( name ); + } + return null; + } + + /** + * Simple tree walker that will clone recursively a node. This is to avoid + * using parser-specific API such as Sun's changeNodeOwner when we + * are dealing with DOM L1 implementations since cloneNode(boolean) + * will not change the owner document. changeNodeOwner is much + * faster and avoid the costly cloning process. importNode is in + * the DOM L2 interface. + * + * @param parent the node parent to which we should do the import to. + * @param child the node to clone recursively. Its clone will be appended to + * parent . + * @return the cloned node that is appended to parent + */ + public final static Node importNode( Node parent, Node child ) + { + Node copy = null; + final Document doc = parent.getOwnerDocument(); + + switch ( child.getNodeType() ) + { + case Node.CDATA_SECTION_NODE: + copy = doc.createCDATASection( ( ( CDATASection )child ).getData() ); + break; + case Node.COMMENT_NODE: + copy = doc.createComment( ( ( Comment )child ).getData() ); + break; + case Node.DOCUMENT_FRAGMENT_NODE: + copy = doc.createDocumentFragment(); + break; + case Node.ELEMENT_NODE: + final Element elem = doc.createElement( ( ( Element )child ).getTagName() ); + copy = elem; + final NamedNodeMap attributes = child.getAttributes(); + if( attributes != null ) + { + final int size = attributes.getLength(); + for( int i = 0; i < size; i++ ) + { + final Attr attr = ( Attr )attributes.item( i ); + elem.setAttribute( attr.getName(), attr.getValue() ); + } + } + break; + case Node.ENTITY_REFERENCE_NODE: + copy = doc.createEntityReference( child.getNodeName() ); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + final ProcessingInstruction pi = ( ProcessingInstruction )child; + copy = doc.createProcessingInstruction( pi.getTarget(), pi.getData() ); + break; + case Node.TEXT_NODE: + copy = doc.createTextNode( ( ( Text )child ).getData() ); + break; + default: + // this should never happen + throw new IllegalStateException( "Invalid node type: " + child.getNodeType() ); + } + + // okay we have a copy of the child, now the child becomes the parent + // and we are iterating recursively over its children. + try + { + final NodeList children = child.getChildNodes(); + if( children != null ) + { + final int size = children.getLength(); + for( int i = 0; i < size; i++ ) + { + final Node newChild = children.item( i ); + if( newChild != null ) + { + importNode( copy, newChild ); + } + } + } + } + catch( DOMException ignored ) + { + } + + // bingo append it. (this should normally not be done here) + parent.appendChild( copy ); + return copy; + } + + /** + * list a set of node that match a specific filter. The list can be made + * recursively or not. + * + * @param parent the parent node to search from + * @param filter the filter that children should match. + * @param recurse true if you want the list to be made recursively + * otherwise false . + * @return Description of the Returned Value + */ + public static NodeList listChildNodes( Node parent, NodeFilter filter, boolean recurse ) + { + NodeListImpl matches = new NodeListImpl(); + NodeList children = parent.getChildNodes(); + if( children != null ) + { + final int len = children.getLength(); + for( int i = 0; i < len; i++ ) + { + Node child = children.item( i ); + if( filter.accept( child ) ) + { + matches.addElement( child ); + } + if( recurse ) + { + NodeList recmatches = listChildNodes( child, filter, recurse ); + final int reclength = matches.getLength(); + for( int j = 0; j < reclength; j++ ) + { + matches.addElement( recmatches.item( i ) ); + } + } + } + } + return matches; + } + + /** + * Filter interface to be applied when iterating over a DOM tree. Just think + * of it like a FileFilter clone. + * + * @author RT + */ + public interface NodeFilter + { + /** + * @param node the node to check for acceptance. + * @return true if the node is accepted by this filter, + * otherwise false + */ + boolean accept( Node node ); + } + + /** + * custom implementation of a nodelist + * + * @author RT + */ + public static class NodeListImpl extends Vector implements NodeList + { + public int getLength() + { + return size(); + } + + public Node item( int i ) + { + try + { + return ( Node )elementAt( i ); + } + catch( ArrayIndexOutOfBoundsException e ) + { + return null;// conforming to NodeList interface + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java new file mode 100644 index 000000000..bc75a4e5a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * A couple of methods related to enumerations that might be useful. This class + * should probably disappear once the required JDK is set to 1.2 instead of 1.1. + * + * @author Stephane Bailliez + */ +public final class Enumerations +{ + + private Enumerations() { } + + /** + * creates an enumeration from an array of objects. + * + * @param array the array of object to enumerate. + * @return the enumeration over the array of objects. + */ + public static Enumeration fromArray( Object[] array ) + { + return new ArrayEnumeration( array ); + } + + /** + * creates an enumeration from an array of enumeration. The created + * enumeration will sequentially enumerate over all elements of each + * enumeration and skip null enumeration elements in the array. + * + * @param enums the array of enumerations. + * @return the enumeration over the array of enumerations. + */ + public static Enumeration fromCompound( Enumeration[] enums ) + { + return new CompoundEnumeration( enums ); + } + +} + + +/** + * Convenient enumeration over an array of objects. + * + * @author Stephane Bailliez + */ +class ArrayEnumeration implements Enumeration +{ + + /** + * object array + */ + private Object[] array; + + /** + * current index + */ + private int pos; + + /** + * Initialize a new enumeration that wraps an array. + * + * @param array the array of object to enumerate. + */ + public ArrayEnumeration( Object[] array ) + { + this.array = array; + this.pos = 0; + } + + /** + * Tests if this enumeration contains more elements. + * + * @return true if and only if this enumeration object contains + * at least one more element to provide; false otherwise. + */ + public boolean hasMoreElements() + { + return ( pos < array.length ); + } + + /** + * Returns the next element of this enumeration if this enumeration object + * has at least one more element to provide. + * + * @return the next element of this enumeration. + * @throws NoSuchElementException if no more elements exist. + */ + public Object nextElement() + throws NoSuchElementException + { + if( hasMoreElements() ) + { + Object o = array[pos]; + pos++; + return o; + } + throw new NoSuchElementException(); + } +} + +/** + * Convenient enumeration over an array of enumeration. For example:

      + * Enumeration e1 = v1.elements();
      + * while (e1.hasMoreElements()){
      + *    // do something
      + * }
      + * Enumeration e2 = v2.elements();
      + * while (e2.hasMoreElements()){
      + *    // do the same thing
      + * }
      + * 
      can be written as:
      + * Enumeration[] enums = { v1.elements(), v2.elements() };
      + * Enumeration e = Enumerations.fromCompound(enums);
      + * while (e.hasMoreElements()){
      + *    // do something
      + * }
      + * 
      Note that the enumeration will skip null elements in the array. The + * following is thus possible:
      + * Enumeration[] enums = { v1.elements(), null, v2.elements() }; // a null enumeration in the array
      + * Enumeration e = Enumerations.fromCompound(enums);
      + * while (e.hasMoreElements()){
      + *    // do something
      + * }
      + * 
      + * + * @author Stephane Bailliez + */ +class CompoundEnumeration implements Enumeration +{ + + /** + * index in the enums array + */ + private int index = 0; + + /** + * enumeration array + */ + private Enumeration[] enumArray; + + public CompoundEnumeration( Enumeration[] enumarray ) + { + this.enumArray = enumarray; + } + + /** + * Tests if this enumeration contains more elements. + * + * @return true if and only if this enumeration object contains + * at least one more element to provide; false otherwise. + */ + public boolean hasMoreElements() + { + while( index < enumArray.length ) + { + if( enumArray[index] != null && enumArray[index].hasMoreElements() ) + { + return true; + } + index++; + } + return false; + } + + /** + * Returns the next element of this enumeration if this enumeration object + * has at least one more element to provide. + * + * @return the next element of this enumeration. + * @throws NoSuchElementException if no more elements exist. + */ + public Object nextElement() + throws NoSuchElementException + { + if( hasMoreElements() ) + { + return enumArray[index].nextElement(); + } + throw new NoSuchElementException(); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java new file mode 100644 index 000000000..b7db0aa61 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + *

      + * + * A wrapper for the implementations of JUnitResultFormatter. In + * particular, used as a nested <formatter> element in a + * <junit> task.

      + * + * For example,

      + *       <junit printsummary="no" haltonfailure="yes" fork="false">
      + *           <formatter type="plain" usefile="false" />
      + *           <test name="org.apache.ecs.InternationalCharTest" />
      + *       </junit>
      adds a plain type + * implementation (PlainJUnitResultFormatter) to display the + * results of the test.

      + * + * Either the type or the classname attribute must be + * set. + * + * @author Stefan Bodewig + * @see JUnitTask + * @see XMLJUnitResultFormatter + * @see BriefJUnitResultFormatter + * @see PlainJUnitResultFormatter + * @see JUnitResultFormatter + */ +public class FormatterElement +{ + private OutputStream out = System.out; + private boolean useFile = true; + + private String classname; + private String extension; + private File outFile; + + /** + *

      + * + * Set name of class to be used as the formatter.

      + * + * This class must implement JUnitResultFormatter + * + * @param classname The new Classname value + */ + public void setClassname( String classname ) + { + this.classname = classname; + } + + public void setExtension( String ext ) + { + this.extension = ext; + } + + /** + *

      + * + * Set output stream for formatter to use.

      + * + * Defaults to standard out. + * + * @param out The new Output value + */ + public void setOutput( OutputStream out ) + { + this.out = out; + } + + /** + *

      + * + * Quick way to use a standard formatter.

      + * + * At the moment, there are three supported standard formatters. + *

        + *
      • The xml type uses a XMLJUnitResultFormatter + * . + *
      • The brief type uses a BriefJUnitResultFormatter + * . + *
      • The plain type (the default) uses a PlainJUnitResultFormatter + * . + *
      + *

      + * + * Sets classname attribute - so you can't use that attribute + * if you use this one. + * + * @param type The new Type value + */ + public void setType( TypeAttribute type ) + { + if( "xml".equals( type.getValue() ) ) + { + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter" ); + setExtension( ".xml" ); + } + else + { + if( "brief".equals( type.getValue() ) ) + { + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.BriefJUnitResultFormatter" ); + } + else + {// must be plain, ensured by TypeAttribute + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.PlainJUnitResultFormatter" ); + } + setExtension( ".txt" ); + } + } + + /** + * Set whether the formatter should log to file. + * + * @param useFile The new UseFile value + */ + public void setUseFile( boolean useFile ) + { + this.useFile = useFile; + } + + /** + * Get name of class to be used as the formatter. + * + * @return The Classname value + */ + public String getClassname() + { + return classname; + } + + public String getExtension() + { + return extension; + } + + /** + *

      + * + * Set the file which the formatte should log to.

      + * + * Note that logging to file must be enabled . + * + * @param out The new Outfile value + */ + void setOutfile( File out ) + { + this.outFile = out; + } + + /** + * Get whether the formatter should log to file. + * + * @return The UseFile value + */ + boolean getUseFile() + { + return useFile; + } + + JUnitResultFormatter createFormatter() + throws BuildException + { + if( classname == null ) + { + throw new BuildException( "you must specify type or classname" ); + } + + Class f = null; + try + { + f = Class.forName( classname ); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( e ); + } + + Object o = null; + try + { + o = f.newInstance(); + } + catch( InstantiationException e ) + { + throw new BuildException( e ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( e ); + } + + if( !( o instanceof JUnitResultFormatter ) ) + { + throw new BuildException( classname + " is not a JUnitResultFormatter" ); + } + + JUnitResultFormatter r = ( JUnitResultFormatter )o; + + if( useFile && outFile != null ) + { + try + { + out = new FileOutputStream( outFile ); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + r.setOutput( out ); + return r; + } + + /** + *

      + * + * Enumerated attribute with the values "plain", "xml" and "brief".

      + * + * Use to enumerate options for type attribute. + * + * @author RT + */ + public static class TypeAttribute extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"plain", "xml", "brief"}; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java new file mode 100644 index 000000000..7f5f3a4ed --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import junit.framework.TestListener; +import org.apache.tools.ant.BuildException; + +/** + * This Interface describes classes that format the results of a JUnit testrun. + * + * @author Stefan Bodewig + */ +public interface JUnitResultFormatter extends TestListener +{ + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + void startTestSuite( JUnitTest suite ) + throws BuildException; + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + void endTestSuite( JUnitTest suite ) + throws BuildException; + + /** + * Sets the stream the formatter is supposed to write its results to. + * + * @param out The new Output value + */ + void setOutput( OutputStream out ); + + /** + * This is what the test has written to System.out + * + * @param out The new SystemOutput value + */ + void setSystemOutput( String out ); + + /** + * This is what the test has written to System.err + * + * @param err The new SystemError value + */ + void setSystemError( String err ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java new file mode 100644 index 000000000..b53a45da3 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java @@ -0,0 +1,812 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteWatchdog; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; + +/** + * Ant task to run JUnit tests.

      + * + * JUnit is a framework to create unit test. It has been initially created by + * Erich Gamma and Kent Beck. JUnit can be found at http://www.junit.org .

      + * + * JUnitTask can run a single specific JUnitTest using + * the test element. For example, the following target

      + *   <target name="test-int-chars" depends="jar-test">
      + *       <echo message="testing international characters"/>
      + *       <junit printsummary="no" haltonfailure="yes" fork="false">
      + *           <classpath refid="classpath"/>
      + *           <formatter type="plain" usefile="false" />
      + *           <test name="org.apache.ecs.InternationalCharTest" />
      + *       </junit>
      + *   </target>
      + * 
      runs a single junit test (org.apache.ecs.InternationalCharTest + * ) in the current VM using the path with id classpath as + * classpath and presents the results formatted using the standard plain + * formatter on the command line.

      + * + * This task can also run batches of tests. The batchtest element + * creates a BatchTest based on a fileset. This allows, for + * example, all classes found in directory to be run as testcases. For example, + *

      + * <target name="run-tests" depends="dump-info,compile-tests" if="junit.present">
      + *   <junit printsummary="no" haltonfailure="yes" fork="${junit.fork}">
      + *     <jvmarg value="-classic"/>
      + *     <classpath refid="tests-classpath"/>
      + *     <sysproperty key="build.tests" value="${build.tests}"/>
      + *     <formatter type="brief" usefile="false" />
      + *     <batchtest>
      + *       <fileset dir="${tests.dir}">
      + *         <include name="**/*Test*" />
      + *       </fileset>
      + *     </batchtest>
      + *   </junit>
      + * </target>
      + * 
      this target finds any classes with a test + * directory anywhere in their path (under the top ${tests.dir}, of + * course) and creates JUnitTest's for each one.

      + * + * Of course, <junit> and <batch> elements + * can be combined for more complex tests. For an example, see the ant build.xml + * target run-tests (the second example is an edited version).

      + * + * To spawn a new Java VM to prevent interferences between different testcases, + * you need to enable fork. A number of attributes and elements + * allow you to set up how this JVM runs. + *

        + *
      • {@link #setTimeout} property sets the maximum time allowed before a + * test is 'timed out' + *
      • {@link #setMaxmemory} property sets memory assignment for the forked + * jvm + *
      • {@link #setJvm} property allows the jvm to be specified + *
      • The <jvmarg> element sets arguements to be passed + * to the forked jvm + *
      + * + * + * @author Thomas Haas + * @author Stefan Bodewig + * @author Stephane Bailliez + * @author Gerrit Riessen + * @author Erik Hatcher + * @see JUnitTest + * @see BatchTest + */ +public class JUnitTask extends Task +{ + + private CommandlineJava commandline = new CommandlineJava(); + private Vector tests = new Vector(); + private Vector batchTests = new Vector(); + private Vector formatters = new Vector(); + private File dir = null; + + private Integer timeout = null; + private boolean summary = false; + private String summaryValue = ""; + private boolean filtertrace = true; + private JUnitTestRunner runner = null; + + /** + * Creates a new JUnitRunner and enables fork of a new Java VM. + * + * @exception Exception Description of Exception + */ + public JUnitTask() + throws Exception + { + commandline.setClassname( "org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" ); + } + + /** + * The directory to invoke the VM in. Ignored if no JVM is forked. + * + * @param dir the directory to invoke the JVM from. + * @see #setFork(boolean) + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Tells this task to set the named property to "true" when there is a error + * in a test. This property is applied on all BatchTest (batchtest) and + * JUnitTest (test), however, it can possibly be overriden by their own + * properties. + * + * @param propertyName The new ErrorProperty value + */ + public void setErrorProperty( String propertyName ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setErrorProperty( propertyName ); + } + } + + /** + * Tells this task to set the named property to "true" when there is a + * failure in a test. This property is applied on all BatchTest (batchtest) + * and JUnitTest (test), however, it can possibly be overriden by their own + * properties. + * + * @param propertyName The new FailureProperty value + */ + public void setFailureProperty( String propertyName ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFailureProperty( propertyName ); + } + } + + /** + * Tells this task whether to smartly filter the stack frames of JUnit + * testcase errors and failures before reporting them. This property is + * applied on all BatchTest (batchtest) and JUnitTest (test) however it can + * possibly be overridden by their own properties. + * + * @param value false if it should not filter, otherwise true + * + */ + public void setFiltertrace( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFiltertrace( value ); + } + } + + /** + * Tells whether a JVM should be forked for each testcase. It avoids + * interference between testcases and possibly avoids hanging the build. + * this property is applied on all BatchTest (batchtest) and JUnitTest + * (test) however it can possibly be overridden by their own properties. + * + * @param value true if a JVM should be forked, otherwise false + * + * @see #setTimeout + */ + public void setFork( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFork( value ); + } + } + + /** + * Tells this task to halt when there is an error in a test. this property + * is applied on all BatchTest (batchtest) and JUnitTest (test) however it + * can possibly be overridden by their own properties. + * + * @param value true if it should halt, otherwise false + */ + public void setHaltonerror( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setHaltonerror( value ); + } + } + + /** + * Tells this task to halt when there is a failure in a test. this property + * is applied on all BatchTest (batchtest) and JUnitTest (test) however it + * can possibly be overridden by their own properties. + * + * @param value true if it should halt, otherwise false + */ + public void setHaltonfailure( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setHaltonfailure( value ); + } + } + + /** + * Set a new VM to execute the testcase. Default is java . Ignored + * if no JVM is forked. + * + * @param value the new VM to use instead of java + * @see #setFork(boolean) + */ + public void setJvm( String value ) + { + commandline.setVm( value ); + } + + /** + * Set the maximum memory to be used by all forked JVMs. + * + * @param max the value as defined by -mx or -Xmx in the + * java command line options. + */ + public void setMaxmemory( String max ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + createJvmarg().setValue( "-mx" + max ); + } + else + { + createJvmarg().setValue( "-Xmx" + max ); + } + } + + /** + * Tells whether the task should print a short summary of the task. + * + * @param value true to print a summary, withOutAndErr to + * include the test's output as well, false otherwise. + * @see SummaryJUnitResultFormatter + */ + public void setPrintsummary( SummaryAttribute value ) + { + summaryValue = value.getValue(); + summary = value.asBoolean(); + } + + /** + * Set the timeout value (in milliseconds). If the test is running for more + * than this value, the test will be canceled. (works only when in 'fork' + * mode). + * + * @param value the maximum time (in milliseconds) allowed before declaring + * the test as 'timed-out' + * @see #setFork(boolean) + */ + public void setTimeout( Integer value ) + { + timeout = value; + } + + /** + * Add a new formatter to all tests of this task. + * + * @param fe The feature to be added to the Formatter attribute + */ + public void addFormatter( FormatterElement fe ) + { + formatters.addElement( fe ); + } + + /** + * Add a nested sysproperty element. This might be useful to tranfer Ant + * properties to the testcases when JVM forking is not enabled. + * + * @param sysp The feature to be added to the Sysproperty attribute + */ + public void addSysproperty( Environment.Variable sysp ) + { + commandline.addSysproperty( sysp ); + } + + /** + * Add a new single testcase. + * + * @param test a new single testcase + * @see JUnitTest + */ + public void addTest( JUnitTest test ) + { + tests.addElement( test ); + } + + /** + * Create a new set of testcases (also called ..batchtest) and add it to the + * list. + * + * @return a new instance of a batch test. + * @see BatchTest + */ + public BatchTest createBatchTest() + { + BatchTest test = new BatchTest( project ); + batchTests.addElement( test ); + return test; + } + + /** + * <classpath> allows classpath to be set for tests. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return commandline.createClasspath( project ).createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg() + { + return commandline.createVmArgument(); + } + + /** + * Runs the testcase. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Enumeration list = getIndividualTests(); + while( list.hasMoreElements() ) + { + JUnitTest test = ( JUnitTest )list.nextElement(); + if( test.shouldRun( project ) ) + { + execute( test ); + } + } + } + + /** + * Adds the jars or directories containing Ant, this task and JUnit to the + * classpath - this should make the forked JVM work without having to + * specify them directly. + */ + public void init() + { + addClasspathEntry( "/junit/framework/TestCase.class" ); + addClasspathEntry( "/org/apache/tools/ant/Task.class" ); + addClasspathEntry( "/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.class" ); + } + + /** + * Get the default output for a formatter. + * + * @return The DefaultOutput value + */ + protected OutputStream getDefaultOutput() + { + return new LogOutputStream( this, Project.MSG_INFO ); + } + + /** + * Merge all individual tests from the batchtest with all individual tests + * and return an enumeration over all JUnitTest . + * + * @return The IndividualTests value + */ + protected Enumeration getIndividualTests() + { + Enumeration[] enums = new Enumeration[batchTests.size() + 1]; + for( int i = 0; i < batchTests.size(); i++ ) + { + BatchTest batchtest = ( BatchTest )batchTests.elementAt( i ); + enums[i] = batchtest.elements(); + } + enums[enums.length - 1] = tests.elements(); + return Enumerations.fromCompound( enums ); + } + + /** + * return the file or null if does not use a file + * + * @param fe Description of Parameter + * @param test Description of Parameter + * @return The Output value + */ + protected File getOutput( FormatterElement fe, JUnitTest test ) + { + if( fe.getUseFile() ) + { + String filename = test.getOutfile() + fe.getExtension(); + File destFile = new File( test.getTodir(), filename ); + String absFilename = destFile.getAbsolutePath(); + return project.resolveFile( absFilename ); + } + return null; + } + + /** + * Search for the given resource and add the directory or archive that + * contains it to the classpath.

      + * + * Doesn't work for archives in JDK 1.1 as the URL returned by getResource + * doesn't contain the name of the archive.

      + * + * @param resource The feature to be added to the ClasspathEntry attribute + */ + protected void addClasspathEntry( String resource ) + { + URL url = getClass().getResource( resource ); + if( url != null ) + { + String u = url.toString(); + if( u.startsWith( "jar:file:" ) ) + { + int pling = u.indexOf( "!" ); + String jarName = u.substring( 9, pling ); + log( "Implicitly adding " + jarName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( jarName ) ).getAbsolutePath() ) ); + } + else if( u.startsWith( "file:" ) ) + { + int tail = u.indexOf( resource ); + String dirName = u.substring( 5, tail ); + log( "Implicitly adding " + dirName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( dirName ) ).getAbsolutePath() ) ); + } + else + { + log( "Don\'t know how to handle resource URL " + u, + Project.MSG_DEBUG ); + } + } + else + { + log( "Couldn\'t find " + resource, Project.MSG_DEBUG ); + } + } + + protected Enumeration allTests() + { + Enumeration[] enums = {tests.elements(), batchTests.elements()}; + return Enumerations.fromCompound( enums ); + } + + /** + * @return null if there is a timeout value, otherwise the watchdog + * instance. + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + if( timeout == null ) + { + return null; + } + return new ExecuteWatchdog( timeout.intValue() ); + } + + /** + * Run the tests. + * + * @param test Description of Parameter + * @exception BuildException Description of Exception + */ + protected void execute( JUnitTest test ) + throws BuildException + { + // set the default values if not specified + //@todo should be moved to the test class instead. + if( test.getTodir() == null ) + { + test.setTodir( project.resolveFile( "." ) ); + } + + if( test.getOutfile() == null ) + { + test.setOutfile( "TEST-" + test.getName() ); + } + + // execute the test and get the return code + int exitValue = JUnitTestRunner.ERRORS; + boolean wasKilled = false; + if( !test.getFork() ) + { + exitValue = executeInVM( test ); + } + else + { + ExecuteWatchdog watchdog = createWatchdog(); + exitValue = executeAsForked( test, watchdog ); + // null watchdog means no timeout, you'd better not check with null + if( watchdog != null ) + { + wasKilled = watchdog.killedProcess(); + } + } + + // if there is an error/failure and that it should halt, stop everything otherwise + // just log a statement + boolean errorOccurredHere = exitValue == JUnitTestRunner.ERRORS; + boolean failureOccurredHere = exitValue != JUnitTestRunner.SUCCESS; + if( errorOccurredHere || failureOccurredHere ) + { + if( errorOccurredHere && test.getHaltonerror() + || failureOccurredHere && test.getHaltonfailure() ) + { + throw new BuildException( "Test " + test.getName() + " failed" + + ( wasKilled ? " (timeout)" : "" ), + location ); + } + else + { + log( "TEST " + test.getName() + " FAILED" + + ( wasKilled ? " (timeout)" : "" ), Project.MSG_ERR ); + if( errorOccurredHere && test.getErrorProperty() != null ) + { + project.setProperty( test.getErrorProperty(), "true" ); + } + if( failureOccurredHere && test.getFailureProperty() != null ) + { + project.setProperty( test.getFailureProperty(), "true" ); + } + } + } + } + + protected void handleErrorOutput( String line ) + { + if( runner != null ) + { + runner.handleErrorOutput( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + // in VM is not very nice since it could probably hang the + // whole build. IMHO this method should be avoided and it would be best + // to remove it in future versions. TBD. (SBa) + + + protected void handleOutput( String line ) + { + if( runner != null ) + { + runner.handleOutput( line ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Execute a testcase by forking a new JVM. The command will block until it + * finishes. To know if the process was destroyed or not, use the + * killedProcess() method of the watchdog class. + * + * @param test the testcase to execute. + * @param watchdog the watchdog in charge of cancelling the test if it + * exceeds a certain amount of time. Can be null , in this + * case the test could probably hang forever. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int executeAsForked( JUnitTest test, ExecuteWatchdog watchdog ) + throws BuildException + { + CommandlineJava cmd = ( CommandlineJava )commandline.clone(); + + cmd.setClassname( "org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" ); + cmd.createArgument().setValue( test.getName() ); + cmd.createArgument().setValue( "filtertrace=" + test.getFiltertrace() ); + cmd.createArgument().setValue( "haltOnError=" + test.getHaltonerror() ); + cmd.createArgument().setValue( "haltOnFailure=" + test.getHaltonfailure() ); + if( summary ) + { + log( "Running " + test.getName(), Project.MSG_INFO ); + cmd.createArgument().setValue( "formatter=org.apache.tools.ant.taskdefs.optional.junit.SummaryJUnitResultFormatter" ); + } + + StringBuffer formatterArg = new StringBuffer( 128 ); + final FormatterElement[] feArray = mergeFormatters( test ); + for( int i = 0; i < feArray.length; i++ ) + { + FormatterElement fe = feArray[i]; + formatterArg.append( "formatter=" ); + formatterArg.append( fe.getClassname() ); + File outFile = getOutput( fe, test ); + if( outFile != null ) + { + formatterArg.append( "," ); + formatterArg.append( outFile ); + } + cmd.createArgument().setValue( formatterArg.toString() ); + formatterArg.setLength( 0 ); + } + + // Create a temporary file to pass the Ant properties to the forked test + File propsFile = new File( "junit" + ( new Random( System.currentTimeMillis() ) ).nextLong() + ".properties" ); + cmd.createArgument().setValue( "propsfile=" + propsFile.getAbsolutePath() ); + Hashtable p = project.getProperties(); + Properties props = new Properties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + try + { + FileOutputStream outstream = new FileOutputStream( propsFile ); + props.save( outstream, "Ant JUnitTask generated properties file" ); + outstream.close(); + } + catch( java.io.IOException e ) + { + throw new BuildException( "Error creating temporary properties file.", e, location ); + } + + Execute execute = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ), watchdog ); + execute.setCommandline( cmd.getCommandline() ); + execute.setAntRun( project ); + if( dir != null ) + { + execute.setWorkingDirectory( dir ); + } + + log( "Executing: " + cmd.toString(), Project.MSG_VERBOSE ); + int retVal; + try + { + retVal = execute.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Process fork failed.", e, location ); + } + finally + { + if( !propsFile.delete() ) + throw new BuildException( "Could not delete temporary properties file." ); + } + + return retVal; + } + + /** + * Execute inside VM. + * + * @param test Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int executeInVM( JUnitTest test ) + throws BuildException + { + test.setProperties( project.getProperties() ); + if( dir != null ) + { + log( "dir attribute ignored if running in the same VM", Project.MSG_WARN ); + } + + CommandlineJava.SysProperties sysProperties = commandline.getSystemProperties(); + if( sysProperties != null ) + { + sysProperties.setSystem(); + } + try + { + log( "Using System properties " + System.getProperties(), Project.MSG_VERBOSE ); + AntClassLoader cl = null; + Path classpath = commandline.getClasspath(); + if( classpath != null ) + { + log( "Using CLASSPATH " + classpath, Project.MSG_VERBOSE ); + + cl = new AntClassLoader( null, project, classpath, false ); + // make sure the test will be accepted as a TestCase + cl.addSystemPackageRoot( "junit" ); + // will cause trouble in JDK 1.1 if omitted + cl.addSystemPackageRoot( "org.apache.tools.ant" ); + } + runner = new JUnitTestRunner( test, test.getHaltonerror(), test.getFiltertrace(), test.getHaltonfailure(), cl ); + if( summary ) + { + log( "Running " + test.getName(), Project.MSG_INFO ); + + SummaryJUnitResultFormatter f = + new SummaryJUnitResultFormatter(); + f.setWithOutAndErr( "withoutanderr".equalsIgnoreCase( summaryValue ) ); + f.setOutput( getDefaultOutput() ); + runner.addFormatter( f ); + } + + final FormatterElement[] feArray = mergeFormatters( test ); + for( int i = 0; i < feArray.length; i++ ) + { + FormatterElement fe = feArray[i]; + File outFile = getOutput( fe, test ); + if( outFile != null ) + { + fe.setOutfile( outFile ); + } + else + { + fe.setOutput( getDefaultOutput() ); + } + runner.addFormatter( fe.createFormatter() ); + } + + runner.run(); + return runner.getRetCode(); + } + finally + { + if( sysProperties != null ) + { + sysProperties.restoreSystem(); + } + } + } + + private FormatterElement[] mergeFormatters( JUnitTest test ) + { + Vector feVector = ( Vector )formatters.clone(); + test.addFormattersTo( feVector ); + FormatterElement[] feArray = new FormatterElement[feVector.size()]; + feVector.copyInto( feArray ); + return feArray; + } + + /** + * Print summary enumeration values. + * + * @author RT + */ + public static class SummaryAttribute extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"true", "yes", "false", "no", + "on", "off", "withOutAndErr"}; + } + + public boolean asBoolean() + { + return "true".equals( value ) + || "on".equals( value ) + || "yes".equals( value ) + || "withOutAndErr".equals( value ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java new file mode 100644 index 000000000..93c2b2d01 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.Project; + +/** + *

      + * + * Run a single JUnit test.

      + * + * The JUnit test is actually run by {@link JUnitTestRunner}. So read the doc + * comments for that class :) + * + * @author Thomas Haas + * @author Stefan Bodewig , + * @author Stephane Bailliez + * @see JUnitTask + * @see JUnitTestRunner + */ +public class JUnitTest extends BaseTest +{ + + /** + * the name of the test case + */ + private String name = null; + + /** + * the name of the result file + */ + private String outfile = null; + + // Snapshot of the system properties + private Properties props = null; + private long runTime; + + // @todo this is duplicating TestResult information. Only the time is not + // part of the result. So we'd better derive a new class from TestResult + // and deal with it. (SB) + private long runs, failures, errors; + + public JUnitTest() { } + + public JUnitTest( String name ) + { + this.name = name; + } + + public JUnitTest( String name, boolean haltOnError, boolean haltOnFailure, boolean filtertrace ) + { + this.name = name; + this.haltOnError = haltOnError; + this.haltOnFail = haltOnFailure; + this.filtertrace = filtertrace; + } + + public void setCounts( long runs, long failures, long errors ) + { + this.runs = runs; + this.failures = failures; + this.errors = errors; + } + + /** + * Set the name of the test class. + * + * @param value The new Name value + */ + public void setName( String value ) + { + name = value; + } + + /** + * Set the name of the output file. + * + * @param value The new Outfile value + */ + public void setOutfile( String value ) + { + outfile = value; + } + + public void setProperties( Hashtable p ) + { + props = new Properties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + } + + public void setRunTime( long runTime ) + { + this.runTime = runTime; + } + + public FormatterElement[] getFormatters() + { + FormatterElement[] fes = new FormatterElement[formatters.size()]; + formatters.copyInto( fes ); + return fes; + } + + /** + * Get the name of the test class. + * + * @return The Name value + */ + public String getName() + { + return name; + } + + /** + * Get the name of the output file + * + * @return the name of the output file. + */ + public String getOutfile() + { + return outfile; + } + + public Properties getProperties() + { + return props; + } + + public long getRunTime() + { + return runTime; + } + + public long errorCount() + { + return errors; + } + + public long failureCount() + { + return failures; + } + + public long runCount() + { + return runs; + } + + public boolean shouldRun( Project p ) + { + if( ifProperty != null && p.getProperty( ifProperty ) == null ) + { + return false; + } + else if( unlessProperty != null && + p.getProperty( unlessProperty ) != null ) + { + return false; + } + + return true; + } + + /** + * Convenient method to add formatters to a vector + * + * @param v The feature to be added to the FormattersTo attribute + */ + void addFormattersTo( Vector v ) + { + final int count = formatters.size(); + for( int i = 0; i < count; i++ ) + { + v.addElement( formatters.elementAt( i ) ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java new file mode 100644 index 000000000..29170e402 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java @@ -0,0 +1,637 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Vector; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + +/** + * Simple Testrunner for JUnit that runs all tests of a testsuite.

      + * + * This TestRunner expects a name of a TestCase class as its argument. If this + * class provides a static suite() method it will be called and the resulting + * Test will be run. So, the signature should be

      
      + *     public static junit.framework.Test suite()
      + * 

      + * + * If no such method exists, all public methods starting with "test" and taking + * no argument will be run.

      + * + * Summary output is generated at the end. + * + * @author Stefan Bodewig + * @author Erik Hatcher + */ + +public class JUnitTestRunner implements TestListener +{ + + /** + * No problems with this test. + */ + public final static int SUCCESS = 0; + + /** + * Some tests failed. + */ + public final static int FAILURES = 1; + + /** + * An error occured. + */ + public final static int ERRORS = 2; + + /** + * Do we filter junit.*.* stack frames out of failure and error exceptions. + */ + private static boolean filtertrace = true; + + private final static String[] DEFAULT_TRACE_FILTERS = new String[]{ + "junit.framework.TestCase", + "junit.framework.TestResult", + "junit.framework.TestSuite", + "junit.framework.Assert.", // don't filter AssertionFailure + "junit.swingui.TestRunner", + "junit.awtui.TestRunner", + "junit.textui.TestRunner", + "java.lang.reflect.Method.invoke(", + "org.apache.tools.ant." + }; + + private static Vector fromCmdLine = new Vector(); + + /** + * Holds the registered formatters. + */ + private Vector formatters = new Vector(); + + /** + * Do we stop on errors. + */ + private boolean haltOnError = false; + + /** + * Do we stop on test failures. + */ + private boolean haltOnFailure = false; + + /** + * The corresponding testsuite. + */ + private Test suite = null; + + /** + * Returncode + */ + private int retCode = SUCCESS; + + /** + * Exception caught in constructor. + */ + private Exception exception; + + /** + * The TestSuite we are currently running. + */ + private JUnitTest junitTest; + + /** + * Collects TestResults. + */ + private TestResult res; + + /** + * output written during the test + */ + private PrintStream systemError; + + /** + * Error output during the test + */ + private PrintStream systemOut; + + /** + * Constructor for fork=true or when the user hasn't specified a classpath. + * + * @param test Description of Parameter + * @param haltOnError Description of Parameter + * @param filtertrace Description of Parameter + * @param haltOnFailure Description of Parameter + */ + public JUnitTestRunner( JUnitTest test, boolean haltOnError, boolean filtertrace, + boolean haltOnFailure ) + { + this( test, haltOnError, filtertrace, haltOnFailure, null ); + } + + /** + * Constructor to use when the user has specified a classpath. + * + * @param test Description of Parameter + * @param haltOnError Description of Parameter + * @param filtertrace Description of Parameter + * @param haltOnFailure Description of Parameter + * @param loader Description of Parameter + */ + public JUnitTestRunner( JUnitTest test, boolean haltOnError, boolean filtertrace, + boolean haltOnFailure, ClassLoader loader ) + { + //JUnitTestRunner.filtertrace = filtertrace; + this.filtertrace = filtertrace; + this.junitTest = test; + this.haltOnError = haltOnError; + this.haltOnFailure = haltOnFailure; + + try + { + Class testClass = null; + if( loader == null ) + { + testClass = Class.forName( test.getName() ); + } + else + { + testClass = loader.loadClass( test.getName() ); + AntClassLoader.initializeClass( testClass ); + } + + Method suiteMethod = null; + try + { + // check if there is a suite method + suiteMethod = testClass.getMethod( "suite", new Class[0] ); + } + catch( Exception e ) + { + // no appropriate suite method found. We don't report any + // error here since it might be perfectly normal. We don't + // know exactly what is the cause, but we're doing exactly + // the same as JUnit TestRunner do. We swallow the exceptions. + } + if( suiteMethod != null ) + { + // if there is a suite method available, then try + // to extract the suite from it. If there is an error + // here it will be caught below and reported. + suite = ( Test )suiteMethod.invoke( null, new Class[0] ); + } + else + { + // try to extract a test suite automatically + // this will generate warnings if the class is no suitable Test + suite = new TestSuite( testClass ); + } + + } + catch( Exception e ) + { + retCode = ERRORS; + exception = e; + } + } + + /** + * Returns a filtered stack trace. This is ripped out of + * junit.runner.BaseTestRunner. Scott M. Stirling. + * + * @param t Description of Parameter + * @return The FilteredTrace value + */ + public static String getFilteredTrace( Throwable t ) + { + String trace = StringUtils.getStackTrace( t ); + return JUnitTestRunner.filterStack( trace ); + } + + /** + * Filters stack frames from internal JUnit and Ant classes + * + * @param stack Description of Parameter + * @return Description of the Returned Value + */ + public static String filterStack( String stack ) + { + if( !filtertrace ) + { + return stack; + } + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter( sw ); + StringReader sr = new StringReader( stack ); + BufferedReader br = new BufferedReader( sr ); + + String line; + try + { + while( ( line = br.readLine() ) != null ) + { + if( !filterLine( line ) ) + { + pw.println( line ); + } + } + } + catch( Exception IOException ) + { + return stack;// return the stack unfiltered + } + return sw.toString(); + } + + /** + * Entry point for standalone (forked) mode. Parameters: testcaseclassname + * plus parameters in the format key=value, none of which is required. + * + * + * + * + * + * + * key + * + * + * + * description + * + * + * + * default value + * + * + * + * + * + * + * + * haltOnError + * + * + * + * halt test on errors? + * + * + * + * false + * + * + * + * + * + * + * + * haltOnFailure + * + * + * + * halt test on failures? + * + * + * + * false + * + * + * + * + * + * + * + * formatter + * + * + * + * A JUnitResultFormatter given as classname,filename. If filename is + * ommitted, System.out is assumed. + * + * + * + * none + * + * + * + * + * + * + * + * @param args The command line arguments + * @exception IOException Description of Exception + */ + public static void main( String[] args ) + throws IOException + { + boolean exitAtEnd = true; + boolean haltError = false; + boolean haltFail = false; + boolean stackfilter = true; + Properties props = new Properties(); + + if( args.length == 0 ) + { + System.err.println( "required argument TestClassName missing" ); + System.exit( ERRORS ); + } + + for( int i = 1; i < args.length; i++ ) + { + if( args[i].startsWith( "haltOnError=" ) ) + { + haltError = Project.toBoolean( args[i].substring( 12 ) ); + } + else if( args[i].startsWith( "haltOnFailure=" ) ) + { + haltFail = Project.toBoolean( args[i].substring( 14 ) ); + } + else if( args[i].startsWith( "filtertrace=" ) ) + { + stackfilter = Project.toBoolean( args[i].substring( 12 ) ); + } + else if( args[i].startsWith( "formatter=" ) ) + { + try + { + createAndStoreFormatter( args[i].substring( 10 ) ); + } + catch( BuildException be ) + { + System.err.println( be.getMessage() ); + System.exit( ERRORS ); + } + } + else if( args[i].startsWith( "propsfile=" ) ) + { + FileInputStream in = new FileInputStream( args[i].substring( 10 ) ); + props.load( in ); + in.close(); + } + } + + JUnitTest t = new JUnitTest( args[0] ); + + // Add/overlay system properties on the properties from the Ant project + Hashtable p = System.getProperties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + t.setProperties( props ); + + JUnitTestRunner runner = new JUnitTestRunner( t, haltError, stackfilter, haltFail ); + transferFormatters( runner ); + runner.run(); + System.exit( runner.getRetCode() ); + } + + /** + * Line format is: formatter=(, + * + * )? + * + * @param line Description of Parameter + * @exception BuildException Description of Exception + */ + private static void createAndStoreFormatter( String line ) + throws BuildException + { + FormatterElement fe = new FormatterElement(); + int pos = line.indexOf( ',' ); + if( pos == -1 ) + { + fe.setClassname( line ); + } + else + { + fe.setClassname( line.substring( 0, pos ) ); + fe.setOutfile( new File( line.substring( pos + 1 ) ) ); + } + fromCmdLine.addElement( fe.createFormatter() ); + } + + private static boolean filterLine( String line ) + { + for( int i = 0; i < DEFAULT_TRACE_FILTERS.length; i++ ) + { + if( line.indexOf( DEFAULT_TRACE_FILTERS[i] ) > 0 ) + { + return true; + } + } + return false; + } + + private static void transferFormatters( JUnitTestRunner runner ) + { + for( int i = 0; i < fromCmdLine.size(); i++ ) + { + runner.addFormatter( ( JUnitResultFormatter )fromCmdLine.elementAt( i ) ); + } + } + + /** + * Returns what System.exit() would return in the standalone version. + * + * @return 2 if errors occurred, 1 if tests failed else 0. + */ + public int getRetCode() + { + return retCode; + } + + /** + * Interface TestListener.

      + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + if( haltOnError ) + { + res.stop(); + } + } + + /** + * Interface TestListener for JUnit <= 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + if( haltOnFailure ) + { + res.stop(); + } + } + + /** + * Interface TestListener for JUnit > 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + public void addFormatter( JUnitResultFormatter f ) + { + formatters.addElement( f ); + } + + /** + * Interface TestListener.

      + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + public void run() + { + res = new TestResult(); + res.addListener( this ); + for( int i = 0; i < formatters.size(); i++ ) + { + res.addListener( ( TestListener )formatters.elementAt( i ) ); + } + + long start = System.currentTimeMillis(); + + fireStartTestSuite(); + if( exception != null ) + {// had an exception in the constructor + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( TestListener )formatters.elementAt( i ) ).addError( null, + exception ); + } + junitTest.setCounts( 1, 0, 1 ); + junitTest.setRunTime( 0 ); + } + else + { + + ByteArrayOutputStream errStrm = new ByteArrayOutputStream(); + systemError = new PrintStream( errStrm ); + + ByteArrayOutputStream outStrm = new ByteArrayOutputStream(); + systemOut = new PrintStream( outStrm ); + + try + { + suite.run( res ); + } + finally + { + systemError.close(); + systemError = null; + systemOut.close(); + systemOut = null; + sendOutAndErr( new String( outStrm.toByteArray() ), + new String( errStrm.toByteArray() ) ); + + junitTest.setCounts( res.runCount(), res.failureCount(), + res.errorCount() ); + junitTest.setRunTime( System.currentTimeMillis() - start ); + } + } + fireEndTestSuite(); + + if( retCode != SUCCESS || res.errorCount() != 0 ) + { + retCode = ERRORS; + } + else if( res.failureCount() != 0 ) + { + retCode = FAILURES; + } + } + + /** + * Interface TestListener.

      + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) { } + + protected void handleErrorOutput( String line ) + { + if( systemError != null ) + { + systemError.println( line ); + } + } + + protected void handleOutput( String line ) + { + if( systemOut != null ) + { + systemOut.println( line ); + } + } + + private void fireEndTestSuite() + { + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( JUnitResultFormatter )formatters.elementAt( i ) ).endTestSuite( junitTest ); + } + } + + private void fireStartTestSuite() + { + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( JUnitResultFormatter )formatters.elementAt( i ) ).startTestSuite( junitTest ); + } + } + + private void sendOutAndErr( String out, String err ) + { + for( int i = 0; i < formatters.size(); i++ ) + { + JUnitResultFormatter formatter = + ( ( JUnitResultFormatter )formatters.elementAt( i ) ); + + formatter.setSystemOutput( out ); + formatter.setSystemError( err ); + } + } + +}// JUnitTestRunner diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java new file mode 100644 index 000000000..082af4960 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.lang.reflect.Method; +import junit.framework.Test; +import junit.framework.TestCase; + +/** + * Work around for some changes to the public JUnit API between different JUnit + * releases. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class JUnitVersionHelper +{ + + private static Method testCaseName = null; + static + { + try + { + testCaseName = TestCase.class.getMethod( "getName", new Class[0] ); + } + catch( NoSuchMethodException e ) + { + // pre JUnit 3.7 + try + { + testCaseName = TestCase.class.getMethod( "name", new Class[0] ); + } + catch( NoSuchMethodException e2 ) + {} + } + } + + /** + * JUnit 3.7 introduces TestCase.getName() and subsequent versions of JUnit + * remove the old name() method. This method provides access to the name of + * a TestCase via reflection that is supposed to work with version before + * and after JUnit 3.7. + * + * @param t Description of Parameter + * @return The TestCaseName value + */ + public static String getTestCaseName( Test t ) + { + if( t instanceof TestCase && testCaseName != null ) + { + try + { + return ( String )testCaseName.invoke( t, new Object[0] ); + } + catch( Throwable e ) + {} + } + return "unknown"; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java new file mode 100644 index 000000000..239da0895 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.NumberFormat; +import java.util.Hashtable; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import org.apache.tools.ant.BuildException; + +/** + * Prints plain text output of the test to a specified Writer. + * + * @author Stefan Bodewig + */ + +public class PlainJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private NumberFormat nf = NumberFormat.getInstance(); + /** + * Timing helper. + */ + private Hashtable testStarts = new Hashtable(); + /** + * Suppress endTest if testcase failed. + */ + private Hashtable failed = new Hashtable(); + + private String systemOutput = null; + private String systemError = null; + /** + * Helper to store intermediate output. + */ + private StringWriter inner; + /** + * Where to write the log to. + */ + private OutputStream out; + /** + * Convenience layer on top of {@link #inner inner}. + */ + private PrintWriter wri; + + public PlainJUnitResultFormatter() + { + inner = new StringWriter(); + wri = new PrintWriter( inner ); + } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * Interface TestListener.

      + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + formatError( "\tCaused an ERROR", test, t ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( "\tFAILED", test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Interface TestListener.

      + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) + { + synchronized( wri ) + { + wri.print( "Testcase: " + + JUnitVersionHelper.getTestCaseName( test ) ); + if( Boolean.TRUE.equals( failed.get( test ) ) ) + { + return; + } + Long l = ( Long )testStarts.get( test ); + wri.println( " took " + + nf.format( ( System.currentTimeMillis() - l.longValue() ) + / 1000.0 ) + + " sec" ); + } + } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Testsuite: " ); + sb.append( suite.getName() ); + sb.append( newLine ); + sb.append( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( nf.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + + // append the err and output streams to the log + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "------------- Standard Output ---------------" ) + .append( newLine ) + .append( systemOutput ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "------------- Standard Error -----------------" ) + .append( newLine ) + .append( systemError ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + sb.append( newLine ); + + if( out != null ) + { + try + { + out.write( sb.toString().getBytes() ); + wri.close(); + out.write( inner.toString().getBytes() ); + out.flush(); + } + catch( IOException ioex ) + { + throw new BuildException( "Unable to write output", ioex ); + } + finally + { + if( out != System.out && out != System.err ) + { + try + { + out.close(); + } + catch( IOException e ) + {} + } + } + } + } + + /** + * Interface TestListener.

      + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) + { + testStarts.put( t, new Long( System.currentTimeMillis() ) ); + failed.put( t, Boolean.FALSE ); + } + + /** + * Empty. + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) { } + + private void formatError( String type, Test test, Throwable t ) + { + synchronized( wri ) + { + if( test != null ) + { + endTest( test ); + failed.put( test, Boolean.TRUE ); + } + + wri.println( type ); + wri.println( t.getMessage() ); + String strace = JUnitTestRunner.getFilteredTrace( t ); + wri.print( strace ); + wri.println( "" ); + } + } + +}// PlainJUnitResultFormatter diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java new file mode 100644 index 000000000..2932da2d0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.text.NumberFormat; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import org.apache.tools.ant.BuildException; + +/** + * Prints short summary output of the test to Ant's logging system. + * + * @author Stefan Bodewig + */ + +public class SummaryJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private NumberFormat nf = NumberFormat.getInstance(); + + private boolean withOutAndErr = false; + private String systemOutput = null; + private String systemError = null; + /** + * OutputStream to write to. + */ + private OutputStream out; + + /** + * Empty + */ + public SummaryJUnitResultFormatter() { } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * Should the output to System.out and System.err be written to the summary. + * + * @param value The new WithOutAndErr value + */ + public void setWithOutAndErr( boolean value ) + { + withOutAndErr = value; + } + + /** + * Empty + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) { } + + /** + * Empty + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) { } + + /** + * Interface TestListener for JUnit > 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Empty + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( nf.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + + if( withOutAndErr ) + { + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "Output:" ).append( newLine ).append( systemOutput ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "Error: " ).append( newLine ).append( systemError ) + .append( newLine ); + } + } + + try + { + out.write( sb.toString().getBytes() ); + out.flush(); + } + catch( IOException ioex ) + { + throw new BuildException( "Unable to write summary output", ioex ); + } + finally + { + if( out != System.out && out != System.err ) + { + try + { + out.close(); + } + catch( IOException e ) + {} + } + } + } + + /** + * Empty + * + * @param t Description of Parameter + */ + public void startTest( Test t ) { } + + /** + * Empty + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) { } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java new file mode 100644 index 000000000..53a2d1bf5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; + +/** + *

      + * + * Interface groups XML constants. Interface that groups all constants used + * throughout the XML documents that are generated by the + * XMLJUnitResultFormatter As of now the DTD is:

      + * <-----------------
      + *
      + * @author Stephane Bailliez
      + * @see XMLJUnitResultFormatter
      + * @see XMLResultAggregator
      + * @todo describe DTDs ----------------------> 
      + */ +public interface XMLConstants +{ + /** + * the testsuites element for the aggregate document + */ + String TESTSUITES = "testsuites"; + + /** + * the testsuite element + */ + String TESTSUITE = "testsuite"; + + /** + * the testcase element + */ + String TESTCASE = "testcase"; + + /** + * the error element + */ + String ERROR = "error"; + + /** + * the failure element + */ + String FAILURE = "failure"; + + /** + * the system-err element + */ + String SYSTEM_ERR = "system-err"; + + /** + * the system-out element + */ + String SYSTEM_OUT = "system-out"; + + /** + * package attribute for the aggregate document + */ + String ATTR_PACKAGE = "package"; + + /** + * name attribute for property, testcase and testsuite elements + */ + String ATTR_NAME = "name"; + + /** + * time attribute for testcase and testsuite elements + */ + String ATTR_TIME = "time"; + + /** + * errors attribute for testsuite elements + */ + String ATTR_ERRORS = "errors"; + + /** + * failures attribute for testsuite elements + */ + String ATTR_FAILURES = "failures"; + + /** + * tests attribute for testsuite elements + */ + String ATTR_TESTS = "tests"; + + /** + * type attribute for failure and error elements + */ + String ATTR_TYPE = "type"; + + /** + * message attribute for failure elements + */ + String ATTR_MESSAGE = "message"; + + /** + * the properties element + */ + String PROPERTIES = "properties"; + + /** + * the property element + */ + String PROPERTY = "property"; + + /** + * value attribute for property elements + */ + String ATTR_VALUE = "value"; + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java new file mode 100644 index 000000000..97501dc2c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.DOMElementWriter; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; + +/** + * Prints XML output of the test to a specified Writer. + * + * @author Stefan Bodewig + * @author Erik Hatcher + * @see FormatterElement + */ + +public class XMLJUnitResultFormatter implements JUnitResultFormatter, XMLConstants +{ + /** + * Element for the current test. + */ + private Hashtable testElements = new Hashtable(); + /** + * Timing helper. + */ + private Hashtable testStarts = new Hashtable(); + + /** + * The XML document. + */ + private Document doc; + /** + * Where to write the log to. + */ + private OutputStream out; + /** + * The wrapper for the whole testsuite. + */ + private Element rootElement; + + public XMLJUnitResultFormatter() { } + + private static DocumentBuilder getDocumentBuilder() + { + try + { + return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } + catch( Exception exc ) + { + throw new ExceptionInInitializerError( exc ); + } + } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String out ) + { + formatOutput( SYSTEM_ERR, out ); + } + + public void setSystemOutput( String out ) + { + formatOutput( SYSTEM_OUT, out ); + } + + /** + * Interface TestListener.

      + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + formatError( ERROR, test, t ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( FAILURE, test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Interface TestListener.

      + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) + { + Element currentTest = ( Element )testElements.get( test ); + Long l = ( Long )testStarts.get( test ); + currentTest.setAttribute( ATTR_TIME, + "" + ( ( System.currentTimeMillis() - l.longValue() ) + / 1000.0 ) ); + } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + rootElement.setAttribute( ATTR_TESTS, "" + suite.runCount() ); + rootElement.setAttribute( ATTR_FAILURES, "" + suite.failureCount() ); + rootElement.setAttribute( ATTR_ERRORS, "" + suite.errorCount() ); + rootElement.setAttribute( ATTR_TIME, "" + ( suite.getRunTime() / 1000.0 ) ); + if( out != null ) + { + Writer wri = null; + try + { + wri = new OutputStreamWriter( out, "UTF8" ); + wri.write( "\n" ); + ( new DOMElementWriter() ).write( rootElement, wri, 0, " " ); + wri.flush(); + } + catch( IOException exc ) + { + throw new BuildException( "Unable to write log file", exc ); + } + finally + { + if( out != System.out && out != System.err ) + { + if( wri != null ) + { + try + { + wri.close(); + } + catch( IOException e ) + {} + } + } + } + } + } + + /** + * Interface TestListener.

      + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) + { + testStarts.put( t, new Long( System.currentTimeMillis() ) ); + + Element currentTest = doc.createElement( TESTCASE ); + currentTest.setAttribute( ATTR_NAME, + JUnitVersionHelper.getTestCaseName( t ) ); + rootElement.appendChild( currentTest ); + testElements.put( t, currentTest ); + } + + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) + { + doc = getDocumentBuilder().newDocument(); + rootElement = doc.createElement( TESTSUITE ); + rootElement.setAttribute( ATTR_NAME, suite.getName() ); + + // Output properties + Element propsElement = doc.createElement( PROPERTIES ); + rootElement.appendChild( propsElement ); + Properties props = suite.getProperties(); + if( props != null ) + { + Enumeration e = props.propertyNames(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + Element propElement = doc.createElement( PROPERTY ); + propElement.setAttribute( ATTR_NAME, name ); + propElement.setAttribute( ATTR_VALUE, props.getProperty( name ) ); + propsElement.appendChild( propElement ); + } + } + } + + private void formatError( String type, Test test, Throwable t ) + { + if( test != null ) + { + endTest( test ); + } + + Element nested = doc.createElement( type ); + Element currentTest = null; + if( test != null ) + { + currentTest = ( Element )testElements.get( test ); + } + else + { + currentTest = rootElement; + } + + currentTest.appendChild( nested ); + + String message = t.getMessage(); + if( message != null && message.length() > 0 ) + { + nested.setAttribute( ATTR_MESSAGE, t.getMessage() ); + } + nested.setAttribute( ATTR_TYPE, t.getClass().getName() ); + + String strace = JUnitTestRunner.getFilteredTrace( t ); + Text trace = doc.createTextNode( strace ); + nested.appendChild( trace ); + } + + private void formatOutput( String type, String output ) + { + Element nested = doc.createElement( type ); + rootElement.appendChild( nested ); + Text content = doc.createTextNode( output ); + nested.appendChild( content ); + } + +}// XMLJUnitResultFormatter diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java new file mode 100644 index 000000000..cc72e239f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Enumeration; +import java.util.Vector; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.DOMElementWriter; +import org.apache.tools.ant.util.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + + +/** + *

      + * + * This is an helper class that will aggregate all testsuites under a specific + * directory and create a new single document. It is not particulary clean but + * should be helpful while I am thinking about another technique.

      + * + * The main problem is due to the fact that a JVM can be forked for a testcase + * thus making it impossible to aggregate all testcases since the listener is + * (obviously) in the forked JVM. A solution could be to write a TestListener + * that will receive events from the TestRunner via sockets. This is IMHO the + * simplest way to do it to avoid this file hacking thing. + * + * @author Stephane Bailliez + */ +public class XMLResultAggregator extends Task implements XMLConstants +{ + + /** + * the default directory: . . It is resolved from the project + * directory + */ + public final static String DEFAULT_DIR = "."; + + /** + * the default file name: TESTS-TestSuites.xml + */ + public final static String DEFAULT_FILENAME = "TESTS-TestSuites.xml"; + + /** + * the list of all filesets, that should contains the xml to aggregate + */ + protected Vector filesets = new Vector(); + + protected Vector transformers = new Vector(); + + /** + * the directory to write the file to + */ + protected File toDir; + + /** + * the name of the result file + */ + protected String toFile; + + /** + * Create a new document builder. Will issue an + * ExceptionInitializerError if something is going wrong. It is fatal + * anyway. + * + * @return a new document builder to create a DOM + * @todo factorize this somewhere else. It is duplicated code. + */ + private static DocumentBuilder getDocumentBuilder() + { + try + { + return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } + catch( Exception exc ) + { + throw new ExceptionInInitializerError( exc ); + } + } + + /** + * Set the destination directory where the results should be written. If not + * set if will use {@link #DEFAULT_DIR}. When given a relative directory it + * will resolve it from the project directory. + * + * @param value the directory where to write the results, absolute or + * relative. + */ + public void setTodir( File value ) + { + toDir = value; + } + + /** + * Set the name of the file aggregating the results. It must be relative + * from the todir attribute. If not set it will use {@link + * #DEFAULT_FILENAME} + * + * @param value the name of the file. + * @see #setTodir(File) + */ + public void setTofile( String value ) + { + toFile = value; + } + + /** + * Add a new fileset containing the xml results to aggregate + * + * @param fs the new fileset of xml results. + */ + public void addFileSet( FileSet fs ) + { + filesets.addElement( fs ); + } + + + public AggregateTransformer createReport() + { + AggregateTransformer transformer = new AggregateTransformer( this ); + transformers.addElement( transformer ); + return transformer; + } + + /** + * Aggregate all testsuites into a single document and write it to the + * specified directory and file. + * + * @throws BuildException thrown if there is a serious error while writing + * the document. + */ + public void execute() + throws BuildException + { + Element rootElement = createDocument(); + File destFile = getDestinationFile(); + // write the document + try + { + writeDOMTree( rootElement.getOwnerDocument(), destFile ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to write test aggregate to '" + destFile + "'", e ); + } + // apply transformation + Enumeration enum = transformers.elements(); + while( enum.hasMoreElements() ) + { + AggregateTransformer transformer = + ( AggregateTransformer )enum.nextElement(); + transformer.setXmlDocument( rootElement.getOwnerDocument() ); + transformer.transform(); + } + } + + /** + * Get the full destination file where to write the result. It is made of + * the todir and tofile attributes. + * + * @return the destination file where should be written the result file. + */ + protected File getDestinationFile() + { + if( toFile == null ) + { + toFile = DEFAULT_FILENAME; + } + if( toDir == null ) + { + toDir = project.resolveFile( DEFAULT_DIR ); + } + return new File( toDir, toFile ); + } + + /** + * Get all .xml files in the fileset. + * + * @return all files in the fileset that end with a '.xml'. + */ + protected File[] getFiles() + { + Vector v = new Vector(); + final int size = filesets.size(); + for( int i = 0; i < size; i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + for( int j = 0; j < f.length; j++ ) + { + String pathname = f[j]; + if( pathname.endsWith( ".xml" ) ) + { + File file = new File( ds.getBasedir(), pathname ); + file = project.resolveFile( file.getPath() ); + v.addElement( file ); + } + } + } + + File[] files = new File[v.size()]; + v.copyInto( files ); + return files; + } + + /** + *

      + * + * Add a new testsuite node to the document. The main difference is that it + * split the previous fully qualified name into a package and a name.

      + * + * For example: org.apache.Whatever will be split into + * org.apache and Whatever . + * + * @param root the root element to which the testsuite node should + * be appended. + * @param testsuite the element to append to the given root. It will + * slightly modify the original node to change the name attribute and + * add a package one. + */ + protected void addTestSuite( Element root, Element testsuite ) + { + String fullclassname = testsuite.getAttribute( ATTR_NAME ); + int pos = fullclassname.lastIndexOf( '.' ); + + // a missing . might imply no package at all. Don't get fooled. + String pkgName = ( pos == -1 ) ? "" : fullclassname.substring( 0, pos ); + String classname = ( pos == -1 ) ? fullclassname : fullclassname.substring( pos + 1 ); + Element copy = ( Element )DOMUtil.importNode( root, testsuite ); + + // modify the name attribute and set the package + copy.setAttribute( ATTR_NAME, classname ); + copy.setAttribute( ATTR_PACKAGE, pkgName ); + } + + /** + *

      + * + * Create a DOM tree. Has 'testsuites' as firstchild and aggregates all + * testsuite results that exists in the base directory. + * + * @return the root element of DOM tree that aggregates all testsuites. + */ + protected Element createDocument() + { + // create the dom tree + DocumentBuilder builder = getDocumentBuilder(); + Document doc = builder.newDocument(); + Element rootElement = doc.createElement( TESTSUITES ); + doc.appendChild( rootElement ); + + // get all files and add them to the document + File[] files = getFiles(); + for( int i = 0; i < files.length; i++ ) + { + try + { + log( "Parsing file: '" + files[i] + "'", Project.MSG_VERBOSE ); + //XXX there seems to be a bug in xerces 1.3.0 that doesn't like file object + // will investigate later. It does not use the given directory but + // the vm dir instead ? Works fine with crimson. + Document testsuiteDoc = builder.parse( "file:///" + files[i].getAbsolutePath() ); + Element elem = testsuiteDoc.getDocumentElement(); + // make sure that this is REALLY a testsuite. + if( TESTSUITE.equals( elem.getNodeName() ) ) + { + addTestSuite( rootElement, elem ); + } + else + { + // issue a warning. + log( "the file " + files[i] + " is not a valid testsuite XML document", Project.MSG_WARN ); + } + } + catch( SAXException e ) + { + // a testcase might have failed and write a zero-length document, + // It has already failed, but hey.... mm. just put a warning + log( "The file " + files[i] + " is not a valid XML document. It is possibly corrupted.", Project.MSG_WARN ); + log( StringUtils.getStackTrace( e ), Project.MSG_DEBUG ); + } + catch( IOException e ) + { + log( "Error while accessing file " + files[i] + ": " + e.getMessage(), Project.MSG_ERR ); + } + } + return rootElement; + } + + //----- from now, the methods are all related to DOM tree manipulation + + /** + * Write the DOM tree to a file. + * + * @param doc the XML document to dump to disk. + * @param file the filename to write the document to. Should obviouslly be a + * .xml file. + * @throws IOException thrown if there is an error while writing the + * content. + */ + protected void writeDOMTree( Document doc, File file ) + throws IOException + { + OutputStream out = new FileOutputStream( file ); + PrintWriter wri = new PrintWriter( new OutputStreamWriter( out, "UTF8" ) ); + wri.write( "\n" ); + ( new DOMElementWriter() ).write( doc.getDocumentElement(), wri, 0, " " ); + wri.flush(); + wri.close(); + // writers do not throw exceptions, so check for them. + if( wri.checkError() ) + { + throw new IOException( "Error while writing DOM content" ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java new file mode 100644 index 000000000..5f426d55e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import org.apache.xalan.xslt.XSLTInputSource; +import org.apache.xalan.xslt.XSLTProcessor; +import org.apache.xalan.xslt.XSLTProcessorFactory; +import org.apache.xalan.xslt.XSLTResultTarget; + +/** + * Xalan 1 executor. It will need a lot of things in the classpath: xerces for + * the serialization, xalan and bsf for the extension. + * + * @author RT + * @todo do everything via reflection to avoid compile problems ? + */ +public class Xalan1Executor extends XalanExecutor +{ + void execute() + throws Exception + { + XSLTProcessor processor = XSLTProcessorFactory.getProcessor(); + // need to quote otherwise it breaks because of "extra illegal tokens" + processor.setStylesheetParam( "output.dir", "'" + caller.toDir.getAbsolutePath() + "'" ); + XSLTInputSource xml_src = new XSLTInputSource( caller.document ); + String system_id = caller.getStylesheetSystemId(); + XSLTInputSource xsl_src = new XSLTInputSource( system_id ); + OutputStream os = getOutputStream(); + XSLTResultTarget target = new XSLTResultTarget( os ); + processor.process( xml_src, xsl_src, target ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java new file mode 100644 index 000000000..617424290 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * Xalan executor via JAXP. Nothing special must exists in the classpath besides + * of course, a parser, jaxp and xalan. + * + * @author RT + */ +public class Xalan2Executor extends XalanExecutor +{ + void execute() + throws Exception + { + TransformerFactory tfactory = TransformerFactory.newInstance(); + String system_id = caller.getStylesheetSystemId(); + Source xsl_src = new StreamSource( system_id ); + Transformer tformer = tfactory.newTransformer( xsl_src ); + Source xml_src = new DOMSource( caller.document ); + OutputStream os = getOutputStream(); + tformer.setParameter( "output.dir", caller.toDir.getAbsolutePath() ); + Result result = new StreamResult( os ); + tformer.transform( xml_src, result ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java new file mode 100644 index 000000000..0b95c3426 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import org.apache.tools.ant.BuildException; + +/** + * Command class that encapsulate specific behavior for each Xalan version. The + * right executor will be instantiated at runtime via class lookup. For + * instance, it will check first for Xalan2, then for Xalan1. + * + * @author RT + */ +abstract class XalanExecutor +{ + /** + * the transformer caller + */ + protected AggregateTransformer caller; + + /** + * Create a valid Xalan executor. It checks first if Xalan2 is present, if + * not it checks for xalan1. If none is available, it fails. + * + * @param caller object containing the transformation information. + * @return Description of the Returned Value + * @throws BuildException thrown if it could not find a valid xalan + * executor. + */ + static XalanExecutor newInstance( AggregateTransformer caller ) + throws BuildException + { + Class procVersion = null; + XalanExecutor executor = null; + try + { + procVersion = Class.forName( "org.apache.xalan.processor.XSLProcessorVersion" ); + executor = new Xalan2Executor(); + } + catch( Exception xalan2missing ) + { + try + { + procVersion = Class.forName( "org.apache.xalan.xslt.XSLProcessorVersion" ); + executor = ( XalanExecutor )Class.forName( + "org.apache.tools.ant.taskdefs.optional.junit.Xalan1Executor" ).newInstance(); + } + catch( Exception xalan1missing ) + { + throw new BuildException( "Could not find xalan2 nor xalan1 in the classpath. Check http://xml.apache.org/xalan-j" ); + } + } + String version = getXalanVersion( procVersion ); + caller.task.log( "Using Xalan version: " + version ); + executor.setCaller( caller ); + return executor; + } + + /** + * pretty useful data (Xalan version information) to display. + * + * @param procVersion Description of Parameter + * @return The XalanVersion value + */ + private static String getXalanVersion( Class procVersion ) + { + try + { + Field f = procVersion.getField( "S_VERSION" ); + return f.get( null ).toString(); + } + catch( Exception e ) + { + return "?"; + } + } + + /** + * get the appropriate stream based on the format (frames/noframes) + * + * @return The OutputStream value + * @exception IOException Description of Exception + */ + protected OutputStream getOutputStream() + throws IOException + { + if( caller.FRAMES.equals( caller.format ) ) + { + // dummy output for the framed report + // it's all done by extension... + return new ByteArrayOutputStream(); + } + else + { + return new FileOutputStream( new File( caller.toDir, "junit-noframes.html" ) ); + } + } + + /** + * override to perform transformation + * + * @exception Exception Description of Exception + */ + abstract void execute() + throws Exception; + + /** + * set the caller for this object. + * + * @param caller The new Caller value + */ + private final void setCaller( AggregateTransformer caller ) + { + this.caller = caller; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java new file mode 100644 index 000000000..500cddfd7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.metamata; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; + +/** + * Somewhat abstract framework to be used for other metama 2.0 tasks. This + * should include, audit, metrics, cover and mparse. For more information, visit + * the website at www.metamata.com + * + * @author Stephane Bailliez + */ +public abstract class AbstractMetamataTask extends Task +{ + + //--------------------------- ATTRIBUTES ----------------------------------- + + /** + * The user classpath to be provided. It matches the -classpath of the + * command line. The classpath must includes both the .class and + * the .java files for accurate audit. + */ + protected Path classPath = null; + + /** + * the path to the source file + */ + protected Path sourcePath = null; + + /** + * Metamata home directory. It will be passed as a metamata.home + * property and should normally matches the environment property + * META_HOME set by the Metamata installer. + */ + protected File metamataHome = null; + + /** + * the command line used to run MAudit + */ + protected CommandlineJava cmdl = new CommandlineJava(); + + /** + * the set of files to be audited + */ + protected Vector fileSets = new Vector(); + + /** + * the options file where are stored the command line options + */ + protected File optionsFile = null; + + // this is used to keep track of which files were included. It will + // be set when calling scanFileSets(); + protected Hashtable includedFiles = null; + + public AbstractMetamataTask() { } + + /** + * initialize the task with the classname of the task to run + * + * @param className Description of Parameter + */ + protected AbstractMetamataTask( String className ) + { + cmdl.setVm( "java" ); + cmdl.setClassname( className ); + } + + /** + * convenient method for JDK 1.1. Will copy all elements from src to dest + * + * @param dest The feature to be added to the AllVector attribute + * @param files The feature to be added to the AllVector attribute + */ + protected final static void addAllVector( Vector dest, Enumeration files ) + { + while( files.hasMoreElements() ) + { + dest.addElement( files.nextElement() ); + } + } + + protected final static File createTmpFile() + { + // must be compatible with JDK 1.1 !!!! + final long rand = ( new Random( System.currentTimeMillis() ) ).nextLong(); + File file = new File( "metamata" + rand + ".tmp" ); + return file; + } + + /** + * -mx or -Xmx depending on VM version + * + * @param max The new Maxmemory value + */ + public void setMaxmemory( String max ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + createJvmarg().setValue( "-mx" + max ); + } + else + { + createJvmarg().setValue( "-Xmx" + max ); + } + } + + /** + * the metamata.home property to run all tasks. + * + * @param metamataHome The new Metamatahome value + */ + public void setMetamatahome( final File metamataHome ) + { + this.metamataHome = metamataHome; + } + + + /** + * The java files or directory to be audited + * + * @param fs The feature to be added to the FileSet attribute + */ + public void addFileSet( FileSet fs ) + { + fileSets.addElement( fs ); + } + + /** + * user classpath + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classPath == null ) + { + classPath = new Path( project ); + } + return classPath; + } + + /** + * Creates a nested jvmarg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createJvmarg() + { + return cmdl.createVmArgument(); + } + + /** + * create the source path for this task + * + * @return Description of the Returned Value + */ + public Path createSourcepath() + { + if( sourcePath == null ) + { + sourcePath = new Path( project ); + } + return sourcePath; + } + + /** + * execute the command line + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + setUp(); + ExecuteStreamHandler handler = createStreamHandler(); + execute0( handler ); + } + finally + { + cleanUp(); + } + } + + //--------------------- PRIVATE/PROTECTED METHODS -------------------------- + + /** + * check the options and build the command line + * + * @exception BuildException Description of Exception + */ + protected void setUp() + throws BuildException + { + checkOptions(); + + // set the classpath as the jar file + File jar = getMetamataJar( metamataHome ); + final Path classPath = cmdl.createClasspath( project ); + classPath.createPathElement().setLocation( jar ); + + // set the metamata.home property + final Commandline.Argument vmArgs = cmdl.createVmArgument(); + vmArgs.setValue( "-Dmetamata.home=" + metamataHome.getAbsolutePath() ); + + // retrieve all the files we want to scan + includedFiles = scanFileSets(); + log( includedFiles.size() + " files added for audit", Project.MSG_VERBOSE ); + + // write all the options to a temp file and use it ro run the process + Vector options = getOptions(); + optionsFile = createTmpFile(); + generateOptionsFile( optionsFile, options ); + Commandline.Argument args = cmdl.createArgument(); + args.setLine( "-arguments " + optionsFile.getAbsolutePath() ); + } + + /** + * return the location of the jar file used to run + * + * @param home Description of Parameter + * @return The MetamataJar value + */ + protected final File getMetamataJar( File home ) + { + return new File( new File( home.getAbsolutePath() ), "lib/metamata.jar" ); + } + + + protected Hashtable getFileMapping() + { + return includedFiles; + } + + /** + * return all options of the command line as string elements + * + * @return The Options value + */ + protected abstract Vector getOptions(); + + /** + * validate options set + * + * @exception BuildException Description of Exception + */ + protected void checkOptions() + throws BuildException + { + // do some validation first + if( metamataHome == null || !metamataHome.exists() ) + { + throw new BuildException( "'metamatahome' must point to Metamata home directory." ); + } + metamataHome = project.resolveFile( metamataHome.getPath() ); + File jar = getMetamataJar( metamataHome ); + if( !jar.exists() ) + { + throw new BuildException( jar + " does not exist. Check your metamata installation." ); + } + } + + /** + * clean up all the mess that we did with temporary objects + */ + protected void cleanUp() + { + if( optionsFile != null ) + { + optionsFile.delete(); + optionsFile = null; + } + } + + /** + * create a stream handler that will be used to get the output since + * metamata tools do not report with convenient files such as XML. + * + * @return Description of the Returned Value + */ + protected abstract ExecuteStreamHandler createStreamHandler(); + + + /** + * execute the process with a specific handler + * + * @param handler Description of Parameter + * @exception BuildException Description of Exception + */ + protected void execute0( ExecuteStreamHandler handler ) + throws BuildException + { + final Execute process = new Execute( handler ); + log( cmdl.toString(), Project.MSG_VERBOSE ); + process.setCommandline( cmdl.getCommandline() ); + try + { + if( process.execute() != 0 ) + { + throw new BuildException( "Metamata task failed." ); + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to launch Metamata task: " + e ); + } + } + + + protected void generateOptionsFile( File tofile, Vector options ) + throws BuildException + { + FileWriter fw = null; + try + { + fw = new FileWriter( tofile ); + PrintWriter pw = new PrintWriter( fw ); + final int size = options.size(); + for( int i = 0; i < size; i++ ) + { + pw.println( options.elementAt( i ) ); + } + pw.flush(); + } + catch( IOException e ) + { + throw new BuildException( "Error while writing options file " + tofile, e ); + } + finally + { + if( fw != null ) + { + try + { + fw.close(); + } + catch( IOException ignored ) + {} + } + } + } + + /** + * @return the list of .java files (as their absolute path) that should be + * audited. + */ + protected Hashtable scanFileSets() + { + Hashtable files = new Hashtable(); + for( int i = 0; i < fileSets.size(); i++ ) + { + FileSet fs = ( FileSet )fileSets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + log( i + ") Adding " + f.length + " files from directory " + ds.getBasedir(), Project.MSG_VERBOSE ); + for( int j = 0; j < f.length; j++ ) + { + String pathname = f[j]; + if( pathname.endsWith( ".java" ) ) + { + File file = new File( ds.getBasedir(), pathname ); +// file = project.resolveFile(file.getAbsolutePath()); + String classname = pathname.substring( 0, pathname.length() - ".java".length() ); + classname = classname.replace( File.separatorChar, '.' ); + files.put( file.getAbsolutePath(), classname );// it's a java file, add it. + } + } + } + return files; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java new file mode 100644 index 000000000..80c5415bb --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.metamata; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Path; + +/** + * Metamata Audit evaluates Java code for programming errors, weaknesses, and + * style violation.

      + * + * Metamata Audit exists in three versions: + *

        + *
      • The Lite version evaluates about 15 built-in rules.
      • + *
      • The Pro version evaluates about 50 built-in rules.
      • + *
      • The Enterprise version allows you to add your own customized rules via + * the API.
      • + *
          For more information, visit the website at www.metamata.com + * + * @author Stephane Bailliez + */ +public class MAudit extends AbstractMetamataTask +{ + + /* + * As of Metamata 2.0, the command line of MAudit is as follows: + * Usage + * maudit + */ +public class DOMElementWriter +{ + + private static String lSep = System.getProperty( "line.separator" ); + private StringBuffer sb = new StringBuffer(); + + /** + * Don't try to be too smart but at least recognize the predefined entities. + */ + protected String[] knownEntities = {"gt", "amp", "lt", "apos", "quot"}; + + /** + * Is the given argument a character or entity reference? + * + * @param ent Description of Parameter + * @return The Reference value + */ + public boolean isReference( String ent ) + { + if( !( ent.charAt( 0 ) == '&' ) || !ent.endsWith( ";" ) ) + { + return false; + } + + if( ent.charAt( 1 ) == '#' ) + { + if( ent.charAt( 2 ) == 'x' ) + { + try + { + Integer.parseInt( ent.substring( 3, ent.length() - 1 ), 16 ); + return true; + } + catch( NumberFormatException nfe ) + { + return false; + } + } + else + { + try + { + Integer.parseInt( ent.substring( 2, ent.length() - 1 ) ); + return true; + } + catch( NumberFormatException nfe ) + { + return false; + } + } + } + + String name = ent.substring( 1, ent.length() - 1 ); + for( int i = 0; i < knownEntities.length; i++ ) + { + if( name.equals( knownEntities[i] ) ) + { + return true; + } + } + return false; + } + + /** + * Escape <, > & ' and " as their entities. + * + * @param value Description of Parameter + * @return Description of the Returned Value + */ + public String encode( String value ) + { + sb.setLength( 0 ); + for( int i = 0; i < value.length(); i++ ) + { + char c = value.charAt( i ); + switch ( c ) + { + case '<': + sb.append( "<" ); + break; + case '>': + sb.append( ">" ); + break; + case '\'': + sb.append( "'" ); + break; + case '\"': + sb.append( """ ); + break; + case '&': + int nextSemi = value.indexOf( ";", i ); + if( nextSemi < 0 + || !isReference( value.substring( i, nextSemi + 1 ) ) ) + { + sb.append( "&" ); + } + else + { + sb.append( '&' ); + } + break; + default: + sb.append( c ); + break; + } + } + return sb.toString(); + } + + /** + * Writes a DOM tree to a stream. + * + * @param element the Root DOM element of the tree + * @param out where to send the output + * @param indent number of + * @param indentWith strings, that should be used to indent the + * corresponding tag. + * @exception IOException Description of Exception + */ + public void write( Element element, Writer out, int indent, + String indentWith ) + throws IOException + { + + // Write indent characters + for( int i = 0; i < indent; i++ ) + { + out.write( indentWith ); + } + + // Write element + out.write( "<" ); + out.write( element.getTagName() ); + + // Write attributes + NamedNodeMap attrs = element.getAttributes(); + for( int i = 0; i < attrs.getLength(); i++ ) + { + Attr attr = ( Attr )attrs.item( i ); + out.write( " " ); + out.write( attr.getName() ); + out.write( "=\"" ); + out.write( encode( attr.getValue() ) ); + out.write( "\"" ); + } + out.write( ">" ); + + // Write child elements and text + boolean hasChildren = false; + NodeList children = element.getChildNodes(); + for( int i = 0; i < children.getLength(); i++ ) + { + Node child = children.item( i ); + + switch ( child.getNodeType() ) + { + + case Node.ELEMENT_NODE: + if( !hasChildren ) + { + out.write( lSep ); + hasChildren = true; + } + write( ( Element )child, out, indent + 1, indentWith ); + break; + case Node.TEXT_NODE: + out.write( encode( child.getNodeValue() ) ); + break; + case Node.CDATA_SECTION_NODE: + out.write( "" ); + break; + case Node.ENTITY_REFERENCE_NODE: + out.write( '&' ); + out.write( child.getNodeName() ); + out.write( ';' ); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + out.write( " 0 ) + { + out.write( ' ' ); + out.write( data ); + } + out.write( "?>" ); + break; + } + } + + // If we had child elements, we need to indent before we close + // the element, otherwise we're on the same line and don't need + // to indent + if( hasChildren ) + { + for( int i = 0; i < indent; i++ ) + { + out.write( indentWith ); + } + } + + // Write element close + out.write( "" ); + out.write( lSep ); + out.flush(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileNameMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileNameMapper.java new file mode 100644 index 000000000..33ae65096 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileNameMapper.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Interface to be used by SourceFileScanner.

          + * + * Used to find the name of the target file(s) corresponding to a source file. + *

          + * + * The rule by which the file names are transformed is specified via the setFrom + * and setTo methods. The exact meaning of these is implementation dependent. + *

          + * + * @author
          Stefan Bodewig + */ +public interface FileNameMapper +{ + + /** + * Sets the from part of the transformation rule. + * + * @param from The new From value + */ + void setFrom( String from ); + + /** + * Sets the to part of the transformation rule. + * + * @param to The new To value + */ + void setTo( String to ); + + /** + * Returns an array containing the target filename(s) for the given source + * file.

          + * + * if the given rule doesn't apply to the source file, implementation must + * return null. SourceFileScanner will then omit the source file in + * question.

          + * + * @param sourceFileName the name of the source file relative to some given + * basedirectory. + * @return Description of the Returned Value + */ + String[] mapFileName( String sourceFileName ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileUtils.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileUtils.java new file mode 100644 index 000000000..a27ab6f63 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileUtils.java @@ -0,0 +1,674 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.text.DecimalFormat; +import java.util.Random; +import java.util.Stack; +import java.util.StringTokenizer; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FilterSetCollection; + +/** + * This class also encapsulates methods which allow Files to be refered to using + * abstract path names which are translated to native system file paths at + * runtime as well as copying files or setting there last modification time. + * + * @author duncan@x180.com + * @author Conor MacNeill + * @author Stefan Bodewig + * @version $Revision$ + */ + +public class FileUtils +{ + private static Random rand = new Random( System.currentTimeMillis() ); + private static Object lockReflection = new Object(); + private static java.lang.reflect.Method setLastModified = null; + + /** + * Empty constructor. + */ + protected FileUtils() { } + + /** + * Factory method. + * + * @return Description of the Returned Value + */ + public static FileUtils newFileUtils() + { + return new FileUtils(); + } + + /** + * Calls File.setLastModified(long time) in a Java 1.1 compatible way. + * + * @param file The new FileLastModified value + * @param time The new FileLastModified value + * @exception BuildException Description of Exception + */ + public void setFileLastModified( File file, long time ) + throws BuildException + { + if( Project.getJavaVersion() == Project.JAVA_1_1 ) + { + return; + } + Long[] times = new Long[1]; + if( time < 0 ) + { + times[0] = new Long( System.currentTimeMillis() ); + } + else + { + times[0] = new Long( time ); + } + + try + { + getSetLastModified().invoke( file, times ); + } + catch( java.lang.reflect.InvocationTargetException ite ) + { + Throwable nested = ite.getTargetException(); + throw new BuildException( "Exception setting the modification time " + + "of " + file, nested ); + } + catch( Throwable other ) + { + throw new BuildException( "Exception setting the modification time " + + "of " + file, other ); + } + } + + /** + * Emulation of File.getParentFile for JDK 1.1 + * + * @param f Description of Parameter + * @return The ParentFile value + * @since 1.10 + */ + public File getParentFile( File f ) + { + if( f != null ) + { + String p = f.getParent(); + if( p != null ) + { + return new File( p ); + } + } + return null; + } + + /** + * Compares the contents of two files. + * + * @param f1 Description of Parameter + * @param f2 Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + * @since 1.9 + */ + public boolean contentEquals( File f1, File f2 ) + throws IOException + { + if( f1.exists() != f2.exists() ) + { + return false; + } + + if( !f1.exists() ) + { + // two not existing files are equal + return true; + } + + if( f1.isDirectory() || f2.isDirectory() ) + { + // don't want to compare directory contents for now + return false; + } + + InputStream in1 = null; + InputStream in2 = null; + try + { + in1 = new BufferedInputStream( new FileInputStream( f1 ) ); + in2 = new BufferedInputStream( new FileInputStream( f2 ) ); + + int expectedByte = in1.read(); + while( expectedByte != -1 ) + { + if( expectedByte != in2.read() ) + { + return false; + } + expectedByte = in1.read(); + } + if( in2.read() != -1 ) + { + return false; + } + return true; + } + finally + { + if( in1 != null ) + { + try + { + in1.close(); + } + catch( IOException e ) + {} + } + if( in2 != null ) + { + try + { + in2.close(); + } + catch( IOException e ) + {} + } + } + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), null, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters, + boolean overwrite ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, + overwrite, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, + overwrite, preserveLastModified ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile ) + throws IOException + { + copyFile( sourceFile, destFile, null, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters ) + throws IOException + { + copyFile( sourceFile, destFile, filters, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters, + boolean overwrite ) + throws IOException + { + copyFile( sourceFile, destFile, filters, overwrite, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + + if( overwrite || !destFile.exists() || + destFile.lastModified() < sourceFile.lastModified() ) + { + + if( destFile.exists() && destFile.isFile() ) + { + destFile.delete(); + } + + // ensure that parent dir of dest file exists! + // not using getParentFile method to stay 1.1 compat + File parent = getParentFile( destFile ); + if( !parent.exists() ) + { + parent.mkdirs(); + } + + if( filters != null && filters.hasFilters() ) + { + BufferedReader in = new BufferedReader( new FileReader( sourceFile ) ); + BufferedWriter out = new BufferedWriter( new FileWriter( destFile ) ); + + int length; + String newline = null; + String line = in.readLine(); + while( line != null ) + { + if( line.length() == 0 ) + { + out.newLine(); + } + else + { + newline = filters.replaceTokens( line ); + out.write( newline ); + out.newLine(); + } + line = in.readLine(); + } + + out.close(); + in.close(); + } + else + { + FileInputStream in = new FileInputStream( sourceFile ); + FileOutputStream out = new FileOutputStream( destFile ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + + in.close(); + out.close(); + } + + if( preserveLastModified ) + { + setFileLastModified( destFile, sourceFile.lastModified() ); + } + } + } + + /** + * Create a temporary file in a given directory.

          + * + * The file denoted by the returned abstract pathname did not exist before + * this method was invoked, any subsequent invocation of this method will + * yield a different file name.

          + * + * This method is different to File.createTempFile of JDK 1.2 as it doesn't + * create the file itself and doesn't use platform specific temporary + * directory when the parentDir attribute is null.

          + * + * @param parentDir Directory to create the temporary file in - current + * working directory will be assumed if this parameter is null. + * @param prefix Description of Parameter + * @param suffix Description of Parameter + * @return Description of the Returned Value + * @since 1.8 + */ + public File createTempFile( String prefix, String suffix, File parentDir ) + { + + File result = null; + String parent = null; + if( parentDir != null ) + { + parent = parentDir.getPath(); + } + DecimalFormat fmt = new DecimalFormat( "#####" ); + synchronized( rand ) + { + do + { + result = new File( parent, + prefix + fmt.format( rand.nextInt() ) + + suffix ); + }while ( result.exists() ); + } + return result; + } + + /** + * "normalize" the given absolute path.

          + * + * This includes: + *

            + *
          • Uppercase the drive letter if there is one.
          • + *
          • Remove redundant slashes after the drive spec.
          • + *
          • resolve all ./, .\, ../ and ..\ sequences.
          • + *
          • DOS style paths that start with a drive letter will have \ as the + * separator.
          • + *
          + * + * + * @param path Description of Parameter + * @return Description of the Returned Value + * @throws java.lang.NullPointerException if the file path is equal to null. + */ + public File normalize( String path ) + { + String orig = path; + + path = path.replace( '/', File.separatorChar ) + .replace( '\\', File.separatorChar ); + + // make sure we are dealing with an absolute path + if( !path.startsWith( File.separator ) && + !( path.length() >= 2 && + Character.isLetter( path.charAt( 0 ) ) && + path.charAt( 1 ) == ':' ) + ) + { + String msg = path + " is not an absolute path"; + throw new BuildException( msg ); + } + + boolean dosWithDrive = false; + String root = null; + // Eliminate consecutive slashes after the drive spec + if( path.length() >= 2 && + Character.isLetter( path.charAt( 0 ) ) && + path.charAt( 1 ) == ':' ) + { + + dosWithDrive = true; + + char[] ca = path.replace( '/', '\\' ).toCharArray(); + StringBuffer sb = new StringBuffer(); + sb.append( Character.toUpperCase( ca[0] ) ).append( ':' ); + + for( int i = 2; i < ca.length; i++ ) + { + if( ( ca[i] != '\\' ) || + ( ca[i] == '\\' && ca[i - 1] != '\\' ) + ) + { + sb.append( ca[i] ); + } + } + + path = sb.toString().replace( '\\', File.separatorChar ); + if( path.length() == 2 ) + { + root = path; + path = ""; + } + else + { + root = path.substring( 0, 3 ); + path = path.substring( 3 ); + } + + } + else + { + if( path.length() == 1 ) + { + root = File.separator; + path = ""; + } + else if( path.charAt( 1 ) == File.separatorChar ) + { + // UNC drive + root = File.separator + File.separator; + path = path.substring( 2 ); + } + else + { + root = File.separator; + path = path.substring( 1 ); + } + } + + Stack s = new Stack(); + s.push( root ); + StringTokenizer tok = new StringTokenizer( path, File.separator ); + while( tok.hasMoreTokens() ) + { + String thisToken = tok.nextToken(); + if( ".".equals( thisToken ) ) + { + continue; + } + else if( "..".equals( thisToken ) ) + { + if( s.size() < 2 ) + { + throw new BuildException( "Cannot resolve path " + orig ); + } + else + { + s.pop(); + } + } + else + {// plain component + s.push( thisToken ); + } + } + + StringBuffer sb = new StringBuffer(); + for( int i = 0; i < s.size(); i++ ) + { + if( i > 1 ) + { + // not before the filesystem root and not after it, since root + // already contains one + sb.append( File.separatorChar ); + } + sb.append( s.elementAt( i ) ); + } + + path = sb.toString(); + if( dosWithDrive ) + { + path = path.replace( '/', '\\' ); + } + return new File( path ); + } + + /** + * Interpret the filename as a file relative to the given file - unless the + * filename already represents an absolute filename. + * + * @param file the "reference" file for relative paths. This instance must + * be an absolute file and must not contain "./" or + * "../" sequences (same for \ instead of /). If it is null, + * this call is equivalent to new java.io.File(filename). + * @param filename a file name + * @return an absolute file that doesn't contain "./" or + * "../" sequences and uses the correct separator for the + * current platform. + */ + public File resolveFile( File file, String filename ) + { + filename = filename.replace( '/', File.separatorChar ) + .replace( '\\', File.separatorChar ); + + // deal with absolute files + if( filename.startsWith( File.separator ) || + ( filename.length() >= 2 && + Character.isLetter( filename.charAt( 0 ) ) && + filename.charAt( 1 ) == ':' ) + ) + { + return normalize( filename ); + } + + if( file == null ) + { + return new File( filename ); + } + + File helpFile = new File( file.getAbsolutePath() ); + StringTokenizer tok = new StringTokenizer( filename, File.separator ); + while( tok.hasMoreTokens() ) + { + String part = tok.nextToken(); + if( part.equals( ".." ) ) + { + helpFile = getParentFile( helpFile ); + if( helpFile == null ) + { + String msg = "The file or path you specified (" + + filename + ") is invalid relative to " + + file.getPath(); + throw new BuildException( msg ); + } + } + else if( part.equals( "." ) ) + { + // Do nothing here + } + else + { + helpFile = new File( helpFile, part ); + } + } + + return new File( helpFile.getAbsolutePath() ); + } + + /** + * see whether we have a setLastModified method in File and return it. + * + * @return The SetLastModified value + */ + protected final Method getSetLastModified() + { + if( Project.getJavaVersion() == Project.JAVA_1_1 ) + { + return null; + } + if( setLastModified == null ) + { + synchronized( lockReflection ) + { + if( setLastModified == null ) + { + try + { + setLastModified = + java.io.File.class.getMethod( "setLastModified", + new Class[]{Long.TYPE} ); + } + catch( NoSuchMethodException nse ) + { + throw new BuildException( "File.setlastModified not in JDK > 1.1?", + nse ); + } + } + } + } + return setLastModified; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/FlatFileNameMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FlatFileNameMapper.java new file mode 100644 index 000000000..37c6e5bad --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FlatFileNameMapper.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the source file name + * without any leading directory information.

          + * + * This is the default FileNameMapper for the copy and move tasks if the flatten + * attribute has been set.

          + * + * @author Stefan Bodewig + */ +public class FlatFileNameMapper implements FileNameMapper +{ + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Ignored. + * + * @param to The new To value + */ + public void setTo( String to ) { } + + /** + * Returns an one-element array containing the source file name without any + * leading directory information. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return new String[]{new java.io.File( sourceFileName ).getName()}; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/GlobPatternMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/GlobPatternMapper.java new file mode 100644 index 000000000..f9cb24277 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/GlobPatternMapper.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that does simple wildcard pattern + * replacements.

          + * + * This does simple translations like *.foo -> *.bar where the prefix to .foo + * will be left unchanged. It only handles a single * character, use regular + * expressions for more complicated situations.

          + * + * This is one of the more useful Mappers, it is used by javac for example.

          + * + * @author Stefan Bodewig + */ +public class GlobPatternMapper implements FileNameMapper +{ + /** + * Part of "from" pattern before the *. + */ + protected String fromPrefix = null; + + /** + * Part of "from" pattern after the *. + */ + protected String fromPostfix = null; + + /** + * Part of "to" pattern before the *. + */ + protected String toPrefix = null; + + /** + * Part of "to" pattern after the *. + */ + protected String toPostfix = null; + + /** + * Length of the postfix ("from" pattern). + */ + protected int postfixLength; + + /** + * Length of the prefix ("from" pattern). + */ + protected int prefixLength; + + /** + * Sets the "from" pattern. Required. + * + * @param from The new From value + */ + public void setFrom( String from ) + { + int index = from.lastIndexOf( "*" ); + if( index == -1 ) + { + fromPrefix = from; + fromPostfix = ""; + } + else + { + fromPrefix = from.substring( 0, index ); + fromPostfix = from.substring( index + 1 ); + } + prefixLength = fromPrefix.length(); + postfixLength = fromPostfix.length(); + } + + /** + * Sets the "to" pattern. Required. + * + * @param to The new To value + */ + public void setTo( String to ) + { + int index = to.lastIndexOf( "*" ); + if( index == -1 ) + { + toPrefix = to; + toPostfix = ""; + } + else + { + toPrefix = to.substring( 0, index ); + toPostfix = to.substring( index + 1 ); + } + } + + /** + * Returns null if the source file name doesn't match the "from" + * pattern, an one-element array containing the translated file otherwise. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + if( fromPrefix == null + || !sourceFileName.startsWith( fromPrefix ) + || !sourceFileName.endsWith( fromPostfix ) ) + { + return null; + } + return new String[]{toPrefix + + extractVariablePart( sourceFileName ) + + toPostfix}; + } + + /** + * Returns the part of the given string that matches the * in the + * "from" pattern. + * + * @param name Description of Parameter + * @return Description of the Returned Value + */ + protected String extractVariablePart( String name ) + { + return name.substring( prefixLength, + name.length() - postfixLength ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/IdentityMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/IdentityMapper.java new file mode 100644 index 000000000..a93007e83 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/IdentityMapper.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the source file name. + *

          + * + * This is the default FileNameMapper for the copy and move tasks.

          + * + * @author Stefan Bodewig + */ +public class IdentityMapper implements FileNameMapper +{ + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Ignored. + * + * @param to The new To value + */ + public void setTo( String to ) { } + + /** + * Returns an one-element array containing the source file name. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return new String[]{sourceFileName}; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/MergingMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/MergingMapper.java new file mode 100644 index 000000000..d9c0b866e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/MergingMapper.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the same target file + * name.

          + * + * This is the default FileNameMapper for the archiving tasks and uptodate.

          + * + * @author Stefan Bodewig + */ +public class MergingMapper implements FileNameMapper +{ + protected String[] mergedFile = null; + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Sets the name of the merged file. + * + * @param to The new To value + */ + public void setTo( String to ) + { + mergedFile = new String[]{to}; + } + + /** + * Returns an one-element array containing the file name set via setTo. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return mergedFile; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/RegexpPatternMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/RegexpPatternMapper.java new file mode 100644 index 000000000..df29877a4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/RegexpPatternMapper.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.regexp.RegexpMatcher; +import org.apache.tools.ant.util.regexp.RegexpMatcherFactory; + +/** + * Implementation of FileNameMapper that does regular expression replacements. + * + * @author Stefan Bodewig + */ +public class RegexpPatternMapper implements FileNameMapper +{ + protected RegexpMatcher reg = null; + protected char[] to = null; + protected StringBuffer result = new StringBuffer(); + + public RegexpPatternMapper() + throws BuildException + { + reg = ( new RegexpMatcherFactory() ).newRegexpMatcher(); + } + + /** + * Sets the "from" pattern. Required. + * + * @param from The new From value + * @exception BuildException Description of Exception + */ + public void setFrom( String from ) + throws BuildException + { + try + { + reg.setPattern( from ); + } + catch( NoClassDefFoundError e ) + { + // depending on the implementation the actual RE won't + // get instantiated in the constructor. + throw new BuildException( "Cannot load regular expression matcher", + e ); + } + } + + /** + * Sets the "to" pattern. Required. + * + * @param to The new To value + */ + public void setTo( String to ) + { + this.to = to.toCharArray(); + } + + /** + * Returns null if the source file name doesn't match the "from" + * pattern, an one-element array containing the translated file otherwise. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + if( reg == null || to == null + || !reg.matches( sourceFileName ) ) + { + return null; + } + return new String[]{replaceReferences( sourceFileName )}; + } + + /** + * Replace all backreferences in the to pattern with the matched groups of + * the source. + * + * @param source Description of Parameter + * @return Description of the Returned Value + */ + protected String replaceReferences( String source ) + { + Vector v = reg.getGroups( source ); + + result.setLength( 0 ); + for( int i = 0; i < to.length; i++ ) + { + if( to[i] == '\\' ) + { + if( ++i < to.length ) + { + int value = Character.digit( to[i], 10 ); + if( value > -1 ) + { + result.append( ( String )v.elementAt( value ) ); + } + else + { + result.append( to[i] ); + } + } + else + { + // XXX - should throw an exception instead? + result.append( '\\' ); + } + } + else + { + result.append( to[i] ); + } + } + return result.toString(); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/SourceFileScanner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/SourceFileScanner.java new file mode 100644 index 000000000..4106357ac --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/SourceFileScanner.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; + +/** + * Utility class that collects the functionality of the various scanDir methods + * that have been scattered in several tasks before.

          + * + * The only method returns an array of source files. The array is a subset of + * the files given as a parameter and holds only those that are newer than their + * corresponding target files.

          + * + * @author Stefan Bodewig + */ +public class SourceFileScanner +{ + + protected Task task; + + private FileUtils fileUtils; + + /** + * @param task The task we should log messages through + */ + public SourceFileScanner( Task task ) + { + this.task = task; + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Restrict the given set of files to those that are newer than their + * corresponding target files. + * + * @param files the original set of files + * @param srcDir all files are relative to this directory + * @param destDir target files live here. if null file names returned by the + * mapper are assumed to be absolute. + * @param mapper knows how to construct a target file names from source file + * names. + * @return Description of the Returned Value + */ + public String[] restrict( String[] files, File srcDir, File destDir, + FileNameMapper mapper ) + { + + long now = ( new java.util.Date() ).getTime(); + StringBuffer targetList = new StringBuffer(); + + /* + * If we're on Windows, we have to munge the time up to 2 secs to + * be able to check file modification times. + * (Windows has a max resolution of two secs for modification times) + * Actually this is a feature of the FAT file system, NTFS does + * not have it, so if we could reliably passively test for an NTFS + * file systems we could turn this off... + */ + if( Os.isFamily( "windows" ) ) + { + now += 2000; + } + + Vector v = new Vector(); + for( int i = 0; i < files.length; i++ ) + { + + String[] targets = mapper.mapFileName( files[i] ); + if( targets == null || targets.length == 0 ) + { + task.log( files[i] + " skipped - don\'t know how to handle it", + Project.MSG_VERBOSE ); + continue; + } + + File src = fileUtils.resolveFile( srcDir, files[i] ); + + if( src.lastModified() > now ) + { + task.log( "Warning: " + files[i] + " modified in the future.", + Project.MSG_WARN ); + } + + boolean added = false; + targetList.setLength( 0 ); + for( int j = 0; !added && j < targets.length; j++ ) + { + File dest = fileUtils.resolveFile( destDir, targets[j] ); + + if( !dest.exists() ) + { + task.log( files[i] + " added as " + dest.getAbsolutePath() + " doesn\'t exist.", + Project.MSG_VERBOSE ); + v.addElement( files[i] ); + added = true; + } + else if( src.lastModified() > dest.lastModified() ) + { + task.log( files[i] + " added as " + dest.getAbsolutePath() + " is outdated.", + Project.MSG_VERBOSE ); + v.addElement( files[i] ); + added = true; + } + else + { + if( targetList.length() > 0 ) + { + targetList.append( ", " ); + } + targetList.append( dest.getAbsolutePath() ); + } + } + + if( !added ) + { + task.log( files[i] + " omitted as " + targetList.toString() + + ( targets.length == 1 ? " is" : " are " ) + + " up to date.", Project.MSG_VERBOSE ); + } + + } + String[] result = new String[v.size()]; + v.copyInto( result ); + return result; + } + + /** + * Convinience layer on top of restrict that returns the source files as + * File objects (containing absolute paths if srcDir is absolute). + * + * @param files Description of Parameter + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param mapper Description of Parameter + * @return Description of the Returned Value + */ + public File[] restrictAsFiles( String[] files, File srcDir, File destDir, + FileNameMapper mapper ) + { + String[] res = restrict( files, srcDir, destDir, mapper ); + File[] result = new File[res.length]; + for( int i = 0; i < res.length; i++ ) + { + result[i] = new File( srcDir, res[i] ); + } + return result; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/StringUtils.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/StringUtils.java new file mode 100644 index 000000000..6881490c0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/StringUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * A set of helper methods related to string manipulation. + * + * @author Stephane Bailliez + */ +public final class StringUtils +{ + + /** + * the line separator for this OS + */ + public final static String LINE_SEP = System.getProperty( "line.separator" ); + + /** + * Convenient method to retrieve the full stacktrace from a given exception. + * + * @param t the exception to get the stacktrace from. + * @return the stacktrace from the given exception. + */ + public static String getStackTrace( Throwable t ) + { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter( sw, true ); + t.printStackTrace( pw ); + pw.flush(); + pw.close(); + return sw.toString(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Dependencies.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Dependencies.java new file mode 100644 index 000000000..b7808ff0c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Dependencies.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.depend; +import java.io.*; +import java.util.*; +import org.apache.bcel.*; +import org.apache.bcel.classfile.*; + + +public class Dependencies implements Visitor +{ + private boolean verbose = false; + private Set dependencies = new HashSet(); + private ConstantPool constantPool; + + private JavaClass javaClass; + + public static void applyFilter( Collection collection, Filter filter ) + { + Iterator i = collection.iterator(); + while( i.hasNext() ) + { + Object next = i.next(); + if( !filter.accept( next ) ) + { + i.remove(); + } + } + } + + public static void main( String[] args ) + { + try + { + Dependencies visitor = new Dependencies(); + + Set set = new TreeSet(); + Set newSet = new HashSet(); + + int o = 0; + String arg = null; + if( "-base".equals( args[0] ) ) + { + arg = args[1]; + if( !arg.endsWith( File.separator ) ) + { + arg = arg + File.separator; + } + o = 2; + } + final String base = arg; + + for( int i = o; i < args.length; i++ ) + { + String fileName = args[i].substring( 0, args[i].length() - ".class".length() ); + if( base != null && fileName.startsWith( base ) ) + fileName = fileName.substring( base.length() ); + newSet.add( fileName ); + } + set.addAll( newSet ); + + do + { + Iterator i = newSet.iterator(); + while( i.hasNext() ) + { + String fileName = i.next() + ".class"; + + if( base != null ) + { + fileName = base + fileName; + } + + JavaClass javaClass = new ClassParser( fileName ).parse(); + javaClass.accept( visitor ); + } + newSet.clear(); + newSet.addAll( visitor.getDependencies() ); + visitor.clearDependencies(); + + applyFilter( newSet, + new Filter() + { + public boolean accept( Object object ) + { + String fileName = object + ".class"; + if( base != null ) + fileName = base + fileName; + return new File( fileName ).exists(); + } + } ); + newSet.removeAll( set ); + set.addAll( newSet ); + }while ( newSet.size() > 0 ); + + Iterator i = set.iterator(); + while( i.hasNext() ) + { + System.out.println( i.next() ); + } + } + catch( Exception e ) + { + System.err.println( e.getMessage() ); + e.printStackTrace( System.err ); + } + } + + public Set getDependencies() + { + return dependencies; + } + + public void clearDependencies() + { + dependencies.clear(); + } + + public void visitCode( Code obj ) { } + + public void visitCodeException( CodeException obj ) { } + + public void visitConstantClass( ConstantClass obj ) + { + if( verbose ) + { + System.out.println( "visit ConstantClass" ); + System.out.println( obj.getConstantValue( constantPool ) ); + } + dependencies.add( "" + obj.getConstantValue( constantPool ) ); + } + + public void visitConstantDouble( ConstantDouble obj ) { } + + public void visitConstantFieldref( ConstantFieldref obj ) { } + + public void visitConstantFloat( ConstantFloat obj ) { } + + public void visitConstantInteger( ConstantInteger obj ) { } + + public void visitConstantInterfaceMethodref( ConstantInterfaceMethodref obj ) { } + + public void visitConstantLong( ConstantLong obj ) { } + + public void visitConstantMethodref( ConstantMethodref obj ) { } + + public void visitConstantNameAndType( ConstantNameAndType obj ) { } + + public void visitConstantPool( ConstantPool obj ) + { + if( verbose ) + System.out.println( "visit ConstantPool" ); + this.constantPool = obj; + + // visit constants + for( int idx = 0; idx < constantPool.getLength(); idx++ ) + { + Constant c = constantPool.getConstant( idx ); + if( c != null ) + { + c.accept( this ); + } + } + } + + public void visitConstantString( ConstantString obj ) { } + + public void visitConstantUtf8( ConstantUtf8 obj ) { } + + public void visitConstantValue( ConstantValue obj ) { } + + public void visitDeprecated( Deprecated obj ) { } + + public void visitExceptionTable( ExceptionTable obj ) { } + + public void visitField( Field obj ) + { + if( verbose ) + { + System.out.println( "visit Field" ); + System.out.println( obj.getSignature() ); + } + addClasses( obj.getSignature() ); + } + + public void visitInnerClass( InnerClass obj ) { } + + public void visitInnerClasses( InnerClasses obj ) { } + + public void visitJavaClass( JavaClass obj ) + { + if( verbose ) + { + System.out.println( "visit JavaClass" ); + } + + this.javaClass = obj; + dependencies.add( javaClass.getClassName().replace( '.', '/' ) ); + + // visit constant pool + javaClass.getConstantPool().accept( this ); + + // visit fields + Field[] fields = obj.getFields(); + for( int i = 0; i < fields.length; i++ ) + { + fields[i].accept( this ); + } + + // visit methods + Method[] methods = obj.getMethods(); + for( int i = 0; i < methods.length; i++ ) + { + methods[i].accept( this ); + } + } + + public void visitLineNumber( LineNumber obj ) { } + + public void visitLineNumberTable( LineNumberTable obj ) { } + + public void visitLocalVariable( LocalVariable obj ) { } + + public void visitLocalVariableTable( LocalVariableTable obj ) { } + + public void visitMethod( Method obj ) + { + if( verbose ) + { + System.out.println( "visit Method" ); + System.out.println( obj.getSignature() ); + } + String signature = obj.getSignature(); + int pos = signature.indexOf( ")" ); + addClasses( signature.substring( 1, pos ) ); + addClasses( signature.substring( pos + 1 ) ); + } + + public void visitSourceFile( SourceFile obj ) { } + + public void visitStackMap( StackMap obj ) { } + + public void visitStackMapEntry( StackMapEntry obj ) { } + + public void visitSynthetic( Synthetic obj ) { } + + public void visitUnknown( Unknown obj ) { } + + void addClass( String string ) + { + int pos = string.indexOf( 'L' ); + if( pos != -1 ) + { + dependencies.add( string.substring( pos + 1 ) ); + } + } + + void addClasses( String string ) + { + StringTokenizer tokens = new StringTokenizer( string, ";" ); + while( tokens.hasMoreTokens() ) + { + addClass( tokens.nextToken() ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Filter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Filter.java new file mode 100644 index 000000000..2cd26d2aa --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Filter.java @@ -0,0 +1,14 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.depend; +import java.util.*; + +public interface Filter +{ + boolean accept( Object object ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java new file mode 100644 index 000000000..1421fb167 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for Jakarta-ORO. + * + * @author Stefan Bodewig + * @author Matthew Inger + */ +public class JakartaOroMatcher implements RegexpMatcher +{ + protected final Perl5Compiler compiler = new Perl5Compiler(); + protected final Perl5Matcher matcher = new Perl5Matcher(); + + private String pattern; + + public JakartaOroMatcher() { } + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String input, int options ) + throws BuildException + { + if( !matches( input, options ) ) + { + return null; + } + Vector v = new Vector(); + MatchResult mr = matcher.getMatch(); + int cnt = mr.groups(); + for( int i = 0; i < cnt; i++ ) + { + v.addElement( mr.group( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return this.pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + Pattern p = getCompiledPattern( options ); + return matcher.contains( input, p ); + } + + /** + * Get a compiled representation of the regexp pattern + * + * @param options Description of Parameter + * @return The CompiledPattern value + * @exception BuildException Description of Exception + */ + protected Pattern getCompiledPattern( int options ) + throws BuildException + { + try + { + // compute the compiler options based on the input options first + Pattern p = compiler.compile( pattern, getCompilerOptions( options ) ); + return p; + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = Perl5Compiler.DEFAULT_MASK; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + { + cOptions |= Perl5Compiler.CASE_INSENSITIVE_MASK; + } + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + { + cOptions |= Perl5Compiler.MULTILINE_MASK; + } + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + { + cOptions |= Perl5Compiler.SINGLELINE_MASK; + } + + return cOptions; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java new file mode 100644 index 000000000..88e8f31f6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.oro.text.regex.Perl5Substitution; +import org.apache.oro.text.regex.Substitution; +import org.apache.oro.text.regex.Util; +import org.apache.tools.ant.BuildException; + + +/** + * Regular expression implementation using the Jakarta Oro package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaOroRegexp extends JakartaOroMatcher implements Regexp +{ + + public JakartaOroRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + // translate \1 to $1 so that the Perl5Substitution will work + StringBuffer subst = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + subst.append( "$" ).append( value ); + } + else + { + subst.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + subst.append( '\\' ); + } + } + else + { + subst.append( c ); + } + } + + // Do the substitution + Substitution s = + new Perl5Substitution( subst.toString(), + Perl5Substitution.INTERPOLATE_ALL ); + return Util.substitute( matcher, + getCompiledPattern( options ), + s, + input, + getSubsOptions( options ) ); + } + + protected int getSubsOptions( int options ) + { + boolean replaceAll = RegexpUtil.hasFlag( options, REPLACE_ALL ); + int subsOptions = 1; + if( replaceAll ) + { + subsOptions = Util.SUBSTITUTE_ALL; + } + return subsOptions; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java new file mode 100644 index 000000000..a51a8ff3a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.regexp.RE; +import org.apache.regexp.RESyntaxException; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for Jakarta-Regexp. + * + * @author Stefan Bodewig + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaRegexpMatcher implements RegexpMatcher +{ + + private String pattern; + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + public Vector getGroups( String input, int options ) + throws BuildException + { + RE reg = getCompiledPattern( options ); + if( !matches( input, reg ) ) + { + return null; + } + Vector v = new Vector(); + int cnt = reg.getParenCount(); + for( int i = 0; i < cnt; i++ ) + { + v.addElement( reg.getParen( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + return matches( input, getCompiledPattern( options ) ); + } + + protected RE getCompiledPattern( int options ) + throws BuildException + { + int cOptions = getCompilerOptions( options ); + try + { + RE reg = new RE( pattern ); + reg.setMatchFlags( cOptions ); + return reg; + } + catch( RESyntaxException e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = RE.MATCH_NORMAL; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + cOptions |= RE.MATCH_CASEINDEPENDENT; + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + cOptions |= RE.MATCH_MULTILINE; + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + cOptions |= RE.MATCH_SINGLELINE; + + return cOptions; + } + + private boolean matches( String input, RE reg ) + { + return reg.match( input ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java new file mode 100644 index 000000000..3f41e50e5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.regexp.RE; +import org.apache.tools.ant.BuildException; + +/** + * Regular expression implementation using the Jakarta Regexp package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaRegexpRegexp extends JakartaRegexpMatcher implements Regexp +{ + + public JakartaRegexpRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + Vector v = getGroups( input, options ); + + // replace \1 with the corresponding group + StringBuffer result = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + result.append( ( String )v.elementAt( value ) ); + } + else + { + result.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + result.append( '\\' ); + } + } + else + { + result.append( c ); + } + } + argument = result.toString(); + + RE reg = getCompiledPattern( options ); + int sOptions = getSubsOptions( options ); + return reg.subst( input, argument, sOptions ); + } + + protected int getSubsOptions( int options ) + { + int subsOptions = RE.REPLACE_FIRSTONLY; + if( RegexpUtil.hasFlag( options, REPLACE_ALL ) ) + subsOptions = RE.REPLACE_ALL; + return subsOptions; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java new file mode 100644 index 000000000..7c486420e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for the built-in regexp matcher of JDK 1.4. + * + * @author Stefan Bodewig + * @author Matthew Inger + * mattinger@mindless.com + */ +public class Jdk14RegexpMatcher implements RegexpMatcher +{ + + private String pattern; + + public Jdk14RegexpMatcher() { } + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String input, int options ) + throws BuildException + { + Pattern p = getCompiledPattern( options ); + Matcher matcher = p.matcher( input ); + if( !matcher.find() ) + { + return null; + } + Vector v = new Vector(); + int cnt = matcher.groupCount(); + for( int i = 0; i <= cnt; i++ ) + { + v.addElement( matcher.group( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + try + { + Pattern p = getCompiledPattern( options ); + return p.matcher( input ).find(); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + protected Pattern getCompiledPattern( int options ) + throws BuildException + { + int cOptions = getCompilerOptions( options ); + try + { + Pattern p = Pattern.compile( this.pattern, cOptions ); + return p; + } + catch( PatternSyntaxException e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = 0; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + cOptions |= Pattern.CASE_INSENSITIVE; + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + cOptions |= Pattern.MULTILINE; + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + cOptions |= Pattern.DOTALL; + + return cOptions; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java new file mode 100644 index 000000000..23998f0e6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.tools.ant.BuildException; + + +/** + * Regular expression implementation using the JDK 1.4 regular expression + * package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class Jdk14RegexpRegexp extends Jdk14RegexpMatcher implements Regexp +{ + + public Jdk14RegexpRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + // translate \1 to $(1) so that the Matcher will work + StringBuffer subst = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + subst.append( "$" ).append( value ); + } + else + { + subst.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + subst.append( '\\' ); + } + } + else + { + subst.append( c ); + } + } + argument = subst.toString(); + + int sOptions = getSubsOptions( options ); + Pattern p = getCompiledPattern( options ); + StringBuffer sb = new StringBuffer(); + + Matcher m = p.matcher( input ); + if( RegexpUtil.hasFlag( sOptions, REPLACE_ALL ) ) + { + sb.append( m.replaceAll( argument ) ); + } + else + { + boolean res = m.find(); + if( res ) + { + m.appendReplacement( sb, argument ); + m.appendTail( sb ); + } + else + { + sb.append( input ); + } + } + + return sb.toString(); + } + + protected int getSubsOptions( int options ) + { + int subsOptions = REPLACE_FIRST; + if( RegexpUtil.hasFlag( options, REPLACE_ALL ) ) + subsOptions = REPLACE_ALL; + return subsOptions; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Regexp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Regexp.java new file mode 100644 index 000000000..2acaeda47 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Regexp.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; + +/** + * Interface which represents a regular expression, and the operations that can + * be performed on it. + * + * @author Matthew Inger + */ +public interface Regexp extends RegexpMatcher +{ + + /** + * Replace only the first occurance of the regular expression + */ + int REPLACE_FIRST = 0x00000001; + + /** + * Replace all occurances of the regular expression + */ + int REPLACE_ALL = 0x00000010; + + /** + * Perform a substitution on the regular expression. + * + * @param input The string to substitute on + * @param argument The string which defines the substitution + * @param options The list of options for the match and replace. See the + * MATCH_ and REPLACE_ constants above. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + String substitute( String input, String argument, int options ) + throws BuildException; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpFactory.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpFactory.java new file mode 100644 index 000000000..559bdf958 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpFactory.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Regular expression factory, which will create Regexp objects. The actual + * implementation class depends on the System or Ant Property: ant.regexp.regexpimpl + * . + * + * @author Matthew Inger + * mattinger@mindless.com + * @version $Revision$ + */ +public class RegexpFactory extends RegexpMatcherFactory +{ + public RegexpFactory() { } + + /** + * Create a new regular expression matcher instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Regexp newRegexp() + throws BuildException + { + return ( Regexp )newRegexp( null ); + } + + /** + * Create a new regular expression matcher instance. + * + * @param p Project whose ant.regexp.regexpimpl property will be used. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Regexp newRegexp( Project p ) + throws BuildException + { + String systemDefault = null; + if( p == null ) + { + systemDefault = System.getProperty( "ant.regexp.regexpimpl" ); + } + else + { + systemDefault = ( String )p.getProperties().get( "ant.regexp.regexpimpl" ); + } + + if( systemDefault != null ) + { + return createRegexpInstance( systemDefault ); + // XXX should we silently catch possible exceptions and try to + // load a different implementation? + } + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp" ); + } + catch( BuildException be ) + {} + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.JakartaOroRegexp" ); + } + catch( BuildException be ) + {} + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.JakartaRegexpRegexp" ); + } + catch( BuildException be ) + {} + + throw new BuildException( "No supported regular expression matcher found" ); + } + + /** + * Wrapper over {@seee RegexpMatcherFactory#createInstance createInstance} + * that ensures that we are dealing with a Regexp implementation. + * + * @param classname Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @since 1.3 + */ + protected Regexp createRegexpInstance( String classname ) + throws BuildException + { + + RegexpMatcher m = createInstance( classname ); + if( m instanceof Regexp ) + { + return ( Regexp )m; + } + else + { + throw new BuildException( classname + " doesn't implement the Regexp interface" ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcher.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcher.java new file mode 100644 index 000000000..26ef487c0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcher.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.tools.ant.BuildException; + +/** + * Interface describing a regular expression matcher. + * + * @author Stefan Bodewig + * @author Matthew Inger + */ +public interface RegexpMatcher +{ + + /** + * Default Mask (case insensitive, neither multiline nor singleline + * specified). + */ + int MATCH_DEFAULT = 0x00000000; + + /** + * Perform a case insenstive match + */ + int MATCH_CASE_INSENSITIVE = 0x00000100; + + /** + * Treat the input as a multiline input + */ + int MATCH_MULTILINE = 0x00001000; + + /** + * Treat the input as singleline input ('.' matches newline) + */ + int MATCH_SINGLELINE = 0x00010000; + + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + * @exception BuildException Description of Exception + */ + void setPattern( String pattern ) + throws BuildException; + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + * @exception BuildException Description of Exception + */ + String getPattern() + throws BuildException; + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean matches( String argument ) + throws BuildException; + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + Vector getGroups( String argument ) + throws BuildException; + + /** + * Does this regular expression match the input, given certain options + * + * @param input The string to check for a match + * @param options The list of options for the match. See the MATCH_ + * constants above. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean matches( String input, int options ) + throws BuildException; + + /** + * Get the match groups from this regular expression. The return type of the + * elements is always String. + * + * @param input The string to check for a match + * @param options The list of options for the match. See the MATCH_ + * constants above. + * @return The Groups value + * @exception BuildException Description of Exception + */ + Vector getGroups( String input, int options ) + throws BuildException; + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java new file mode 100644 index 000000000..ae7b5331a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Simple Factory Class that produces an implementation of RegexpMatcher based + * on the system property ant.regexp.matcherimpl and the classes + * available.

          + * + * In a more general framework this class would be abstract and have a static + * newInstance method.

          + * + * @author Stefan Bodewig + */ +public class RegexpMatcherFactory +{ + + public RegexpMatcherFactory() { } + + /** + * Create a new regular expression instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public RegexpMatcher newRegexpMatcher() + throws BuildException + { + return newRegexpMatcher( null ); + } + + /** + * Create a new regular expression instance. + * + * @param p Project whose ant.regexp.regexpimpl property will be used. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public RegexpMatcher newRegexpMatcher( Project p ) + throws BuildException + { + String systemDefault = null; + if( p == null ) + { + systemDefault = System.getProperty( "ant.regexp.regexpimpl" ); + } + else + { + systemDefault = ( String )p.getProperties().get( "ant.regexp.regexpimpl" ); + } + + if( systemDefault != null ) + { + return createInstance( systemDefault ); + // XXX should we silently catch possible exceptions and try to + // load a different implementation? + } + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.Jdk14RegexpMatcher" ); + } + catch( BuildException be ) + {} + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.JakartaOroMatcher" ); + } + catch( BuildException be ) + {} + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.JakartaRegexpMatcher" ); + } + catch( BuildException be ) + {} + + throw new BuildException( "No supported regular expression matcher found" ); + } + + protected RegexpMatcher createInstance( String className ) + throws BuildException + { + try + { + Class implClass = Class.forName( className ); + return ( RegexpMatcher )implClass.newInstance(); + } + catch( Throwable t ) + { + throw new BuildException( t ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpUtil.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpUtil.java new file mode 100644 index 000000000..fb1e3416a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpUtil.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; + +/** + * Regular expression utilities class which handles flag operations + * + * @author Matthew Inger + */ +public class RegexpUtil extends Object +{ + public final static boolean hasFlag( int options, int flag ) + { + return ( ( options & flag ) > 0 ); + } + + public final static int removeFlag( int options, int flag ) + { + return ( options & ( 0xFFFFFFFF - flag ) ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/bzip2/BZip2Constants.java b/proposal/myrmidon/src/main/org/apache/tools/bzip2/BZip2Constants.java new file mode 100644 index 000000000..fa26ce9fc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/bzip2/BZip2Constants.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; + +/** + * Base class for both the compress and decompress classes. Holds common arrays, + * and static data. + * + * @author Keiron Liddle + */ +public interface BZip2Constants +{ + + int baseBlockSize = 100000; + int MAX_ALPHA_SIZE = 258; + int MAX_CODE_LEN = 23; + int RUNA = 0; + int RUNB = 1; + int N_GROUPS = 6; + int G_SIZE = 50; + int N_ITERS = 4; + int MAX_SELECTORS = ( 2 + ( 900000 / G_SIZE ) ); + int NUM_OVERSHOOT_BYTES = 20; + + int rNums[] = { + 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, + 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, + 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, + 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, + 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, + 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, + 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, + 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, + 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, + 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, + 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, + 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, + 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, + 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, + 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, + 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, + 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, + 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, + 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, + 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, + 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, + 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, + 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, + 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, + 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, + 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, + 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, + 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, + 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, + 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, + 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, + 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, + 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, + 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, + 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, + 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, + 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, + 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, + 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, + 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, + 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, + 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, + 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, + 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, + 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, + 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, + 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, + 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, + 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, + 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, + 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, + 936, 638 + }; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2InputStream.java b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2InputStream.java new file mode 100644 index 000000000..d614359db --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2InputStream.java @@ -0,0 +1,939 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; +import java.io.*; + +/** + * An input stream that decompresses from the BZip2 format (without the file + * header chars) to be read as any other stream. + * + * @author Keiron Liddle + */ +public class CBZip2InputStream extends InputStream implements BZip2Constants +{ + + private final static int START_BLOCK_STATE = 1; + private final static int RAND_PART_A_STATE = 2; + private final static int RAND_PART_B_STATE = 3; + private final static int RAND_PART_C_STATE = 4; + private final static int NO_RAND_PART_A_STATE = 5; + private final static int NO_RAND_PART_B_STATE = 6; + private final static int NO_RAND_PART_C_STATE = 7; + private CRC mCrc = new CRC(); + + private boolean inUse[] = new boolean[256]; + + private char seqToUnseq[] = new char[256]; + private char unseqToSeq[] = new char[256]; + + private char selector[] = new char[MAX_SELECTORS]; + private char selectorMtf[] = new char[MAX_SELECTORS]; + + /* + * freq table collected to save a pass over the data + * during decompression. + */ + private int unzftab[] = new int[256]; + + private int limit[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int base[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int perm[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int minLens[] = new int[N_GROUPS]; + + private boolean streamEnd = false; + + private int currentChar = -1; + + private int currentState = START_BLOCK_STATE; + int rNToGo = 0; + int rTPos = 0; + int i, tPos; + + int i2, count, chPrev, ch2; + int j2; + char z; + + private boolean blockRandomised; + + /* + * always: in the range 0 .. 9. + * The current block size is 100000 * this number. + */ + private int blockSize100k; + private int bsBuff; + private int bsLive; + + private InputStream bsStream; + + private int bytesIn; + private int bytesOut; + private int computedBlockCRC, computedCombinedCRC; + + /* + * index of the last char in the block, so + * the block size == last + 1. + */ + private int last; + private char[] ll8; + private int nInUse; + + /* + * index in zptr[] of original string after sorting. + */ + private int origPtr; + + private int storedBlockCRC, storedCombinedCRC; + + private int[] tt; + + public CBZip2InputStream( InputStream zStream ) + { + ll8 = null; + tt = null; + bsSetStream( zStream ); + initialize(); + initBlock(); + setupBlock(); + } + + private static void badBGLengths() + { + cadvise(); + } + + private static void badBlockHeader() + { + cadvise(); + } + + private static void bitStreamEOF() + { + cadvise(); + } + + private static void blockOverrun() + { + cadvise(); + } + + private static void cadvise() + { + System.out.println( "CRC Error" ); + //throw new CCoruptionError(); + } + + private static void compressedStreamEOF() + { + cadvise(); + } + + private static void crcError() + { + cadvise(); + } + + public int read() + { + if( streamEnd ) + { + return -1; + } + else + { + int retChar = currentChar; + switch ( currentState ) + { + case START_BLOCK_STATE: + break; + case RAND_PART_A_STATE: + break; + case RAND_PART_B_STATE: + setupRandPartB(); + break; + case RAND_PART_C_STATE: + setupRandPartC(); + break; + case NO_RAND_PART_A_STATE: + break; + case NO_RAND_PART_B_STATE: + setupNoRandPartB(); + break; + case NO_RAND_PART_C_STATE: + setupNoRandPartC(); + break; + default: + break; + } + return retChar; + } + } + + private void setDecompressStructureSizes( int newSize100k ) + { + if( !( 0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k + && blockSize100k <= 9 ) ) + { + // throw new IOException("Invalid block size"); + } + + blockSize100k = newSize100k; + + if( newSize100k == 0 ) + return; + + int n = baseBlockSize * newSize100k; + ll8 = new char[n]; + tt = new int[n]; + } + + private void setupBlock() + { + int cftab[] = new int[257]; + char ch; + + cftab[0] = 0; + for( i = 1; i <= 256; i++ ) + cftab[i] = unzftab[i - 1]; + for( i = 1; i <= 256; i++ ) + cftab[i] += cftab[i - 1]; + + for( i = 0; i <= last; i++ ) + { + ch = ( char )ll8[i]; + tt[cftab[ch]] = i; + cftab[ch]++; + } + cftab = null; + + tPos = tt[origPtr]; + + count = 0; + i2 = 0; + ch2 = 256; + /* + * not a char and not EOF + */ + if( blockRandomised ) + { + rNToGo = 0; + rTPos = 0; + setupRandPartA(); + } + else + { + setupNoRandPartA(); + } + } + + private void setupNoRandPartA() + { + if( i2 <= last ) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + i2++; + + currentChar = ch2; + currentState = NO_RAND_PART_B_STATE; + mCrc.updateCRC( ch2 ); + } + else + { + endBlock(); + initBlock(); + setupBlock(); + } + } + + private void setupNoRandPartB() + { + if( ch2 != chPrev ) + { + currentState = NO_RAND_PART_A_STATE; + count = 1; + setupNoRandPartA(); + } + else + { + count++; + if( count >= 4 ) + { + z = ll8[tPos]; + tPos = tt[tPos]; + currentState = NO_RAND_PART_C_STATE; + j2 = 0; + setupNoRandPartC(); + } + else + { + currentState = NO_RAND_PART_A_STATE; + setupNoRandPartA(); + } + } + } + + private void setupNoRandPartC() + { + if( j2 < ( int )z ) + { + currentChar = ch2; + mCrc.updateCRC( ch2 ); + j2++; + } + else + { + currentState = NO_RAND_PART_A_STATE; + i2++; + count = 0; + setupNoRandPartA(); + } + } + + private void setupRandPartA() + { + if( i2 <= last ) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + if( rNToGo == 0 ) + { + rNToGo = rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + ch2 ^= ( int )( ( rNToGo == 1 ) ? 1 : 0 ); + i2++; + + currentChar = ch2; + currentState = RAND_PART_B_STATE; + mCrc.updateCRC( ch2 ); + } + else + { + endBlock(); + initBlock(); + setupBlock(); + } + } + + private void setupRandPartB() + { + if( ch2 != chPrev ) + { + currentState = RAND_PART_A_STATE; + count = 1; + setupRandPartA(); + } + else + { + count++; + if( count >= 4 ) + { + z = ll8[tPos]; + tPos = tt[tPos]; + if( rNToGo == 0 ) + { + rNToGo = rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + z ^= ( ( rNToGo == 1 ) ? 1 : 0 ); + j2 = 0; + currentState = RAND_PART_C_STATE; + setupRandPartC(); + } + else + { + currentState = RAND_PART_A_STATE; + setupRandPartA(); + } + } + } + + private void setupRandPartC() + { + if( j2 < ( int )z ) + { + currentChar = ch2; + mCrc.updateCRC( ch2 ); + j2++; + } + else + { + currentState = RAND_PART_A_STATE; + i2++; + count = 0; + setupRandPartA(); + } + } + + private void getAndMoveToFrontDecode() + { + char yy[] = new char[256]; + int i; + int j; + int nextSym; + int limitLast; + int EOB; + int groupNo; + int groupPos; + + limitLast = baseBlockSize * blockSize100k; + origPtr = bsGetIntVS( 24 ); + + recvDecodingTables(); + EOB = nInUse + 1; + groupNo = -1; + groupPos = 0; + + /* + * Setting up the unzftab entries here is not strictly + * necessary, but it does save having to do it later + * in a separate pass, and so saves a block's worth of + * cache misses. + */ + for( i = 0; i <= 255; i++ ) + unzftab[i] = 0; + + for( i = 0; i <= 255; i++ ) + yy[i] = ( char )i; + + last = -1; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + nextSym = perm[zt][zvec - base[zt][zn]]; + } + + while( true ) + { + + if( nextSym == EOB ) + break; + + if( nextSym == RUNA || nextSym == RUNB ) + { + char ch; + int s = -1; + int N = 1; + do + { + if( nextSym == RUNA ) + s = s + ( 0 + 1 ) * N; + else if( nextSym == RUNB ) + s = s + ( 1 + 1 ) * N; + N = N * 2; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + ; + nextSym = perm[zt][zvec - base[zt][zn]]; + } + }while ( nextSym == RUNA || nextSym == RUNB ); + + s++; + ch = seqToUnseq[yy[0]]; + unzftab[ch] += s; + + while( s > 0 ) + { + last++; + ll8[last] = ch; + s--; + } + ; + + if( last >= limitLast ) + blockOverrun(); + continue; + } + else + { + char tmp; + last++; + if( last >= limitLast ) + blockOverrun(); + + tmp = yy[nextSym - 1]; + unzftab[seqToUnseq[tmp]]++; + ll8[last] = seqToUnseq[tmp]; + + /* + * This loop is hammered during decompression, + * hence the unrolling. + * for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1]; + */ + j = nextSym - 1; + for( ; j > 3; j -= 4 ) + { + yy[j] = yy[j - 1]; + yy[j - 1] = yy[j - 2]; + yy[j - 2] = yy[j - 3]; + yy[j - 3] = yy[j - 4]; + } + for( ; j > 0; j-- ) + yy[j] = yy[j - 1]; + + yy[0] = tmp; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + ; + nextSym = perm[zt][zvec - base[zt][zn]]; + } + continue; + } + } + } + + private void bsFinishedWithStream() + { + bsStream = null; + } + + private int bsGetInt32() + { + return ( int )bsGetint(); + } + + private int bsGetIntVS( int numBits ) + { + return ( int )bsR( numBits ); + } + + private char bsGetUChar() + { + return ( char )bsR( 8 ); + } + + private int bsGetint() + { + int u = 0; + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + return u; + } + + private int bsR( int n ) + { + int v; + { + while( bsLive < n ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + + v = ( bsBuff >> ( bsLive - n ) ) & ( ( 1 << n ) - 1 ); + bsLive -= n; + return v; + } + + private void bsSetStream( InputStream f ) + { + bsStream = f; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + bytesIn = 0; + } + + private void complete() + { + storedCombinedCRC = bsGetInt32(); + if( storedCombinedCRC != computedCombinedCRC ) + crcError(); + + bsFinishedWithStream(); + streamEnd = true; + } + + private void endBlock() + { + computedBlockCRC = mCrc.getFinalCRC(); + /* + * A bad CRC is considered a fatal error. + */ + if( storedBlockCRC != computedBlockCRC ) + crcError(); + + computedCombinedCRC = ( computedCombinedCRC << 1 ) + | ( computedCombinedCRC >>> 31 ); + computedCombinedCRC ^= computedBlockCRC; + } + + private void hbCreateDecodeTables( int[] limit, int[] base, + int[] perm, char[] length, + int minLen, int maxLen, int alphaSize ) + { + int pp; + int i; + int j; + int vec; + + pp = 0; + for( i = minLen; i <= maxLen; i++ ) + for( j = 0; j < alphaSize; j++ ) + if( length[j] == i ) + { + perm[pp] = j; + pp++; + } + ; + + for( i = 0; i < MAX_CODE_LEN; i++ ) + base[i] = 0; + for( i = 0; i < alphaSize; i++ ) + base[length[i] + 1]++; + + for( i = 1; i < MAX_CODE_LEN; i++ ) + base[i] += base[i - 1]; + + for( i = 0; i < MAX_CODE_LEN; i++ ) + limit[i] = 0; + vec = 0; + + for( i = minLen; i <= maxLen; i++ ) + { + vec += ( base[i + 1] - base[i] ); + limit[i] = vec - 1; + vec <<= 1; + } + for( i = minLen + 1; i <= maxLen; i++ ) + base[i] = ( ( limit[i - 1] + 1 ) << 1 ) - base[i]; + } + + private void initBlock() + { + char magic1; + char magic2; + char magic3; + char magic4; + char magic5; + char magic6; + magic1 = bsGetUChar(); + magic2 = bsGetUChar(); + magic3 = bsGetUChar(); + magic4 = bsGetUChar(); + magic5 = bsGetUChar(); + magic6 = bsGetUChar(); + if( magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 + && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90 ) + { + complete(); + return; + } + + if( magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 + || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59 ) + { + badBlockHeader(); + streamEnd = true; + return; + } + + storedBlockCRC = bsGetInt32(); + + if( bsR( 1 ) == 1 ) + blockRandomised = true; + else + blockRandomised = false; + + // currBlockNo++; + getAndMoveToFrontDecode(); + + mCrc.initialiseCRC(); + currentState = START_BLOCK_STATE; + } + + private void initialize() + { + char magic3; + char magic4; + magic3 = bsGetUChar(); + magic4 = bsGetUChar(); + if( magic3 != 'h' || magic4 < '1' || magic4 > '9' ) + { + bsFinishedWithStream(); + streamEnd = true; + return; + } + + setDecompressStructureSizes( magic4 - '0' ); + computedCombinedCRC = 0; + } + + private void makeMaps() + { + int i; + nInUse = 0; + for( i = 0; i < 256; i++ ) + if( inUse[i] ) + { + seqToUnseq[nInUse] = ( char )i; + unseqToSeq[i] = ( char )nInUse; + nInUse++; + } + } + + private void recvDecodingTables() + { + char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE]; + int i; + int j; + int t; + int nGroups; + int nSelectors; + int alphaSize; + int minLen; + int maxLen; + boolean inUse16[] = new boolean[16]; + + /* + * Receive the mapping table + */ + for( i = 0; i < 16; i++ ) + if( bsR( 1 ) == 1 ) + inUse16[i] = true; + else + inUse16[i] = false; + + for( i = 0; i < 256; i++ ) + inUse[i] = false; + + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + for( j = 0; j < 16; j++ ) + if( bsR( 1 ) == 1 ) + inUse[i * 16 + j] = true; + + makeMaps(); + alphaSize = nInUse + 2; + + /* + * Now the selectors + */ + nGroups = bsR( 3 ); + nSelectors = bsR( 15 ); + for( i = 0; i < nSelectors; i++ ) + { + j = 0; + while( bsR( 1 ) == 1 ) + j++; + selectorMtf[i] = ( char )j; + } + { + /* + * Undo the MTF values for the selectors. + */ + char pos[] = new char[N_GROUPS]; + char tmp; + char v; + for( v = 0; v < nGroups; v++ ) + pos[v] = v; + + for( i = 0; i < nSelectors; i++ ) + { + v = selectorMtf[i]; + tmp = pos[v]; + while( v > 0 ) + { + pos[v] = pos[v - 1]; + v--; + } + pos[0] = tmp; + selector[i] = tmp; + } + } + + /* + * Now the coding tables + */ + for( t = 0; t < nGroups; t++ ) + { + int curr = bsR( 5 ); + for( i = 0; i < alphaSize; i++ ) + { + while( bsR( 1 ) == 1 ) + { + if( bsR( 1 ) == 0 ) + curr++; + else + curr--; + } + len[t][i] = ( char )curr; + } + } + + /* + * Create the Huffman decoding tables + */ + for( t = 0; t < nGroups; t++ ) + { + minLen = 32; + maxLen = 0; + for( i = 0; i < alphaSize; i++ ) + { + if( len[t][i] > maxLen ) + maxLen = len[t][i]; + if( len[t][i] < minLen ) + minLen = len[t][i]; + } + hbCreateDecodeTables( limit[t], base[t], perm[t], len[t], minLen, + maxLen, alphaSize ); + minLens[t] = minLen; + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2OutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2OutputStream.java new file mode 100644 index 000000000..7ee53781c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2OutputStream.java @@ -0,0 +1,1807 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; +import java.io.*; + +/** + * An output stream that compresses into the BZip2 format (without the file + * header chars) into another stream. + * + * @author Keiron Liddle TODO: Update to + * BZip2 1.0.1 + */ +public class CBZip2OutputStream extends OutputStream implements BZip2Constants +{ + protected final static int SETMASK = ( 1 << 21 ); + protected final static int CLEARMASK = ( ~SETMASK ); + protected final static int GREATER_ICOST = 15; + protected final static int LESSER_ICOST = 0; + protected final static int SMALL_THRESH = 20; + protected final static int DEPTH_THRESH = 10; + + /* + * If you are ever unlucky/improbable enough + * to get a stack overflow whilst sorting, + * increase the following constant and try + * again. In practice I have never seen the + * stack go above 27 elems, so the following + * limit seems very generous. + */ + protected final static int QSORT_STACK_SIZE = 1000; + CRC mCrc = new CRC(); + + private boolean inUse[] = new boolean[256]; + + private char seqToUnseq[] = new char[256]; + private char unseqToSeq[] = new char[256]; + + private char selector[] = new char[MAX_SELECTORS]; + private char selectorMtf[] = new char[MAX_SELECTORS]; + + private int mtfFreq[] = new int[MAX_ALPHA_SIZE]; + + private int currentChar = -1; + private int runLength = 0; + + boolean closed = false; + + /* + * Knuth's increments seem to work better + * than Incerpi-Sedgewick here. Possibly + * because the number of elems to sort is + * usually small, typically <= 20. + */ + private int incs[] = {1, 4, 13, 40, 121, 364, 1093, 3280, + 9841, 29524, 88573, 265720, + 797161, 2391484}; + + boolean blockRandomised; + + /* + * always: in the range 0 .. 9. + * The current block size is 100000 * this number. + */ + int blockSize100k; + int bsBuff; + int bsLive; + + int bytesIn; + int bytesOut; + + /* + * index of the last char in the block, so + * the block size == last + 1. + */ + int last; + + /* + * index in zptr[] of original string after sorting. + */ + int origPtr; + + private int allowableBlockSize; + + private char block[]; + + private int blockCRC, combinedCRC; + + private OutputStream bsStream; + private boolean firstAttempt; + private int ftab[]; + private int nBlocksRandomised; + private int nInUse; + + private int nMTF; + private int quadrant[]; + private short szptr[]; + private int workDone; + + /* + * Used when sorting. If too many long comparisons + * happen, we stop sorting, randomise the block + * slightly, and try again. + */ + private int workFactor; + private int workLimit; + private int zptr[]; + + public CBZip2OutputStream( OutputStream inStream ) + throws IOException + { + this( inStream, 9 ); + } + + public CBZip2OutputStream( OutputStream inStream, int inBlockSize ) + throws IOException + { + block = null; + quadrant = null; + zptr = null; + ftab = null; + + bsSetStream( inStream ); + + workFactor = 50; + if( inBlockSize > 9 ) + { + inBlockSize = 9; + } + if( inBlockSize < 1 ) + { + inBlockSize = 1; + } + blockSize100k = inBlockSize; + allocateCompressStructures(); + initialize(); + initBlock(); + } + + protected static void hbMakeCodeLengths( char[] len, int[] freq, + int alphaSize, int maxLen ) + { + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int nNodes; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int nHeap; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int n1; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int n2; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int i; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int j; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int k; + boolean tooLong; + + int heap[] = new int[MAX_ALPHA_SIZE + 2]; + int weight[] = new int[MAX_ALPHA_SIZE * 2]; + int parent[] = new int[MAX_ALPHA_SIZE * 2]; + + for( i = 0; i < alphaSize; i++ ) + weight[i + 1] = ( freq[i] == 0 ? 1 : freq[i] ) << 8; + + while( true ) + { + nNodes = alphaSize; + nHeap = 0; + + heap[0] = 0; + weight[0] = 0; + parent[0] = -2; + + for( i = 1; i <= alphaSize; i++ ) + { + parent[i] = -1; + nHeap++; + heap[nHeap] = i; + { + int zz; + int tmp; + zz = nHeap; + tmp = heap[zz]; + while( weight[tmp] < weight[heap[zz >> 1]] ) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if( !( nHeap < ( MAX_ALPHA_SIZE + 2 ) ) ) + panic(); + + while( nHeap > 1 ) + { + n1 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0; + int yy = 0; + int tmp = 0; + zz = 1; + tmp = heap[zz]; + while( true ) + { + yy = zz << 1; + if( yy > nHeap ) + break; + if( yy < nHeap && + weight[heap[yy + 1]] < weight[heap[yy]] ) + yy++; + if( weight[tmp] < weight[heap[yy]] ) + break; + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + n2 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0; + int yy = 0; + int tmp = 0; + zz = 1; + tmp = heap[zz]; + while( true ) + { + yy = zz << 1; + if( yy > nHeap ) + break; + if( yy < nHeap && + weight[heap[yy + 1]] < weight[heap[yy]] ) + yy++; + if( weight[tmp] < weight[heap[yy]] ) + break; + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + nNodes++; + parent[n1] = parent[n2] = nNodes; + + weight[nNodes] = ( ( weight[n1] & 0xffffff00 ) + + ( weight[n2] & 0xffffff00 ) ) + | ( 1 + ( ( ( weight[n1] & 0x000000ff ) > + ( weight[n2] & 0x000000ff ) ) ? + ( weight[n1] & 0x000000ff ) : + ( weight[n2] & 0x000000ff ) ) ); + + parent[nNodes] = -1; + nHeap++; + heap[nHeap] = nNodes; + { + int zz = 0; + int tmp = 0; + zz = nHeap; + tmp = heap[zz]; + while( weight[tmp] < weight[heap[zz >> 1]] ) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if( !( nNodes < ( MAX_ALPHA_SIZE * 2 ) ) ) + panic(); + + tooLong = false; + for( i = 1; i <= alphaSize; i++ ) + { + j = 0; + k = i; + while( parent[k] >= 0 ) + { + k = parent[k]; + j++; + } + len[i - 1] = ( char )j; + if( j > maxLen ) + tooLong = true; + } + + if( !tooLong ) + break; + + for( i = 1; i < alphaSize; i++ ) + { + j = weight[i] >> 8; + j = 1 + ( j / 2 ); + weight[i] = j << 8; + } + } + } + + private static void panic() + { + System.out.println( "panic" ); + //throw new CError(); + } + + public void close() + throws IOException + { + if( closed ) + return; + + if( runLength > 0 ) + writeRun(); + currentChar = -1; + endBlock(); + endCompression(); + closed = true; + super.close(); + bsStream.close(); + } + + public void finalize() + throws Throwable + { + close(); + } + + public void flush() + throws IOException + { + super.flush(); + bsStream.flush(); + } + + /** + * modified by Oliver Merkel, 010128 + * + * @param bv Description of Parameter + * @exception IOException Description of Exception + */ + public void write( int bv ) + throws IOException + { + int b = ( 256 + bv ) % 256; + if( currentChar != -1 ) + { + if( currentChar == b ) + { + runLength++; + if( runLength > 254 ) + { + writeRun(); + currentChar = -1; + runLength = 0; + } + } + else + { + writeRun(); + runLength = 1; + currentChar = b; + } + } + else + { + currentChar = b; + runLength++; + } + } + + private void allocateCompressStructures() + { + int n = baseBlockSize * blockSize100k; + block = new char[( n + 1 + NUM_OVERSHOOT_BYTES )]; + quadrant = new int[( n + NUM_OVERSHOOT_BYTES )]; + zptr = new int[n]; + ftab = new int[65537]; + + if( block == null || quadrant == null || zptr == null + || ftab == null ) + { + //int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; + //compressOutOfMemory ( totalDraw, n ); + } + + /* + * The back end needs a place to store the MTF values + * whilst it calculates the coding tables. We could + * put them in the zptr array. However, these values + * will fit in a short, so we overlay szptr at the + * start of zptr, in the hope of reducing the number + * of cache misses induced by the multiple traversals + * of the MTF values when calculating coding tables. + * Seems to improve compression speed by about 1%. + */ + // szptr = zptr; + + szptr = new short[2 * n]; + } + + private void bsFinishedWithStream() + throws IOException + { + while( bsLive > 0 ) + { + int ch = ( bsBuff >> 24 ); + try + { + bsStream.write( ch );// write 8-bit + } + catch( IOException e ) + { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + } + + private void bsPutIntVS( int numBits, int c ) + throws IOException + { + bsW( numBits, c ); + } + + private void bsPutUChar( int c ) + throws IOException + { + bsW( 8, c ); + } + + private void bsPutint( int u ) + throws IOException + { + bsW( 8, ( u >> 24 ) & 0xff ); + bsW( 8, ( u >> 16 ) & 0xff ); + bsW( 8, ( u >> 8 ) & 0xff ); + bsW( 8, u & 0xff ); + } + + private void bsSetStream( OutputStream f ) + { + bsStream = f; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + bytesIn = 0; + } + + private void bsW( int n, int v ) + throws IOException + { + while( bsLive >= 8 ) + { + int ch = ( bsBuff >> 24 ); + try + { + bsStream.write( ch );// write 8-bit + } + catch( IOException e ) + { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + bsBuff |= ( v << ( 32 - bsLive - n ) ); + bsLive += n; + } + + private void doReversibleTransformation() + { + int i; + + workLimit = workFactor * last; + workDone = 0; + blockRandomised = false; + firstAttempt = true; + + mainSort(); + + if( workDone > workLimit && firstAttempt ) + { + randomiseBlock(); + workLimit = workDone = 0; + blockRandomised = true; + firstAttempt = false; + mainSort(); + } + + origPtr = -1; + for( i = 0; i <= last; i++ ) + if( zptr[i] == 0 ) + { + origPtr = i; + break; + } + ; + + if( origPtr == -1 ) + panic(); + } + + private void endBlock() + throws IOException + { + blockCRC = mCrc.getFinalCRC(); + combinedCRC = ( combinedCRC << 1 ) | ( combinedCRC >>> 31 ); + combinedCRC ^= blockCRC; + + /* + * sort the block and establish posn of original string + */ + doReversibleTransformation(); + + /* + * A 6-byte block header, the value chosen arbitrarily + * as 0x314159265359 :-). A 32 bit value does not really + * give a strong enough guarantee that the value will not + * appear by chance in the compressed datastream. Worst-case + * probability of this event, for a 900k block, is about + * 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. + * For a compressed file of size 100Gb -- about 100000 blocks -- + * only a 48-bit marker will do. NB: normal compression/ + * decompression do *not* rely on these statistical properties. + * They are only important when trying to recover blocks from + * damaged files. + */ + bsPutUChar( 0x31 ); + bsPutUChar( 0x41 ); + bsPutUChar( 0x59 ); + bsPutUChar( 0x26 ); + bsPutUChar( 0x53 ); + bsPutUChar( 0x59 ); + + /* + * Now the block's CRC, so it is in a known place. + */ + bsPutint( blockCRC ); + + /* + * Now a single bit indicating randomisation. + */ + if( blockRandomised ) + { + bsW( 1, 1 ); + nBlocksRandomised++; + } + else + { + bsW( 1, 0 ); + } + + /* + * Finally, block's contents proper. + */ + moveToFrontCodeAndSend(); + } + + private void endCompression() + throws IOException + { + /* + * Now another magic 48-bit number, 0x177245385090, to + * indicate the end of the last block. (sqrt(pi), if + * you want to know. I did want to use e, but it contains + * too much repetition -- 27 18 28 18 28 46 -- for me + * to feel statistically comfortable. Call me paranoid.) + */ + bsPutUChar( 0x17 ); + bsPutUChar( 0x72 ); + bsPutUChar( 0x45 ); + bsPutUChar( 0x38 ); + bsPutUChar( 0x50 ); + bsPutUChar( 0x90 ); + + bsPutint( combinedCRC ); + + bsFinishedWithStream(); + } + + private boolean fullGtU( int i1, int i2 ) + { + int k; + char c1; + char c2; + int s1; + int s2; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + k = last + 1; + + do + { + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + if( i1 > last ) + { + i1 -= last; + i1--; + } + ; + if( i2 > last ) + { + i2 -= last; + i2--; + } + ; + + k -= 4; + workDone++; + }while ( k >= 0 ); + + return false; + } + + private void generateMTFValues() + { + char yy[] = new char[256]; + int i; + int j; + char tmp; + char tmp2; + int zPend; + int wr; + int EOB; + + makeMaps(); + EOB = nInUse + 1; + + for( i = 0; i <= EOB; i++ ) + mtfFreq[i] = 0; + + wr = 0; + zPend = 0; + for( i = 0; i < nInUse; i++ ) + yy[i] = ( char )i; + + for( i = 0; i <= last; i++ ) + { + char ll_i; + + ll_i = unseqToSeq[block[zptr[i]]]; + + j = 0; + tmp = yy[j]; + while( ll_i != tmp ) + { + j++; + tmp2 = tmp; + tmp = yy[j]; + yy[j] = tmp2; + } + ; + yy[0] = tmp; + + if( j == 0 ) + { + zPend++; + } + else + { + if( zPend > 0 ) + { + zPend--; + while( true ) + { + switch ( zPend % 2 ) + { + case 0: + szptr[wr] = ( short )RUNA; + wr++; + mtfFreq[RUNA]++; + break; + case 1: + szptr[wr] = ( short )RUNB; + wr++; + mtfFreq[RUNB]++; + break; + } + ; + if( zPend < 2 ) + break; + zPend = ( zPend - 2 ) / 2; + } + ; + zPend = 0; + } + szptr[wr] = ( short )( j + 1 ); + wr++; + mtfFreq[j + 1]++; + } + } + + if( zPend > 0 ) + { + zPend--; + while( true ) + { + switch ( zPend % 2 ) + { + case 0: + szptr[wr] = ( short )RUNA; + wr++; + mtfFreq[RUNA]++; + break; + case 1: + szptr[wr] = ( short )RUNB; + wr++; + mtfFreq[RUNB]++; + break; + } + if( zPend < 2 ) + break; + zPend = ( zPend - 2 ) / 2; + } + } + + szptr[wr] = ( short )EOB; + wr++; + mtfFreq[EOB]++; + + nMTF = wr; + } + + private void hbAssignCodes( int[] code, char[] length, int minLen, + int maxLen, int alphaSize ) + { + int n; + int vec; + int i; + + vec = 0; + for( n = minLen; n <= maxLen; n++ ) + { + for( i = 0; i < alphaSize; i++ ) + if( length[i] == n ) + { + code[i] = vec; + vec++; + } + ; + vec <<= 1; + } + } + + private void initBlock() + { + // blockNo++; + mCrc.initialiseCRC(); + last = -1; + // ch = 0; + + for( int i = 0; i < 256; i++ ) + inUse[i] = false; + + /* + * 20 is just a paranoia constant + */ + allowableBlockSize = baseBlockSize * blockSize100k - 20; + } + + private void initialize() + throws IOException + { + bytesIn = 0; + bytesOut = 0; + nBlocksRandomised = 0; + + /* + * Write `magic' bytes h indicating file-format == huffmanised, + * followed by a digit indicating blockSize100k. + */ + bsPutUChar( 'h' ); + bsPutUChar( '0' + blockSize100k ); + + combinedCRC = 0; + } + + private void mainSort() + { + int i; + int j; + int ss; + int sb; + int runningOrder[] = new int[256]; + int copy[] = new int[256]; + boolean bigDone[] = new boolean[256]; + int c1; + int c2; + int numQSorted; + + /* + * In the various block-sized structures, live data runs + * from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, + * set up the overshoot area for block. + */ + // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); + for( i = 0; i < NUM_OVERSHOOT_BYTES; i++ ) + block[last + i + 2] = block[( i % ( last + 1 ) ) + 1]; + for( i = 0; i <= last + NUM_OVERSHOOT_BYTES; i++ ) + quadrant[i] = 0; + + block[0] = ( char )( block[last + 1] ); + + if( last < 4000 ) + { + /* + * Use simpleSort(), since the full sorting mechanism + * has quite a large constant overhead. + */ + for( i = 0; i <= last; i++ ) + zptr[i] = i; + firstAttempt = false; + workDone = workLimit = 0; + simpleSort( 0, last, 0 ); + } + else + { + numQSorted = 0; + for( i = 0; i <= 255; i++ ) + bigDone[i] = false; + + for( i = 0; i <= 65536; i++ ) + ftab[i] = 0; + + c1 = block[0]; + for( i = 0; i <= last; i++ ) + { + c2 = block[i + 1]; + ftab[( c1 << 8 ) + c2]++; + c1 = c2; + } + + for( i = 1; i <= 65536; i++ ) + ftab[i] += ftab[i - 1]; + + c1 = block[1]; + for( i = 0; i < last; i++ ) + { + c2 = block[i + 2]; + j = ( c1 << 8 ) + c2; + c1 = c2; + ftab[j]--; + zptr[ftab[j]] = i; + } + + j = ( ( block[last + 1] ) << 8 ) + ( block[1] ); + ftab[j]--; + zptr[ftab[j]] = last; + + /* + * Now ftab contains the first loc of every small bucket. + * Calculate the running order, from smallest to largest + * big bucket. + */ + for( i = 0; i <= 255; i++ ) + runningOrder[i] = i; + { + int vv; + int h = 1; + do + h = 3 * h + 1; +while ( h <= 256 ); + do + { + h = h / 3; + for( i = h; i <= 255; i++ ) + { + vv = runningOrder[i]; + j = i; + while( ( ftab[( ( runningOrder[j - h] ) + 1 ) << 8] + - ftab[( runningOrder[j - h] ) << 8] ) > + ( ftab[( ( vv ) + 1 ) << 8] - ftab[( vv ) << 8] ) ) + { + runningOrder[j] = runningOrder[j - h]; + j = j - h; + if( j <= ( h - 1 ) ) + break; + } + runningOrder[j] = vv; + } + }while ( h != 1 ); + } + + /* + * The main sorting loop. + */ + for( i = 0; i <= 255; i++ ) + { + + /* + * Process big buckets, starting with the least full. + */ + ss = runningOrder[i]; + + /* + * Complete the big bucket [ss] by quicksorting + * any unsorted small buckets [ss, j]. Hopefully + * previous pointer-scanning phases have already + * completed many of the small buckets [ss, j], so + * we don't have to sort them at all. + */ + for( j = 0; j <= 255; j++ ) + { + sb = ( ss << 8 ) + j; + if( !( ( ftab[sb] & SETMASK ) == SETMASK ) ) + { + int lo = ftab[sb] & CLEARMASK; + int hi = ( ftab[sb + 1] & CLEARMASK ) - 1; + if( hi > lo ) + { + qSort3( lo, hi, 2 ); + numQSorted += ( hi - lo + 1 ); + if( workDone > workLimit && firstAttempt ) + return; + } + ftab[sb] |= SETMASK; + } + } + + /* + * The ss big bucket is now done. Record this fact, + * and update the quadrant descriptors. Remember to + * update quadrants in the overshoot area too, if + * necessary. The "if (i < 255)" test merely skips + * this updating for the last bucket processed, since + * updating for the last bucket is pointless. + */ + bigDone[ss] = true; + + if( i < 255 ) + { + int bbStart = ftab[ss << 8] & CLEARMASK; + int bbSize = ( ftab[( ss + 1 ) << 8] & CLEARMASK ) - bbStart; + int shifts = 0; + + while( ( bbSize >> shifts ) > 65534 ) + shifts++; + + for( j = 0; j < bbSize; j++ ) + { + int a2update = zptr[bbStart + j]; + int qVal = ( j >> shifts ); + quadrant[a2update] = qVal; + if( a2update < NUM_OVERSHOOT_BYTES ) + quadrant[a2update + last + 1] = qVal; + } + + if( !( ( ( bbSize - 1 ) >> shifts ) <= 65535 ) ) + panic(); + } + + /* + * Now scan this big bucket so as to synthesise the + * sorted order for small buckets [t, ss] for all t != ss. + */ + for( j = 0; j <= 255; j++ ) + copy[j] = ftab[( j << 8 ) + ss] & CLEARMASK; + + for( j = ftab[ss << 8] & CLEARMASK; + j < ( ftab[( ss + 1 ) << 8] & CLEARMASK ); j++ ) + { + c1 = block[zptr[j]]; + if( !bigDone[c1] ) + { + zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; + copy[c1]++; + } + } + + for( j = 0; j <= 255; j++ ) + ftab[( j << 8 ) + ss] |= SETMASK; + } + } + } + + private void makeMaps() + { + int i; + nInUse = 0; + for( i = 0; i < 256; i++ ) + if( inUse[i] ) + { + seqToUnseq[nInUse] = ( char )i; + unseqToSeq[i] = ( char )nInUse; + nInUse++; + } + } + + private char med3( char a, char b, char c ) + { + char t; + if( a > b ) + { + t = a; + a = b; + b = t; + } + if( b > c ) + { + t = b; + b = c; + c = t; + } + if( a > b ) + b = a; + return b; + } + + private void moveToFrontCodeAndSend() + throws IOException + { + bsPutIntVS( 24, origPtr ); + generateMTFValues(); + sendMTFValues(); + } + + private void qSort3( int loSt, int hiSt, int dSt ) + { + int unLo; + int unHi; + int ltLo; + int gtHi; + int med; + int n; + int m; + int sp; + int lo; + int hi; + int d; + StackElem[] stack = new StackElem[QSORT_STACK_SIZE]; + for( int count = 0; count < QSORT_STACK_SIZE; count++ ) + stack[count] = new StackElem(); + + sp = 0; + + stack[sp].ll = loSt; + stack[sp].hh = hiSt; + stack[sp].dd = dSt; + sp++; + + while( sp > 0 ) + { + if( sp >= QSORT_STACK_SIZE ) + panic(); + + sp--; + lo = stack[sp].ll; + hi = stack[sp].hh; + d = stack[sp].dd; + + if( hi - lo < SMALL_THRESH || d > DEPTH_THRESH ) + { + simpleSort( lo, hi, d ); + if( workDone > workLimit && firstAttempt ) + return; + continue; + } + + med = med3( block[zptr[lo] + d + 1], + block[zptr[hi] + d + 1], + block[zptr[( lo + hi ) >> 1] + d + 1] ); + + unLo = ltLo = lo; + unHi = gtHi = hi; + + while( true ) + { + while( true ) + { + if( unLo > unHi ) + break; + n = ( ( int )block[zptr[unLo] + d + 1] ) - med; + if( n == 0 ) + { + int temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[ltLo]; + zptr[ltLo] = temp; + ltLo++; + unLo++; + continue; + } + ; + if( n > 0 ) + break; + unLo++; + } + while( true ) + { + if( unLo > unHi ) + break; + n = ( ( int )block[zptr[unHi] + d + 1] ) - med; + if( n == 0 ) + { + int temp = 0; + temp = zptr[unHi]; + zptr[unHi] = zptr[gtHi]; + zptr[gtHi] = temp; + gtHi--; + unHi--; + continue; + } + ; + if( n < 0 ) + break; + unHi--; + } + if( unLo > unHi ) + break; + int temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[unHi]; + zptr[unHi] = temp; + unLo++; + unHi--; + } + + if( gtHi < ltLo ) + { + stack[sp].ll = lo; + stack[sp].hh = hi; + stack[sp].dd = d + 1; + sp++; + continue; + } + + n = ( ( ltLo - lo ) < ( unLo - ltLo ) ) ? ( ltLo - lo ) : ( unLo - ltLo ); + vswap( lo, unLo - n, n ); + m = ( ( hi - gtHi ) < ( gtHi - unHi ) ) ? ( hi - gtHi ) : ( gtHi - unHi ); + vswap( unLo, hi - m + 1, m ); + + n = lo + unLo - ltLo - 1; + m = hi - ( gtHi - unHi ) + 1; + + stack[sp].ll = lo; + stack[sp].hh = n; + stack[sp].dd = d; + sp++; + + stack[sp].ll = n + 1; + stack[sp].hh = m - 1; + stack[sp].dd = d + 1; + sp++; + + stack[sp].ll = m; + stack[sp].hh = hi; + stack[sp].dd = d; + sp++; + } + } + + private void randomiseBlock() + { + int i; + int rNToGo = 0; + int rTPos = 0; + for( i = 0; i < 256; i++ ) + inUse[i] = false; + + for( i = 0; i <= last; i++ ) + { + if( rNToGo == 0 ) + { + rNToGo = ( char )rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + block[i + 1] ^= ( ( rNToGo == 1 ) ? 1 : 0 ); + // handle 16 bit signed numbers + block[i + 1] &= 0xFF; + + inUse[block[i + 1]] = true; + } + } + + private void sendMTFValues() + throws IOException + { + char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE]; + + int v; + + int t; + + int i; + + int j; + + int gs; + + int ge; + + int totc; + + int bt; + + int bc; + + int iter; + int nSelectors = 0; + int alphaSize; + int minLen; + int maxLen; + int selCtr; + int nGroups; + int nBytes; + + alphaSize = nInUse + 2; + for( t = 0; t < N_GROUPS; t++ ) + for( v = 0; v < alphaSize; v++ ) + len[t][v] = ( char )GREATER_ICOST; + + /* + * Decide how many coding tables to use + */ + if( nMTF <= 0 ) + panic(); + + if( nMTF < 200 ) + nGroups = 2; + else if( nMTF < 600 ) + nGroups = 3; + else if( nMTF < 1200 ) + nGroups = 4; + else if( nMTF < 2400 ) + nGroups = 5; + else + nGroups = 6; + { + /* + * Generate an initial set of coding tables + */ + int nPart; + int remF; + int tFreq; + int aFreq; + + nPart = nGroups; + remF = nMTF; + gs = 0; + while( nPart > 0 ) + { + tFreq = remF / nPart; + ge = gs - 1; + aFreq = 0; + while( aFreq < tFreq && ge < alphaSize - 1 ) + { + ge++; + aFreq += mtfFreq[ge]; + } + + if( ge > gs && nPart != nGroups && nPart != 1 + && ( ( nGroups - nPart ) % 2 == 1 ) ) + { + aFreq -= mtfFreq[ge]; + ge--; + } + + for( v = 0; v < alphaSize; v++ ) + if( v >= gs && v <= ge ) + len[nPart - 1][v] = ( char )LESSER_ICOST; + else + len[nPart - 1][v] = ( char )GREATER_ICOST; + + nPart--; + gs = ge + 1; + remF -= aFreq; + } + } + + int rfreq[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + int fave[] = new int[N_GROUPS]; + short cost[] = new short[N_GROUPS]; + /* + * Iterate up to N_ITERS times to improve the tables. + */ + for( iter = 0; iter < N_ITERS; iter++ ) + { + for( t = 0; t < nGroups; t++ ) + fave[t] = 0; + + for( t = 0; t < nGroups; t++ ) + for( v = 0; v < alphaSize; v++ ) + rfreq[t][v] = 0; + + nSelectors = 0; + totc = 0; + gs = 0; + while( true ) + { + + /* + * Set group start & end marks. + */ + if( gs >= nMTF ) + break; + ge = gs + G_SIZE - 1; + if( ge >= nMTF ) + ge = nMTF - 1; + + /* + * Calculate the cost of this group as coded + * by each of the coding tables. + */ + for( t = 0; t < nGroups; t++ ) + cost[t] = 0; + + if( nGroups == 6 ) + { + short cost0; + short cost1; + short cost2; + short cost3; + short cost4; + short cost5; + cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; + for( i = gs; i <= ge; i++ ) + { + short icv = szptr[i]; + cost0 += len[0][icv]; + cost1 += len[1][icv]; + cost2 += len[2][icv]; + cost3 += len[3][icv]; + cost4 += len[4][icv]; + cost5 += len[5][icv]; + } + cost[0] = cost0; + cost[1] = cost1; + cost[2] = cost2; + cost[3] = cost3; + cost[4] = cost4; + cost[5] = cost5; + } + else + { + for( i = gs; i <= ge; i++ ) + { + short icv = szptr[i]; + for( t = 0; t < nGroups; t++ ) + cost[t] += len[t][icv]; + } + } + + /* + * Find the coding table which is best for this group, + * and record its identity in the selector table. + */ + bc = 999999999; + bt = -1; + for( t = 0; t < nGroups; t++ ) + if( cost[t] < bc ) + { + bc = cost[t]; + bt = t; + } + ; + totc += bc; + fave[bt]++; + selector[nSelectors] = ( char )bt; + nSelectors++; + + /* + * Increment the symbol frequencies for the selected table. + */ + for( i = gs; i <= ge; i++ ) + rfreq[bt][szptr[i]]++; + + gs = ge + 1; + } + + /* + * Recompute the tables based on the accumulated frequencies. + */ + for( t = 0; t < nGroups; t++ ) + hbMakeCodeLengths( len[t], rfreq[t], alphaSize, 20 ); + } + + rfreq = null; + fave = null; + cost = null; + + if( !( nGroups < 8 ) ) + panic(); + if( !( nSelectors < 32768 && nSelectors <= ( 2 + ( 900000 / G_SIZE ) ) ) ) + panic(); + { + /* + * Compute MTF values for the selectors. + */ + char pos[] = new char[N_GROUPS]; + char ll_i; + char tmp2; + char tmp; + for( i = 0; i < nGroups; i++ ) + pos[i] = ( char )i; + for( i = 0; i < nSelectors; i++ ) + { + ll_i = selector[i]; + j = 0; + tmp = pos[j]; + while( ll_i != tmp ) + { + j++; + tmp2 = tmp; + tmp = pos[j]; + pos[j] = tmp2; + } + pos[0] = tmp; + selectorMtf[i] = ( char )j; + } + } + + int code[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + + /* + * Assign actual codes for the tables. + */ + for( t = 0; t < nGroups; t++ ) + { + minLen = 32; + maxLen = 0; + for( i = 0; i < alphaSize; i++ ) + { + if( len[t][i] > maxLen ) + maxLen = len[t][i]; + if( len[t][i] < minLen ) + minLen = len[t][i]; + } + if( maxLen > 20 ) + panic(); + if( minLen < 1 ) + panic(); + hbAssignCodes( code[t], len[t], minLen, maxLen, alphaSize ); + } + { + /* + * Transmit the mapping table. + */ + boolean inUse16[] = new boolean[16]; + for( i = 0; i < 16; i++ ) + { + inUse16[i] = false; + for( j = 0; j < 16; j++ ) + if( inUse[i * 16 + j] ) + inUse16[i] = true; + } + + nBytes = bytesOut; + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + bsW( 1, 1 ); + else + bsW( 1, 0 ); + + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + for( j = 0; j < 16; j++ ) + if( inUse[i * 16 + j] ) + bsW( 1, 1 ); + else + bsW( 1, 0 ); + + } + + /* + * Now the selectors. + */ + nBytes = bytesOut; + bsW( 3, nGroups ); + bsW( 15, nSelectors ); + for( i = 0; i < nSelectors; i++ ) + { + for( j = 0; j < selectorMtf[i]; j++ ) + bsW( 1, 1 ); + bsW( 1, 0 ); + } + + /* + * Now the coding tables. + */ + nBytes = bytesOut; + + for( t = 0; t < nGroups; t++ ) + { + int curr = len[t][0]; + bsW( 5, curr ); + for( i = 0; i < alphaSize; i++ ) + { + while( curr < len[t][i] ) + { + bsW( 2, 2 ); + curr++; + /* + * 10 + */ + } + while( curr > len[t][i] ) + { + bsW( 2, 3 ); + curr--; + /* + * 11 + */ + } + bsW( 1, 0 ); + } + } + + /* + * And finally, the block data proper + */ + nBytes = bytesOut; + selCtr = 0; + gs = 0; + while( true ) + { + if( gs >= nMTF ) + break; + ge = gs + G_SIZE - 1; + if( ge >= nMTF ) + ge = nMTF - 1; + for( i = gs; i <= ge; i++ ) + { + bsW( len[selector[selCtr]][szptr[i]], + code[selector[selCtr]][szptr[i]] ); + } + + gs = ge + 1; + selCtr++; + } + if( !( selCtr == nSelectors ) ) + panic(); + } + + private void simpleSort( int lo, int hi, int d ) + { + int i; + int j; + int h; + int bigN; + int hp; + int v; + + bigN = hi - lo + 1; + if( bigN < 2 ) + return; + + hp = 0; + while( incs[hp] < bigN ) + hp++; + hp--; + + for( ; hp >= 0; hp-- ) + { + h = incs[hp]; + + i = lo + h; + while( true ) + { + /* + * copy 1 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + /* + * copy 2 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + /* + * copy 3 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + if( workDone > workLimit && firstAttempt ) + return; + } + } + } + + private void vswap( int p1, int p2, int n ) + { + int temp = 0; + while( n > 0 ) + { + temp = zptr[p1]; + zptr[p1] = zptr[p2]; + zptr[p2] = temp; + p1++; + p2++; + n--; + } + } + + private void writeRun() + throws IOException + { + if( last < allowableBlockSize ) + { + inUse[currentChar] = true; + for( int i = 0; i < runLength; i++ ) + { + mCrc.updateCRC( ( char )currentChar ); + } + switch ( runLength ) + { + case 1: + last++; + block[last + 1] = ( char )currentChar; + break; + case 2: + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + break; + case 3: + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + break; + default: + inUse[runLength - 4] = true; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )( runLength - 4 ); + break; + } + } + else + { + endBlock(); + initBlock(); + writeRun(); + } + } + + private class StackElem + { + int dd; + int hh; + int ll; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/bzip2/CRC.java b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CRC.java new file mode 100644 index 000000000..f12b482a6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CRC.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; + +/** + * A simple class the hold and calculate the CRC for sanity checking of the + * data. + * + * @author Keiron Liddle + */ +class CRC +{ + public static int crc32Table[] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + }; + + int globalCrc; + + public CRC() + { + initialiseCRC(); + } + + void setGlobalCRC( int newCrc ) + { + globalCrc = newCrc; + } + + int getFinalCRC() + { + return ~globalCrc; + } + + int getGlobalCRC() + { + return globalCrc; + } + + void initialiseCRC() + { + globalCrc = 0xffffffff; + } + + void updateCRC( int inCh ) + { + int temp = ( globalCrc >> 24 ) ^ inCh; + if( temp < 0 ) + temp = 256 + temp; + globalCrc = ( globalCrc << 8 ) ^ CRC.crc32Table[temp]; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/mail/MailMessage.java b/proposal/myrmidon/src/main/org/apache/tools/mail/MailMessage.java new file mode 100644 index 000000000..d8520d6c6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/mail/MailMessage.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.mail; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.InetAddress; +import java.net.Socket; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +/** + * A class to help send SMTP email. This class is an improvement on the + * sun.net.smtp.SmtpClient class found in the JDK. This version has extra + * functionality, and can be used with JVMs that did not extend from the JDK. + * It's not as robust as the JavaMail Standard Extension classes, but it's + * easier to use and easier to install, and has an Open Source license.

          + * + * It can be used like this:

          + * String mailhost = "localhost";  // or another mail host
          + * String from = "Mail Message Servlet <MailMessage@server.com>";
          + * String to = "to@you.com";
          + * String cc1 = "cc1@you.com";
          + * String cc2 = "cc2@you.com";
          + * String bcc = "bcc@you.com";
          + *  
          + * MailMessage msg = new MailMessage(mailhost);
          + * msg.setPort(25);
          + * msg.from(from);
          + * msg.to(to);
          + * msg.cc(cc1);
          + * msg.cc(cc2);
          + * msg.bcc(bcc);
          + * msg.setSubject("Test subject");
          + * PrintStream out = msg.getPrintStream();
          + *  
          + * Enumeration enum = req.getParameterNames();
          + * while (enum.hasMoreElements()) {
          + *   String name = (String)enum.nextElement();
          + *   String value = req.getParameter(name);
          + *   out.println(name + " = " + value);
          + * }
          + *  
          + * msg.sendAndClose();
          + * 

          + * + * Be sure to set the from address, then set the recepient addresses, then set + * the subject and other headers, then get the PrintStream, then write the + * message, and finally send and close. The class does minimal error checking + * internally; it counts on the mail host to complain if there's any + * malformatted input or out of order execution.

          + * + * An attachment mechanism based on RFC 1521 could be implemented on top of this + * class. In the meanwhile, JavaMail is the best solution for sending email with + * attachments.

          + * + * Still to do: + *

            + *
          • Figure out how to close the connection in case of error + *
          + * + * + * @author Jason Hunter + * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers + * version 1.0, 1999/12/29 + */ +public class MailMessage +{ + + /** + * default port for SMTP: 25 + */ + public final static int DEFAULT_PORT = 25; + + /** + * host port for the mail server + */ + private int port = DEFAULT_PORT; + + /** + * list of email addresses to cc to + */ + private Vector cc; + + /** + * sender email address + */ + private String from; + + /** + * headers to send in the mail + */ + private Hashtable headers; + + /** + * host name for the mail server + */ + private String host; + + private SmtpResponseReader in; + + private MailPrintStream out; + + private Socket socket; + + /** + * list of email addresses to send to + */ + private Vector to; + + /** + * Constructs a new MailMessage to send an email. Use localhost as the mail + * server. + * + * @exception IOException if there's any problem contacting the mail server + */ + public MailMessage() + throws IOException + { + this( "localhost" ); + } + + /** + * Constructs a new MailMessage to send an email. Use the given host as the + * mail server. + * + * @param host the mail server to use + * @exception IOException if there's any problem contacting the mail server + */ + public MailMessage( String host ) + throws IOException + { + this.host = host; + to = new Vector(); + cc = new Vector(); + headers = new Hashtable(); + setHeader( "X-Mailer", "org.apache.tools.mail.MailMessage (jakarta.apache.org)" ); + connect(); + sendHelo(); + } + + // Make a limited attempt to extract a sanitized email address + // Prefer text in , ignore anything in (parentheses) + static String sanitizeAddress( String s ) + { + int paramDepth = 0; + int start = 0; + int end = 0; + int len = s.length(); + + for( int i = 0; i < len; i++ ) + { + char c = s.charAt( i ); + if( c == '(' ) + { + paramDepth++; + if( start == 0 ) + { + end = i;// support "address (name)" + } + } + else if( c == ')' ) + { + paramDepth--; + if( end == 0 ) + { + start = i + 1;// support "(name) address" + } + } + else if( paramDepth == 0 && c == '<' ) + { + start = i + 1; + } + else if( paramDepth == 0 && c == '>' ) + { + end = i; + } + } + + if( end == 0 ) + { + end = len; + } + + return s.substring( start, end ); + } + + /** + * Sets the named header to the given value. RFC 822 provides the rules for + * what text may constitute a header name and value. + * + * @param name The new Header value + * @param value The new Header value + */ + public void setHeader( String name, String value ) + { + // Blindly trust the user doesn't set any invalid headers + headers.put( name, value ); + } + + /** + * Set the port to connect to the SMTP host. + * + * @param port the port to use for connection. + * @see #DEFAULT_PORT + */ + public void setPort( int port ) + { + this.port = port; + } + + /** + * Sets the subject of the mail message. Actually sets the "Subject" header. + * + * @param subj The new Subject value + */ + public void setSubject( String subj ) + { + headers.put( "Subject", subj ); + } + + /** + * Returns a PrintStream that can be used to write the body of the message. + * A stream is used since email bodies are byte-oriented. A writer could be + * wrapped on top if necessary for internationalization. + * + * @return The PrintStream value + * @exception IOException if there's any problem reported by the mail server + */ + public PrintStream getPrintStream() + throws IOException + { + setFromHeader(); + setToHeader(); + setCcHeader(); + sendData(); + flushHeaders(); + return out; + } + + /** + * Sets the bcc address. Does NOT set any header since it's a *blind* copy. + * This method may be called multiple times. + * + * @param bcc Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void bcc( String bcc ) + throws IOException + { + sendRcpt( bcc ); + // No need to keep track of Bcc'd addresses + } + + /** + * Sets the cc address. Also sets the "Cc" header. This method may be called + * multiple times. + * + * @param cc Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void cc( String cc ) + throws IOException + { + sendRcpt( cc ); + this.cc.addElement( cc ); + } + + /** + * Sets the from address. Also sets the "From" header. This method should be + * called only once. + * + * @param from Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void from( String from ) + throws IOException + { + sendFrom( from ); + this.from = from; + } + + /** + * Sends the message and closes the connection to the server. The + * MailMessage object cannot be reused. + * + * @exception IOException if there's any problem reported by the mail server + */ + public void sendAndClose() + throws IOException + { + sendDot(); + sendQuit(); + disconnect(); + } + + /** + * Sets the to address. Also sets the "To" header. This method may be called + * multiple times. + * + * @param to Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void to( String to ) + throws IOException + { + sendRcpt( to ); + this.to.addElement( to ); + } + + void setCcHeader() + { + setHeader( "Cc", vectorToList( cc ) ); + } + + void setFromHeader() + { + setHeader( "From", from ); + } + + void setToHeader() + { + setHeader( "To", vectorToList( to ) ); + } + + void getReady() + throws IOException + { + String response = in.getResponse(); + int[] ok = {220}; + if( !isResponseOK( response, ok ) ) + { + throw new IOException( + "Didn't get introduction from server: " + response ); + } + } + + boolean isResponseOK( String response, int[] ok ) + { + // Check that the response is one of the valid codes + for( int i = 0; i < ok.length; i++ ) + { + if( response.startsWith( "" + ok[i] ) ) + { + return true; + } + } + return false; + } + + // * * * * * Raw protocol methods below here * * * * * + + void connect() + throws IOException + { + socket = new Socket( host, port ); + out = new MailPrintStream( + new BufferedOutputStream( + socket.getOutputStream() ) ); + in = new SmtpResponseReader( socket.getInputStream() ); + getReady(); + } + + void disconnect() + throws IOException + { + if( out != null ) + out.close(); + if( in != null ) + in.close(); + if( socket != null ) + socket.close(); + } + + void flushHeaders() + throws IOException + { + // XXX Should I care about order here? + Enumeration e = headers.keys(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + String value = ( String )headers.get( name ); + out.println( name + ": " + value ); + } + out.println(); + out.flush(); + } + + void send( String msg, int[] ok ) + throws IOException + { + out.rawPrint( msg + "\r\n" );// raw supports . + //System.out.println("S: " + msg); + String response = in.getResponse(); + //System.out.println("R: " + response); + if( !isResponseOK( response, ok ) ) + { + throw new IOException( + "Unexpected reply to command: " + msg + ": " + response ); + } + } + + void sendData() + throws IOException + { + int[] ok = {354}; + send( "DATA", ok ); + } + + void sendDot() + throws IOException + { + int[] ok = {250}; + send( "\r\n.", ok );// make sure dot is on new line + } + + void sendFrom( String from ) + throws IOException + { + int[] ok = {250}; + send( "MAIL FROM: " + "<" + sanitizeAddress( from ) + ">", ok ); + } + + void sendHelo() + throws IOException + { + String local = InetAddress.getLocalHost().getHostName(); + int[] ok = {250}; + send( "HELO " + local, ok ); + } + + void sendQuit() + throws IOException + { + int[] ok = {221}; + send( "QUIT", ok ); + } + + void sendRcpt( String rcpt ) + throws IOException + { + int[] ok = {250, 251}; + send( "RCPT TO: " + "<" + sanitizeAddress( rcpt ) + ">", ok ); + } + + String vectorToList( Vector v ) + { + StringBuffer buf = new StringBuffer(); + Enumeration e = v.elements(); + while( e.hasMoreElements() ) + { + buf.append( e.nextElement() ); + if( e.hasMoreElements() ) + { + buf.append( ", " ); + } + } + return buf.toString(); + } +} + +// This PrintStream subclass makes sure that . becomes .. +// per RFC 821. It also ensures that new lines are always \r\n. +// +class MailPrintStream extends PrintStream +{ + + int lastChar; + + public MailPrintStream( OutputStream out ) + { + super( out, true );// deprecated, but email is byte-oriented + } + + // Mac does \n\r, but that's tough to distinguish from Windows \r\n\r\n. + // Don't tackle that problem right now. + public void write( int b ) + { + if( b == '\n' && lastChar != '\r' ) + { + rawWrite( '\r' );// ensure always \r\n + rawWrite( b ); + } + else if( b == '.' && lastChar == '\n' ) + { + rawWrite( '.' );// add extra dot + rawWrite( b ); + } + else + { + rawWrite( b ); + } + lastChar = b; + } + + public void write( byte buf[], int off, int len ) + { + for( int i = 0; i < len; i++ ) + { + write( buf[off + i] ); + } + } + + void rawPrint( String s ) + { + int len = s.length(); + for( int i = 0; i < len; i++ ) + { + rawWrite( s.charAt( i ) ); + } + } + + void rawWrite( int b ) + { + super.write( b ); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/mail/SmtpResponseReader.java b/proposal/myrmidon/src/main/org/apache/tools/mail/SmtpResponseReader.java new file mode 100644 index 000000000..73f935bfc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/mail/SmtpResponseReader.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.mail; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * A wrapper around the raw input from the SMTP server that assembles multi line + * responses into a single String.

          + * + * The same rules used here would apply to FTP and other Telnet based protocols + * as well.

          + * + * @author Stefan Bodewig + */ +public class SmtpResponseReader +{ + + protected BufferedReader reader = null; + private StringBuffer result = new StringBuffer(); + + /** + * Wrap this input stream. + * + * @param in Description of Parameter + */ + public SmtpResponseReader( InputStream in ) + { + reader = new BufferedReader( new InputStreamReader( in ) ); + } + + /** + * Read until the server indicates that the response is complete. + * + * @return Responsecode (3 digits) + Blank + Text from all response line + * concatenated (with blanks replacing the \r\n sequences). + * @exception IOException Description of Exception + */ + public String getResponse() + throws IOException + { + result.setLength( 0 ); + String line = reader.readLine(); + if( line != null && line.length() >= 3 ) + { + result.append( line.substring( 0, 3 ) ); + result.append( " " ); + } + + while( line != null ) + { + append( line ); + if( !hasMoreLines( line ) ) + { + break; + } + line = reader.readLine(); + } + return result.toString().trim(); + } + + /** + * Closes the underlying stream. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + reader.close(); + } + + /** + * Should we expect more input? + * + * @param line Description of Parameter + * @return Description of the Returned Value + */ + protected boolean hasMoreLines( String line ) + { + return line.length() > 3 && line.charAt( 3 ) == '-'; + } + + /** + * Append the text from this line of the resonse. + * + * @param line Description of Parameter + */ + private void append( String line ) + { + if( line.length() > 4 ) + { + result.append( line.substring( 4 ) ); + result.append( " " ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarBuffer.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarBuffer.java new file mode 100644 index 000000000..a580a3377 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarBuffer.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The TarBuffer class implements the tar archive concept of a buffered input + * stream. This concept goes back to the days of blocked tape drives and special + * io devices. In the Java universe, the only real function that this class + * performs is to ensure that files have the correct "block" size, or other tars + * will complain.

          + * + * You should never have a need to access this class directly. TarBuffers are + * created by Tar IO Streams. + * + * @author Timothy Gerard Endres time@ice.com + */ + +public class TarBuffer +{ + + public final static int DEFAULT_RCDSIZE = ( 512 ); + public final static int DEFAULT_BLKSIZE = ( DEFAULT_RCDSIZE * 20 ); + private byte[] blockBuffer; + private int blockSize; + private int currBlkIdx; + private int currRecIdx; + private boolean debug; + + private InputStream inStream; + private OutputStream outStream; + private int recordSize; + private int recsPerBlock; + + public TarBuffer( InputStream inStream ) + { + this( inStream, TarBuffer.DEFAULT_BLKSIZE ); + } + + public TarBuffer( InputStream inStream, int blockSize ) + { + this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarBuffer( InputStream inStream, int blockSize, int recordSize ) + { + this.inStream = inStream; + this.outStream = null; + + this.initialize( blockSize, recordSize ); + } + + public TarBuffer( OutputStream outStream ) + { + this( outStream, TarBuffer.DEFAULT_BLKSIZE ); + } + + public TarBuffer( OutputStream outStream, int blockSize ) + { + this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarBuffer( OutputStream outStream, int blockSize, int recordSize ) + { + this.inStream = null; + this.outStream = outStream; + + this.initialize( blockSize, recordSize ); + } + + /** + * Set the debugging flag for the buffer. + * + * @param debug If true, print debugging output. + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Get the TAR Buffer's block size. Blocks consist of multiple records. + * + * @return The BlockSize value + */ + public int getBlockSize() + { + return this.blockSize; + } + + /** + * Get the current block number, zero based. + * + * @return The current zero based block number. + */ + public int getCurrentBlockNum() + { + return this.currBlkIdx; + } + + /** + * Get the current record number, within the current block, zero based. + * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum. + * + * @return The current zero based record number. + */ + public int getCurrentRecordNum() + { + return this.currRecIdx - 1; + } + + /** + * Get the TAR Buffer's record size. + * + * @return The RecordSize value + */ + public int getRecordSize() + { + return this.recordSize; + } + + /** + * Determine if an archive record indicate End of Archive. End of archive is + * indicated by a record that consists entirely of null bytes. + * + * @param record The record data to check. + * @return The EOFRecord value + */ + public boolean isEOFRecord( byte[] record ) + { + for( int i = 0, sz = this.getRecordSize(); i < sz; ++i ) + { + if( record[i] != 0 ) + { + return false; + } + } + + return true; + } + + /** + * Close the TarBuffer. If this is an output buffer, also flush the current + * block before closing. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + if( this.debug ) + { + System.err.println( "TarBuffer.closeBuffer()." ); + } + + if( this.outStream != null ) + { + this.flushBlock(); + + if( this.outStream != System.out + && this.outStream != System.err ) + { + this.outStream.close(); + + this.outStream = null; + } + } + else if( this.inStream != null ) + { + if( this.inStream != System.in ) + { + this.inStream.close(); + + this.inStream = null; + } + } + } + + /** + * Read a record from the input stream and return the data. + * + * @return The record data. + * @exception IOException Description of Exception + */ + public byte[] readRecord() + throws IOException + { + if( this.debug ) + { + System.err.println( "ReadRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading from an output buffer" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + if( !this.readBlock() ) + { + return null; + } + } + + byte[] result = new byte[this.recordSize]; + + System.arraycopy( this.blockBuffer, + ( this.currRecIdx * this.recordSize ), result, 0, + this.recordSize ); + + this.currRecIdx++; + + return result; + } + + /** + * Skip over a record on the input stream. + * + * @exception IOException Description of Exception + */ + public void skipRecord() + throws IOException + { + if( this.debug ) + { + System.err.println( "SkipRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading (via skip) from an output buffer" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + if( !this.readBlock() ) + { + return;// UNDONE + } + } + + this.currRecIdx++; + } + + /** + * Write an archive record to the archive. + * + * @param record The record data to write to the archive. + * @exception IOException Description of Exception + */ + public void writeRecord( byte[] record ) + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( record.length != this.recordSize ) + { + throw new IOException( "record to write has length '" + + record.length + + "' which is not the record size of '" + + this.recordSize + "'" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + this.writeBlock(); + } + + System.arraycopy( record, 0, this.blockBuffer, + ( this.currRecIdx * this.recordSize ), + this.recordSize ); + + this.currRecIdx++; + } + + /** + * Write an archive record to the archive, where the record may be inside of + * a larger array buffer. The buffer must be "offset plus record size" long. + * + * @param buf The buffer containing the record data to write. + * @param offset The offset of the record data within buf. + * @exception IOException Description of Exception + */ + public void writeRecord( byte[] buf, int offset ) + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( ( offset + this.recordSize ) > buf.length ) + { + throw new IOException( "record has length '" + buf.length + + "' with offset '" + offset + + "' which is less than the record size of '" + + this.recordSize + "'" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + this.writeBlock(); + } + + System.arraycopy( buf, offset, this.blockBuffer, + ( this.currRecIdx * this.recordSize ), + this.recordSize ); + + this.currRecIdx++; + } + + /** + * Flush the current data block if it has any data in it. + * + * @exception IOException Description of Exception + */ + private void flushBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "TarBuffer.flushBlock() called." ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( this.currRecIdx > 0 ) + { + this.writeBlock(); + } + } + + /** + * Initialization common to all constructors. + * + * @param blockSize Description of Parameter + * @param recordSize Description of Parameter + */ + private void initialize( int blockSize, int recordSize ) + { + this.debug = false; + this.blockSize = blockSize; + this.recordSize = recordSize; + this.recsPerBlock = ( this.blockSize / this.recordSize ); + this.blockBuffer = new byte[this.blockSize]; + + if( this.inStream != null ) + { + this.currBlkIdx = -1; + this.currRecIdx = this.recsPerBlock; + } + else + { + this.currBlkIdx = 0; + this.currRecIdx = 0; + } + } + + /** + * @return false if End-Of-File, else true + * @exception IOException Description of Exception + */ + private boolean readBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "ReadBlock: blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading from an output buffer" ); + } + + this.currRecIdx = 0; + + int offset = 0; + int bytesNeeded = this.blockSize; + + while( bytesNeeded > 0 ) + { + long numBytes = this.inStream.read( this.blockBuffer, offset, + bytesNeeded ); + + // + // NOTE + // We have fit EOF, and the block is not full! + // + // This is a broken archive. It does not follow the standard + // blocking algorithm. However, because we are generous, and + // it requires little effort, we will simply ignore the error + // and continue as if the entire block were read. This does + // not appear to break anything upstream. We used to return + // false in this case. + // + // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix. + // + if( numBytes == -1 ) + { + break; + } + + offset += numBytes; + bytesNeeded -= numBytes; + + if( numBytes != this.blockSize ) + { + if( this.debug ) + { + System.err.println( "ReadBlock: INCOMPLETE READ " + + numBytes + " of " + this.blockSize + + " bytes read." ); + } + } + } + + this.currBlkIdx++; + + return true; + } + + /** + * Write a TarBuffer block to the archive. + * + * @exception IOException Description of Exception + */ + private void writeBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteBlock: blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + this.outStream.write( this.blockBuffer, 0, this.blockSize ); + this.outStream.flush(); + + this.currRecIdx = 0; + this.currBlkIdx++; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarConstants.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarConstants.java new file mode 100644 index 000000000..6203d31d1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarConstants.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; + +/** + * This interface contains all the definitions used in the package. + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ + +public interface TarConstants +{ + + /** + * The length of the name field in a header buffer. + */ + int NAMELEN = 100; + + /** + * The length of the mode field in a header buffer. + */ + int MODELEN = 8; + + /** + * The length of the user id field in a header buffer. + */ + int UIDLEN = 8; + + /** + * The length of the group id field in a header buffer. + */ + int GIDLEN = 8; + + /** + * The length of the checksum field in a header buffer. + */ + int CHKSUMLEN = 8; + + /** + * The length of the size field in a header buffer. + */ + int SIZELEN = 12; + + /** + * The length of the magic field in a header buffer. + */ + int MAGICLEN = 8; + + /** + * The length of the modification time field in a header buffer. + */ + int MODTIMELEN = 12; + + /** + * The length of the user name field in a header buffer. + */ + int UNAMELEN = 32; + + /** + * The length of the group name field in a header buffer. + */ + int GNAMELEN = 32; + + /** + * The length of the devices field in a header buffer. + */ + int DEVLEN = 8; + + /** + * LF_ constants represent the "link flag" of an entry, or more commonly, + * the "entry type". This is the "old way" of indicating a normal file. + */ + byte LF_OLDNORM = 0; + + /** + * Normal file type. + */ + byte LF_NORMAL = ( byte )'0'; + + /** + * Link file type. + */ + byte LF_LINK = ( byte )'1'; + + /** + * Symbolic link file type. + */ + byte LF_SYMLINK = ( byte )'2'; + + /** + * Character device file type. + */ + byte LF_CHR = ( byte )'3'; + + /** + * Block device file type. + */ + byte LF_BLK = ( byte )'4'; + + /** + * Directory file type. + */ + byte LF_DIR = ( byte )'5'; + + /** + * FIFO (pipe) file type. + */ + byte LF_FIFO = ( byte )'6'; + + /** + * Contiguous file type. + */ + byte LF_CONTIG = ( byte )'7'; + + /** + * The magic tag representing a POSIX tar archive. + */ + String TMAGIC = "ustar"; + + /** + * The magic tag representing a GNU tar archive. + */ + String GNU_TMAGIC = "ustar "; + + /** + * The namr of the GNU tar entry which contains a long name. + */ + String GNU_LONGLINK = "././@LongLink"; + + /** + * Identifies the *next* file on the tape as having a long name. + */ + byte LF_GNUTYPE_LONGNAME = ( byte )'L'; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarEntry.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarEntry.java new file mode 100644 index 000000000..7914dd132 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarEntry.java @@ -0,0 +1,654 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.File; +import java.util.Date; + +/** + * This class represents an entry in a Tar archive. It consists of the entry's + * header, as well as the entry's File. Entries can be instantiated in one of + * three ways, depending on how they are to be used.

          + * + * TarEntries that are created from the header bytes read from an archive are + * instantiated with the TarEntry( byte[] ) constructor. These entries will be + * used when extracting from or listing the contents of an archive. These + * entries have their header filled in using the header bytes. They also set the + * File to null, since they reference an archive entry not a file.

          + * + * TarEntries that are created from Files that are to be written into an archive + * are instantiated with the TarEntry( File ) constructor. These entries have + * their header filled in using the File's information. They also keep a + * reference to the File for convenience when writing entries.

          + * + * Finally, TarEntries can be constructed from nothing but a name. This allows + * the programmer to construct the entry by hand, for instance when only an + * InputStream is available for writing to the archive, and the header + * information is constructed from other information. In this case the header + * fields are set to defaults and the File is set to null.

          + * + * The C structure for a Tar Entry's header is:

          + * struct header {
          + * char name[NAMSIZ];
          + * char mode[8];
          + * char uid[8];
          + * char gid[8];
          + * char size[12];
          + * char mtime[12];
          + * char chksum[8];
          + * char linkflag;
          + * char linkname[NAMSIZ];
          + * char magic[8];
          + * char uname[TUNMLEN];
          + * char gname[TGNMLEN];
          + * char devmajor[8];
          + * char devminor[8];
          + * } header;
          + * 
          + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ + +public class TarEntry implements TarConstants +{ + /** + * The entry's modification time. + */ + private int checkSum; + /** + * The entry's group name. + */ + private int devMajor; + /** + * The entry's major device number. + */ + private int devMinor; + /** + * The entry's minor device number. + */ + private File file; + /** + * The entry's user id. + */ + private int groupId; + /** + * The entry's user name. + */ + private StringBuffer groupName; + /** + * The entry's checksum. + */ + private byte linkFlag; + /** + * The entry's link flag. + */ + private StringBuffer linkName; + /** + * The entry's link name. + */ + private StringBuffer magic; + /** + * The entry's size. + */ + private long modTime; + /** + * The entry's name. + */ + private int mode; + + private StringBuffer name; + /** + * The entry's group id. + */ + private long size; + /** + * The entry's permission mode. + */ + private int userId; + /** + * The entry's magic tag. + */ + private StringBuffer userName; + + /** + * Construct an entry with only a name. This allows the programmer to + * construct the entry's header "by hand". File is set to null. + * + * @param name Description of Parameter + */ + public TarEntry( String name ) + { + this(); + + boolean isDir = name.endsWith( "/" ); + + this.checkSum = 0; + this.devMajor = 0; + this.devMinor = 0; + this.name = new StringBuffer( name ); + this.mode = isDir ? 040755 : 0100644; + this.linkFlag = isDir ? LF_DIR : LF_NORMAL; + this.userId = 0; + this.groupId = 0; + this.size = 0; + this.checkSum = 0; + this.modTime = ( new Date() ).getTime() / 1000; + this.linkName = new StringBuffer( "" ); + this.userName = new StringBuffer( "" ); + this.groupName = new StringBuffer( "" ); + this.devMajor = 0; + this.devMinor = 0; + + } + + /** + * Construct an entry with a name an a link flag. + * + * @param name Description of Parameter + * @param linkFlag Description of Parameter + */ + public TarEntry( String name, byte linkFlag ) + { + this( name ); + this.linkFlag = linkFlag; + } + + /** + * Construct an entry for a file. File is set to file, and the header is + * constructed from information from the file. + * + * @param file The file that the entry represents. + */ + public TarEntry( File file ) + { + this(); + + this.file = file; + + String name = file.getPath(); + String osname = System.getProperty( "os.name" ); + + if( osname != null ) + { + + // Strip off drive letters! + // REVIEW Would a better check be "(File.separator == '\')"? + String win32Prefix = "Windows"; + String prefix = osname.substring( 0, win32Prefix.length() ); + + if( prefix.equalsIgnoreCase( win32Prefix ) ) + { + if( name.length() > 2 ) + { + char ch1 = name.charAt( 0 ); + char ch2 = name.charAt( 1 ); + + if( ch2 == ':' + && ( ( ch1 >= 'a' && ch1 <= 'z' ) + || ( ch1 >= 'A' && ch1 <= 'Z' ) ) ) + { + name = name.substring( 2 ); + } + } + } + else if( osname.toLowerCase().indexOf( "netware" ) > -1 ) + { + int colon = name.indexOf( ':' ); + if( colon != -1 ) + { + name = name.substring( colon + 1 ); + } + } + } + + name = name.replace( File.separatorChar, '/' ); + + // No absolute pathnames + // Windows (and Posix?) paths can start with "\\NetworkDrive\", + // so we loop on starting /'s. + while( name.startsWith( "/" ) ) + { + name = name.substring( 1 ); + } + + this.linkName = new StringBuffer( "" ); + this.name = new StringBuffer( name ); + + if( file.isDirectory() ) + { + this.mode = 040755; + this.linkFlag = LF_DIR; + + if( this.name.charAt( this.name.length() - 1 ) != '/' ) + { + this.name.append( "/" ); + } + } + else + { + this.mode = 0100644; + this.linkFlag = LF_NORMAL; + } + + this.size = file.length(); + this.modTime = file.lastModified() / 1000; + this.checkSum = 0; + this.devMajor = 0; + this.devMinor = 0; + } + + /** + * Construct an entry from an archive's header bytes. File is set to null. + * + * @param headerBuf The header bytes from a tar archive entry. + */ + public TarEntry( byte[] headerBuf ) + { + this(); + this.parseTarHeader( headerBuf ); + } + + /** + * The entry's file reference + */ + + /** + * Construct an empty entry and prepares the header values. + */ + private TarEntry() + { + this.magic = new StringBuffer( TMAGIC ); + this.name = new StringBuffer(); + this.linkName = new StringBuffer(); + + String user = System.getProperty( "user.name", "" ); + + if( user.length() > 31 ) + { + user = user.substring( 0, 31 ); + } + + this.userId = 0; + this.groupId = 0; + this.userName = new StringBuffer( user ); + this.groupName = new StringBuffer( "" ); + this.file = null; + } + + /** + * Set this entry's group id. + * + * @param groupId This entry's new group id. + */ + public void setGroupId( int groupId ) + { + this.groupId = groupId; + } + + /** + * Set this entry's group name. + * + * @param groupName This entry's new group name. + */ + public void setGroupName( String groupName ) + { + this.groupName = new StringBuffer( groupName ); + } + + /** + * Convenience method to set this entry's group and user ids. + * + * @param userId This entry's new user id. + * @param groupId This entry's new group id. + */ + public void setIds( int userId, int groupId ) + { + this.setUserId( userId ); + this.setGroupId( groupId ); + } + + /** + * Set this entry's modification time. The parameter passed to this method + * is in "Java time". + * + * @param time This entry's new modification time. + */ + public void setModTime( long time ) + { + this.modTime = time / 1000; + } + + /** + * Set this entry's modification time. + * + * @param time This entry's new modification time. + */ + public void setModTime( Date time ) + { + this.modTime = time.getTime() / 1000; + } + + /** + * Set the mode for this entry + * + * @param mode The new Mode value + */ + public void setMode( int mode ) + { + this.mode = mode; + } + + /** + * Set this entry's name. + * + * @param name This entry's new name. + */ + public void setName( String name ) + { + this.name = new StringBuffer( name ); + } + + /** + * Convenience method to set this entry's group and user names. + * + * @param userName This entry's new user name. + * @param groupName This entry's new group name. + */ + public void setNames( String userName, String groupName ) + { + this.setUserName( userName ); + this.setGroupName( groupName ); + } + + /** + * Set this entry's file size. + * + * @param size This entry's new file size. + */ + public void setSize( long size ) + { + this.size = size; + } + + /** + * Set this entry's user id. + * + * @param userId This entry's new user id. + */ + public void setUserId( int userId ) + { + this.userId = userId; + } + + /** + * Set this entry's user name. + * + * @param userName This entry's new user name. + */ + public void setUserName( String userName ) + { + this.userName = new StringBuffer( userName ); + } + + /** + * If this entry represents a file, and the file is a directory, return an + * array of TarEntries for this entry's children. + * + * @return An array of TarEntry's for this entry's children. + */ + public TarEntry[] getDirectoryEntries() + { + if( this.file == null || !this.file.isDirectory() ) + { + return new TarEntry[0]; + } + + String[] list = this.file.list(); + TarEntry[] result = new TarEntry[list.length]; + + for( int i = 0; i < list.length; ++i ) + { + result[i] = new TarEntry( new File( this.file, list[i] ) ); + } + + return result; + } + + /** + * Get this entry's file. + * + * @return This entry's file. + */ + public File getFile() + { + return this.file; + } + + /** + * Get this entry's group id. + * + * @return This entry's group id. + */ + public int getGroupId() + { + return this.groupId; + } + + /** + * Get this entry's group name. + * + * @return This entry's group name. + */ + public String getGroupName() + { + return this.groupName.toString(); + } + + /** + * Set this entry's modification time. + * + * @return The ModTime value + */ + public Date getModTime() + { + return new Date( this.modTime * 1000 ); + } + + /** + * Get this entry's mode. + * + * @return This entry's mode. + */ + public int getMode() + { + return this.mode; + } + + /** + * Get this entry's name. + * + * @return This entry's name. + */ + public String getName() + { + return this.name.toString(); + } + + /** + * Get this entry's file size. + * + * @return This entry's file size. + */ + public long getSize() + { + return this.size; + } + + + /** + * Get this entry's user id. + * + * @return This entry's user id. + */ + public int getUserId() + { + return this.userId; + } + + /** + * Get this entry's user name. + * + * @return This entry's user name. + */ + public String getUserName() + { + return this.userName.toString(); + } + + /** + * Determine if the given entry is a descendant of this entry. Descendancy + * is determined by the name of the descendant starting with this entry's + * name. + * + * @param desc Entry to be checked as a descendent of this. + * @return True if entry is a descendant of this. + */ + public boolean isDescendent( TarEntry desc ) + { + return desc.getName().startsWith( this.getName() ); + } + + /** + * Return whether or not this entry represents a directory. + * + * @return True if this entry is a directory. + */ + public boolean isDirectory() + { + if( this.file != null ) + { + return this.file.isDirectory(); + } + + if( this.linkFlag == LF_DIR ) + { + return true; + } + + if( this.getName().endsWith( "/" ) ) + { + return true; + } + + return false; + } + + + /** + * Indicate if this entry is a GNU long name block + * + * @return true if this is a long name extension provided by GNU tar + */ + public boolean isGNULongNameEntry() + { + return linkFlag == LF_GNUTYPE_LONGNAME && + name.toString().equals( GNU_LONGLINK ); + } + + /** + * Determine if the two entries are equal. Equality is determined by the + * header names being equal. + * + * @param it Description of Parameter + * @return it Entry to be checked for equality. + * @return True if the entries are equal. + */ + public boolean equals( TarEntry it ) + { + return this.getName().equals( it.getName() ); + } + + /** + * Parse an entry's header information from a header buffer. + * + * @param header The tar entry header buffer to get information from. + */ + public void parseTarHeader( byte[] header ) + { + int offset = 0; + + this.name = TarUtils.parseName( header, offset, NAMELEN ); + offset += NAMELEN; + this.mode = ( int )TarUtils.parseOctal( header, offset, MODELEN ); + offset += MODELEN; + this.userId = ( int )TarUtils.parseOctal( header, offset, UIDLEN ); + offset += UIDLEN; + this.groupId = ( int )TarUtils.parseOctal( header, offset, GIDLEN ); + offset += GIDLEN; + this.size = TarUtils.parseOctal( header, offset, SIZELEN ); + offset += SIZELEN; + this.modTime = TarUtils.parseOctal( header, offset, MODTIMELEN ); + offset += MODTIMELEN; + this.checkSum = ( int )TarUtils.parseOctal( header, offset, CHKSUMLEN ); + offset += CHKSUMLEN; + this.linkFlag = header[offset++]; + this.linkName = TarUtils.parseName( header, offset, NAMELEN ); + offset += NAMELEN; + this.magic = TarUtils.parseName( header, offset, MAGICLEN ); + offset += MAGICLEN; + this.userName = TarUtils.parseName( header, offset, UNAMELEN ); + offset += UNAMELEN; + this.groupName = TarUtils.parseName( header, offset, GNAMELEN ); + offset += GNAMELEN; + this.devMajor = ( int )TarUtils.parseOctal( header, offset, DEVLEN ); + offset += DEVLEN; + this.devMinor = ( int )TarUtils.parseOctal( header, offset, DEVLEN ); + } + + /** + * Write an entry's header information to a header buffer. + * + * @param outbuf The tar entry header buffer to fill in. + */ + public void writeEntryHeader( byte[] outbuf ) + { + int offset = 0; + + offset = TarUtils.getNameBytes( this.name, outbuf, offset, NAMELEN ); + offset = TarUtils.getOctalBytes( this.mode, outbuf, offset, MODELEN ); + offset = TarUtils.getOctalBytes( this.userId, outbuf, offset, UIDLEN ); + offset = TarUtils.getOctalBytes( this.groupId, outbuf, offset, GIDLEN ); + offset = TarUtils.getLongOctalBytes( this.size, outbuf, offset, SIZELEN ); + offset = TarUtils.getLongOctalBytes( this.modTime, outbuf, offset, MODTIMELEN ); + + int csOffset = offset; + + for( int c = 0; c < CHKSUMLEN; ++c ) + { + outbuf[offset++] = ( byte )' '; + } + + outbuf[offset++] = this.linkFlag; + offset = TarUtils.getNameBytes( this.linkName, outbuf, offset, NAMELEN ); + offset = TarUtils.getNameBytes( this.magic, outbuf, offset, MAGICLEN ); + offset = TarUtils.getNameBytes( this.userName, outbuf, offset, UNAMELEN ); + offset = TarUtils.getNameBytes( this.groupName, outbuf, offset, GNAMELEN ); + offset = TarUtils.getOctalBytes( this.devMajor, outbuf, offset, DEVLEN ); + offset = TarUtils.getOctalBytes( this.devMinor, outbuf, offset, DEVLEN ); + + while( offset < outbuf.length ) + { + outbuf[offset++] = 0; + } + + long checkSum = TarUtils.computeCheckSum( outbuf ); + + TarUtils.getCheckSumOctalBytes( checkSum, outbuf, csOffset, CHKSUMLEN ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarInputStream.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarInputStream.java new file mode 100644 index 000000000..5e340e6ae --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarInputStream.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The TarInputStream reads a UNIX tar archive as an InputStream. methods are + * provided to position at each successive entry in the archive, and the read + * each entry as a normal input stream using read(). + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ +public class TarInputStream extends FilterInputStream +{ + protected TarBuffer buffer; + protected TarEntry currEntry; + + protected boolean debug; + protected int entryOffset; + protected int entrySize; + protected boolean hasHitEOF; + protected byte[] oneBuf; + protected byte[] readBuf; + + public TarInputStream( InputStream is ) + { + this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarInputStream( InputStream is, int blockSize ) + { + this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarInputStream( InputStream is, int blockSize, int recordSize ) + { + super( is ); + + this.buffer = new TarBuffer( is, blockSize, recordSize ); + this.readBuf = null; + this.oneBuf = new byte[1]; + this.debug = false; + this.hasHitEOF = false; + } + + /** + * Sets the debugging flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + this.buffer.setDebug( debug ); + } + + /** + * Get the next entry in this tar archive. This will skip over any remaining + * data in the current entry, if there is one, and place the input stream at + * the header of the next entry, and read the header and instantiate a new + * TarEntry from the header bytes and return that entry. If there are no + * more entries in the archive, null will be returned to indicate that the + * end of the archive has been reached. + * + * @return The next TarEntry in the archive, or null. + * @exception IOException Description of Exception + */ + public TarEntry getNextEntry() + throws IOException + { + if( this.hasHitEOF ) + { + return null; + } + + if( this.currEntry != null ) + { + int numToSkip = this.entrySize - this.entryOffset; + + if( this.debug ) + { + System.err.println( "TarInputStream: SKIP currENTRY '" + + this.currEntry.getName() + "' SZ " + + this.entrySize + " OFF " + + this.entryOffset + " skipping " + + numToSkip + " bytes" ); + } + + if( numToSkip > 0 ) + { + this.skip( numToSkip ); + } + + this.readBuf = null; + } + + byte[] headerBuf = this.buffer.readRecord(); + + if( headerBuf == null ) + { + if( this.debug ) + { + System.err.println( "READ NULL RECORD" ); + } + this.hasHitEOF = true; + } + else if( this.buffer.isEOFRecord( headerBuf ) ) + { + if( this.debug ) + { + System.err.println( "READ EOF RECORD" ); + } + this.hasHitEOF = true; + } + + if( this.hasHitEOF ) + { + this.currEntry = null; + } + else + { + this.currEntry = new TarEntry( headerBuf ); + + if( !( headerBuf[257] == 'u' && headerBuf[258] == 's' + && headerBuf[259] == 't' && headerBuf[260] == 'a' + && headerBuf[261] == 'r' ) ) + { + this.entrySize = 0; + this.entryOffset = 0; + this.currEntry = null; + + throw new IOException( "bad header in block " + + this.buffer.getCurrentBlockNum() + + " record " + + this.buffer.getCurrentRecordNum() + + ", " + + "header magic is not 'ustar', but '" + + headerBuf[257] + + headerBuf[258] + + headerBuf[259] + + headerBuf[260] + + headerBuf[261] + + "', or (dec) " + + ( ( int )headerBuf[257] ) + + ", " + + ( ( int )headerBuf[258] ) + + ", " + + ( ( int )headerBuf[259] ) + + ", " + + ( ( int )headerBuf[260] ) + + ", " + + ( ( int )headerBuf[261] ) ); + } + + if( this.debug ) + { + System.err.println( "TarInputStream: SET CURRENTRY '" + + this.currEntry.getName() + + "' size = " + + this.currEntry.getSize() ); + } + + this.entryOffset = 0; + + // REVIEW How do we resolve this discrepancy?! + this.entrySize = ( int )this.currEntry.getSize(); + } + + if( this.currEntry != null && this.currEntry.isGNULongNameEntry() ) + { + // read in the name + StringBuffer longName = new StringBuffer(); + byte[] buffer = new byte[256]; + int length = 0; + while( ( length = read( buffer ) ) >= 0 ) + { + longName.append( new String( buffer, 0, length ) ); + } + getNextEntry(); + this.currEntry.setName( longName.toString() ); + } + + return this.currEntry; + } + + /** + * Get the record size being used by this stream's TarBuffer. + * + * @return The TarBuffer record size. + */ + public int getRecordSize() + { + return this.buffer.getRecordSize(); + } + + /** + * Get the available data that can be read from the current entry in the + * archive. This does not indicate how much data is left in the entire + * archive, only in the current entry. This value is determined from the + * entry's size header field and the amount of data already read from the + * current entry. + * + * @return The number of available bytes for the current entry. + * @exception IOException Description of Exception + */ + public int available() + throws IOException + { + return this.entrySize - this.entryOffset; + } + + /** + * Closes this stream. Calls the TarBuffer's close() method. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + this.buffer.close(); + } + + /** + * Copies the contents of the current tar archive entry directly into an + * output stream. + * + * @param out The OutputStream into which to write the entry's data. + * @exception IOException Description of Exception + */ + public void copyEntryContents( OutputStream out ) + throws IOException + { + byte[] buf = new byte[32 * 1024]; + + while( true ) + { + int numRead = this.read( buf, 0, buf.length ); + + if( numRead == -1 ) + { + break; + } + + out.write( buf, 0, numRead ); + } + } + + /** + * Since we do not support marking just yet, we do nothing. + * + * @param markLimit The limit to mark. + */ + public void mark( int markLimit ) { } + + /** + * Since we do not support marking just yet, we return false. + * + * @return False. + */ + public boolean markSupported() + { + return false; + } + + /** + * Reads a byte from the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @return The byte read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read() + throws IOException + { + int num = this.read( this.oneBuf, 0, 1 ); + + if( num == -1 ) + { + return num; + } + else + { + return ( int )this.oneBuf[0]; + } + } + + /** + * Reads bytes from the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @param buf The buffer into which to place bytes read. + * @return The number of bytes read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read( byte[] buf ) + throws IOException + { + return this.read( buf, 0, buf.length ); + } + + /** + * Reads bytes from the current tar archive entry. This method is aware of + * the boundaries of the current entry in the archive and will deal with + * them as if they were this stream's start and EOF. + * + * @param buf The buffer into which to place bytes read. + * @param offset The offset at which to place bytes read. + * @param numToRead The number of bytes to read. + * @return The number of bytes read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read( byte[] buf, int offset, int numToRead ) + throws IOException + { + int totalRead = 0; + + if( this.entryOffset >= this.entrySize ) + { + return -1; + } + + if( ( numToRead + this.entryOffset ) > this.entrySize ) + { + numToRead = ( this.entrySize - this.entryOffset ); + } + + if( this.readBuf != null ) + { + int sz = ( numToRead > this.readBuf.length ) ? this.readBuf.length + : numToRead; + + System.arraycopy( this.readBuf, 0, buf, offset, sz ); + + if( sz >= this.readBuf.length ) + { + this.readBuf = null; + } + else + { + int newLen = this.readBuf.length - sz; + byte[] newBuf = new byte[newLen]; + + System.arraycopy( this.readBuf, sz, newBuf, 0, newLen ); + + this.readBuf = newBuf; + } + + totalRead += sz; + numToRead -= sz; + offset += sz; + } + + while( numToRead > 0 ) + { + byte[] rec = this.buffer.readRecord(); + + if( rec == null ) + { + // Unexpected EOF! + throw new IOException( "unexpected EOF with " + numToRead + + " bytes unread" ); + } + + int sz = numToRead; + int recLen = rec.length; + + if( recLen > sz ) + { + System.arraycopy( rec, 0, buf, offset, sz ); + + this.readBuf = new byte[recLen - sz]; + + System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz ); + } + else + { + sz = recLen; + + System.arraycopy( rec, 0, buf, offset, recLen ); + } + + totalRead += sz; + numToRead -= sz; + offset += sz; + } + + this.entryOffset += totalRead; + + return totalRead; + } + + /** + * Since we do not support marking just yet, we do nothing. + */ + public void reset() { } + + /** + * Skip bytes in the input buffer. This skips bytes in the current entry's + * data, not the entire archive, and will stop at the end of the current + * entry's data if the number to skip extends beyond that point. + * + * @param numToSkip The number of bytes to skip. + * @exception IOException Description of Exception + */ + public void skip( int numToSkip ) + throws IOException + { + + // REVIEW + // This is horribly inefficient, but it ensures that we + // properly skip over bytes via the TarBuffer... + // + byte[] skipBuf = new byte[8 * 1024]; + + for( int num = numToSkip; num > 0; ) + { + int numRead = this.read( skipBuf, 0, + ( num > skipBuf.length ? skipBuf.length + : num ) ); + + if( numRead == -1 ) + { + break; + } + + num -= numRead; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarOutputStream.java new file mode 100644 index 000000000..92914c329 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarOutputStream.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * The TarOutputStream writes a UNIX tar archive as an OutputStream. Methods are + * provided to put entries, and then write their contents by writing to this + * stream using write(). + * + * @author Timothy Gerard Endres time@ice.com + */ +public class TarOutputStream extends FilterOutputStream +{ + public final static int LONGFILE_ERROR = 0; + public final static int LONGFILE_TRUNCATE = 1; + public final static int LONGFILE_GNU = 2; + protected int longFileMode = LONGFILE_ERROR; + protected byte[] assemBuf; + protected int assemLen; + protected TarBuffer buffer; + protected int currBytes; + protected int currSize; + + protected boolean debug; + protected byte[] oneBuf; + protected byte[] recordBuf; + + public TarOutputStream( OutputStream os ) + { + this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarOutputStream( OutputStream os, int blockSize ) + { + this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarOutputStream( OutputStream os, int blockSize, int recordSize ) + { + super( os ); + + this.buffer = new TarBuffer( os, blockSize, recordSize ); + this.debug = false; + this.assemLen = 0; + this.assemBuf = new byte[recordSize]; + this.recordBuf = new byte[recordSize]; + this.oneBuf = new byte[1]; + } + + /** + * Sets the debugging flag in this stream's TarBuffer. + * + * @param debug The new BufferDebug value + */ + public void setBufferDebug( boolean debug ) + { + this.buffer.setDebug( debug ); + } + + + /** + * Sets the debugging flag. + * + * @param debugF True to turn on debugging. + */ + public void setDebug( boolean debugF ) + { + this.debug = debugF; + } + + public void setLongFileMode( int longFileMode ) + { + this.longFileMode = longFileMode; + } + + /** + * Get the record size being used by this stream's TarBuffer. + * + * @return The TarBuffer record size. + */ + public int getRecordSize() + { + return this.buffer.getRecordSize(); + } + + /** + * Ends the TAR archive and closes the underlying OutputStream. This means + * that finish() is called followed by calling the TarBuffer's close(). + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + this.finish(); + this.buffer.close(); + } + + /** + * Close an entry. This method MUST be called for all file entries that + * contain data. The reason is that we must buffer data written to the + * stream in order to satisfy the buffer's record based writes. Thus, there + * may be data fragments still being assembled that must be written to the + * output stream before this entry is closed and the next entry written. + * + * @exception IOException Description of Exception + */ + public void closeEntry() + throws IOException + { + if( this.assemLen > 0 ) + { + for( int i = this.assemLen; i < this.assemBuf.length; ++i ) + { + this.assemBuf[i] = 0; + } + + this.buffer.writeRecord( this.assemBuf ); + + this.currBytes += this.assemLen; + this.assemLen = 0; + } + + if( this.currBytes < this.currSize ) + { + throw new IOException( "entry closed at '" + this.currBytes + + "' before the '" + this.currSize + + "' bytes specified in the header were written" ); + } + } + + /** + * Ends the TAR archive without closing the underlying OutputStream. The + * result is that the EOF record of nulls is written. + * + * @exception IOException Description of Exception + */ + public void finish() + throws IOException + { + this.writeEOFRecord(); + } + + /** + * Put an entry on the output stream. This writes the entry's header record + * and positions the output stream for writing the contents of the entry. + * Once this method is called, the stream is ready for calls to write() to + * write the entry's contents. Once the contents are written, closeEntry() + * MUST be called to ensure that all buffered data is completely + * written to the output stream. + * + * @param entry The TarEntry to be written to the archive. + * @exception IOException Description of Exception + */ + public void putNextEntry( TarEntry entry ) + throws IOException + { + if( entry.getName().length() >= TarConstants.NAMELEN ) + { + + if( longFileMode == LONGFILE_GNU ) + { + // create a TarEntry for the LongLink, the contents + // of which are the entry's name + TarEntry longLinkEntry = new TarEntry( TarConstants.GNU_LONGLINK, + TarConstants.LF_GNUTYPE_LONGNAME ); + + longLinkEntry.setSize( entry.getName().length() + 1 ); + putNextEntry( longLinkEntry ); + write( entry.getName().getBytes() ); + write( 0 ); + closeEntry(); + } + else if( longFileMode != LONGFILE_TRUNCATE ) + { + throw new RuntimeException( "file name '" + entry.getName() + + "' is too long ( > " + + TarConstants.NAMELEN + " bytes)" ); + } + } + + entry.writeEntryHeader( this.recordBuf ); + this.buffer.writeRecord( this.recordBuf ); + + this.currBytes = 0; + + if( entry.isDirectory() ) + { + this.currSize = 0; + } + else + { + this.currSize = ( int )entry.getSize(); + } + } + + /** + * Writes a byte to the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @param b The byte written. + * @exception IOException Description of Exception + */ + public void write( int b ) + throws IOException + { + this.oneBuf[0] = ( byte )b; + + this.write( this.oneBuf, 0, 1 ); + } + + /** + * Writes bytes to the current tar archive entry. This method simply calls + * write( byte[], int, int ). + * + * @param wBuf The buffer to write to the archive. + * @exception IOException Description of Exception + */ + public void write( byte[] wBuf ) + throws IOException + { + this.write( wBuf, 0, wBuf.length ); + } + + /** + * Writes bytes to the current tar archive entry. This method is aware of + * the current entry and will throw an exception if you attempt to write + * bytes past the length specified for the current entry. The method is also + * (painfully) aware of the record buffering required by TarBuffer, and + * manages buffers that are not a multiple of recordsize in length, + * including assembling records from small buffers. + * + * @param wBuf The buffer to write to the archive. + * @param wOffset The offset in the buffer from which to get bytes. + * @param numToWrite The number of bytes to write. + * @exception IOException Description of Exception + */ + public void write( byte[] wBuf, int wOffset, int numToWrite ) + throws IOException + { + if( ( this.currBytes + numToWrite ) > this.currSize ) + { + throw new IOException( "request to write '" + numToWrite + + "' bytes exceeds size in header of '" + + this.currSize + "' bytes" ); + // + // We have to deal with assembly!!! + // The programmer can be writing little 32 byte chunks for all + // we know, and we must assemble complete records for writing. + // REVIEW Maybe this should be in TarBuffer? Could that help to + // eliminate some of the buffer copying. + // + } + + if( this.assemLen > 0 ) + { + if( ( this.assemLen + numToWrite ) >= this.recordBuf.length ) + { + int aLen = this.recordBuf.length - this.assemLen; + + System.arraycopy( this.assemBuf, 0, this.recordBuf, 0, + this.assemLen ); + System.arraycopy( wBuf, wOffset, this.recordBuf, + this.assemLen, aLen ); + this.buffer.writeRecord( this.recordBuf ); + + this.currBytes += this.recordBuf.length; + wOffset += aLen; + numToWrite -= aLen; + this.assemLen = 0; + } + else + { + System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen, + numToWrite ); + + wOffset += numToWrite; + this.assemLen += numToWrite; + numToWrite -= numToWrite; + } + } + + // + // When we get here we have EITHER: + // o An empty "assemble" buffer. + // o No bytes to write (numToWrite == 0) + // + while( numToWrite > 0 ) + { + if( numToWrite < this.recordBuf.length ) + { + System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen, + numToWrite ); + + this.assemLen += numToWrite; + + break; + } + + this.buffer.writeRecord( wBuf, wOffset ); + + int num = this.recordBuf.length; + + this.currBytes += num; + numToWrite -= num; + wOffset += num; + } + } + + /** + * Write an EOF (end of archive) record to the tar archive. An EOF record + * consists of a record of all zeros. + * + * @exception IOException Description of Exception + */ + private void writeEOFRecord() + throws IOException + { + for( int i = 0; i < this.recordBuf.length; ++i ) + { + this.recordBuf[i] = 0; + } + + this.buffer.writeRecord( this.recordBuf ); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarUtils.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarUtils.java new file mode 100644 index 000000000..40bf84d59 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarUtils.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; + +/** + * This class provides static utility methods to work with byte streams. + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ +public class TarUtils +{ + + /** + * Parse the checksum octal integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The integer value of the entry's checksum. + */ + public static int getCheckSumOctalBytes( long value, byte[] buf, int offset, int length ) + { + getOctalBytes( value, buf, offset, length ); + + buf[offset + length - 1] = ( byte )' '; + buf[offset + length - 2] = 0; + + return offset + length; + } + + /** + * Parse an octal long integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The long value of the octal bytes. + */ + public static int getLongOctalBytes( long value, byte[] buf, int offset, int length ) + { + byte[] temp = new byte[length + 1]; + + getOctalBytes( value, temp, 0, length + 1 ); + System.arraycopy( temp, 0, buf, offset, length ); + + return offset + length; + } + + /** + * Determine the number of bytes in an entry name. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param name Description of Parameter + * @param buf Description of Parameter + * @return The number of bytes in a header's entry name. + */ + public static int getNameBytes( StringBuffer name, byte[] buf, int offset, int length ) + { + int i; + + for( i = 0; i < length && i < name.length(); ++i ) + { + buf[offset + i] = ( byte )name.charAt( i ); + } + + for( ; i < length; ++i ) + { + buf[offset + i] = 0; + } + + return offset + length; + } + + /** + * Parse an octal integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The integer value of the octal bytes. + */ + public static int getOctalBytes( long value, byte[] buf, int offset, int length ) + { + byte[] result = new byte[length]; + int idx = length - 1; + + buf[offset + idx] = 0; + --idx; + buf[offset + idx] = ( byte )' '; + --idx; + + if( value == 0 ) + { + buf[offset + idx] = ( byte )'0'; + --idx; + } + else + { + for( long val = value; idx >= 0 && val > 0; --idx ) + { + buf[offset + idx] = ( byte )( ( byte )'0' + ( byte )( val & 7 ) ); + val = val >> 3; + } + } + + for( ; idx >= 0; --idx ) + { + buf[offset + idx] = ( byte )' '; + } + + return offset + length; + } + + /** + * Compute the checksum of a tar entry header. + * + * @param buf The tar entry's header buffer. + * @return The computed checksum. + */ + public static long computeCheckSum( byte[] buf ) + { + long sum = 0; + + for( int i = 0; i < buf.length; ++i ) + { + sum += 255 & buf[i]; + } + + return sum; + } + + /** + * Parse an entry name from a header buffer. + * + * @param header The header buffer from which to parse. + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @return The header's entry name. + */ + public static StringBuffer parseName( byte[] header, int offset, int length ) + { + StringBuffer result = new StringBuffer( length ); + int end = offset + length; + + for( int i = offset; i < end; ++i ) + { + if( header[i] == 0 ) + { + break; + } + + result.append( ( char )header[i] ); + } + + return result; + } + + /** + * Parse an octal string from a header buffer. This is used for the file + * permission mode value. + * + * @param header The header buffer from which to parse. + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @return The long value of the octal string. + */ + public static long parseOctal( byte[] header, int offset, int length ) + { + long result = 0; + boolean stillPadding = true; + int end = offset + length; + + for( int i = offset; i < end; ++i ) + { + if( header[i] == 0 ) + { + break; + } + + if( header[i] == ( byte )' ' || header[i] == '0' ) + { + if( stillPadding ) + { + continue; + } + + if( header[i] == ( byte )' ' ) + { + break; + } + } + + stillPadding = false; + result = ( result << 3 ) + ( header[i] - '0' ); + } + + return result; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/AsiExtraField.java b/proposal/myrmidon/src/main/org/apache/tools/zip/AsiExtraField.java new file mode 100644 index 000000000..e2e3c9122 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/AsiExtraField.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.zip.CRC32; +import java.util.zip.ZipException; + +/** + * Adds Unix file permission and UID/GID fields as well as symbolic link + * handling.

          + * + * This class uses the ASi extra field in the format:

          + *         Value         Size            Description
          + *         -----         ----            -----------
          + * (Unix3) 0x756e        Short           tag for this extra block type
          + *         TSize         Short           total data size for this block
          + *         CRC           Long            CRC-32 of the remaining data
          + *         Mode          Short           file permissions
          + *         SizDev        Long            symlink'd size OR major/minor dev num
          + *         UID           Short           user ID
          + *         GID           Short           group ID
          + *         (var.)        variable        symbolic link filename
          + * 
          taken from appnote.iz (Info-ZIP note, 981119) found at + * ftp://ftp.uu.net/pub/archiving/zip/doc/

          + * + * Short is two bytes and Long is four bytes in big endian byte and word order, + * device numbers are currently not supported.

          + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable +{ + + private final static ZipShort HEADER_ID = new ZipShort( 0x756E ); + + /** + * Standard Unix stat(2) file mode. + * + * @since 1.1 + */ + private int mode = 0; + /** + * User ID. + * + * @since 1.1 + */ + private int uid = 0; + /** + * Group ID. + * + * @since 1.1 + */ + private int gid = 0; + /** + * File this entry points to, if it is a symbolic link.

          + * + * empty string - if entry is not a symbolic link.

          + * + * @since 1.1 + */ + private String link = ""; + /** + * Is this an entry for a directory? + * + * @since 1.1 + */ + private boolean dirFlag = false; + + /** + * Instance used to calculate checksums. + * + * @since 1.1 + */ + private CRC32 crc = new CRC32(); + + public AsiExtraField() { } + + /** + * Indicate whether this entry is a directory. + * + * @param dirFlag The new Directory value + * @since 1.1 + */ + public void setDirectory( boolean dirFlag ) + { + this.dirFlag = dirFlag; + mode = getMode( mode ); + } + + /** + * Set the group id. + * + * @param gid The new GroupId value + * @since 1.1 + */ + public void setGroupId( int gid ) + { + this.gid = gid; + } + + /** + * Indicate that this entry is a symbolic link to the given filename. + * + * @param name Name of the file this entry links to, empty String if it is + * not a symbolic link. + * @since 1.1 + */ + public void setLinkedFile( String name ) + { + link = name; + mode = getMode( mode ); + } + + /** + * File mode of this file. + * + * @param mode The new Mode value + * @since 1.1 + */ + public void setMode( int mode ) + { + this.mode = getMode( mode ); + } + + /** + * Set the user id. + * + * @param uid The new UserId value + * @since 1.1 + */ + public void setUserId( int uid ) + { + this.uid = uid; + } + + /** + * Delegate to local file data. + * + * @return The CentralDirectoryData value + * @since 1.1 + */ + public byte[] getCentralDirectoryData() + { + return getLocalFileDataData(); + } + + /** + * Delegate to local file data. + * + * @return The CentralDirectoryLength value + * @since 1.1 + */ + public ZipShort getCentralDirectoryLength() + { + return getLocalFileDataLength(); + } + + /** + * Get the group id. + * + * @return The GroupId value + * @since 1.1 + */ + public int getGroupId() + { + return gid; + } + + /** + * The Header-ID. + * + * @return The HeaderId value + * @since 1.1 + */ + public ZipShort getHeaderId() + { + return HEADER_ID; + } + + /** + * Name of linked file + * + * @return name of the file this entry links to if it is a symbolic link, + * the empty string otherwise. + * @since 1.1 + */ + public String getLinkedFile() + { + return link; + } + + /** + * The actual data to put into local file data - without Header-ID or length + * specifier. + * + * @return The LocalFileDataData value + * @since 1.1 + */ + public byte[] getLocalFileDataData() + { + // CRC will be added later + byte[] data = new byte[getLocalFileDataLength().getValue() - 4]; + System.arraycopy( ( new ZipShort( getMode() ) ).getBytes(), 0, data, 0, 2 ); + + byte[] linkArray = getLinkedFile().getBytes(); + System.arraycopy( ( new ZipLong( linkArray.length ) ).getBytes(), + 0, data, 2, 4 ); + + System.arraycopy( ( new ZipShort( getUserId() ) ).getBytes(), + 0, data, 6, 2 ); + System.arraycopy( ( new ZipShort( getGroupId() ) ).getBytes(), + 0, data, 8, 2 ); + + System.arraycopy( linkArray, 0, data, 10, linkArray.length ); + + crc.reset(); + crc.update( data ); + long checksum = crc.getValue(); + + byte[] result = new byte[data.length + 4]; + System.arraycopy( ( new ZipLong( checksum ) ).getBytes(), 0, result, 0, 4 ); + System.arraycopy( data, 0, result, 4, data.length ); + return result; + } + + /** + * Length of the extra field in the local file data - without Header-ID or + * length specifier. + * + * @return The LocalFileDataLength value + * @since 1.1 + */ + public ZipShort getLocalFileDataLength() + { + return new ZipShort( 4// CRC + + 2// Mode + + 4// SizDev + + 2// UID + + 2// GID + + getLinkedFile().getBytes().length ); + } + + /** + * File mode of this file. + * + * @return The Mode value + * @since 1.1 + */ + public int getMode() + { + return mode; + } + + /** + * Get the user id. + * + * @return The UserId value + * @since 1.1 + */ + public int getUserId() + { + return uid; + } + + /** + * Is this entry a directory? + * + * @return The Directory value + * @since 1.1 + */ + public boolean isDirectory() + { + return dirFlag && !isLink(); + } + + /** + * Is this entry a symbolic link? + * + * @return The Link value + * @since 1.1 + */ + public boolean isLink() + { + return getLinkedFile().length() != 0; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param data Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public void parseFromLocalFileData( byte[] data, int offset, int length ) + throws ZipException + { + + long givenChecksum = ( new ZipLong( data, offset ) ).getValue(); + byte[] tmp = new byte[length - 4]; + System.arraycopy( data, offset + 4, tmp, 0, length - 4 ); + crc.reset(); + crc.update( tmp ); + long realChecksum = crc.getValue(); + if( givenChecksum != realChecksum ) + { + throw new ZipException( "bad CRC checksum " + + Long.toHexString( givenChecksum ) + + " instead of " + + Long.toHexString( realChecksum ) ); + } + + int newMode = ( new ZipShort( tmp, 0 ) ).getValue(); + byte[] linkArray = new byte[( int )( new ZipLong( tmp, 2 ) ).getValue()]; + uid = ( new ZipShort( tmp, 6 ) ).getValue(); + gid = ( new ZipShort( tmp, 8 ) ).getValue(); + + if( linkArray.length == 0 ) + { + link = ""; + } + else + { + System.arraycopy( tmp, 10, linkArray, 0, linkArray.length ); + link = new String( linkArray ); + } + setDirectory( ( newMode & DIR_FLAG ) != 0 ); + setMode( newMode ); + } + + /** + * Get the file mode for given permissions with the correct file type. + * + * @param mode Description of Parameter + * @return The Mode value + * @since 1.1 + */ + protected int getMode( int mode ) + { + int type = FILE_FLAG; + if( isLink() ) + { + type = LINK_FLAG; + } + else if( isDirectory() ) + { + type = DIR_FLAG; + } + return type | ( mode & PERM_MASK ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ExtraFieldUtils.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ExtraFieldUtils.java new file mode 100644 index 000000000..0b97ce6a7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ExtraFieldUtils.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.ZipException; + +/** + * ZipExtraField related methods + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ExtraFieldUtils +{ + + /** + * Static registry of known extra fields. + * + * @since 1.1 + */ + private static Hashtable implementations; + + static + { + implementations = new Hashtable(); + register( AsiExtraField.class ); + } + + /** + * Create an instance of the approriate ExtraField, falls back to {@link + * UnrecognizedExtraField UnrecognizedExtraField}. + * + * @param headerId Description of Parameter + * @return Description of the Returned Value + * @exception InstantiationException Description of Exception + * @exception IllegalAccessException Description of Exception + * @since 1.1 + */ + public static ZipExtraField createExtraField( ZipShort headerId ) + throws InstantiationException, IllegalAccessException + { + Class c = ( Class )implementations.get( headerId ); + if( c != null ) + { + return ( ZipExtraField )c.newInstance(); + } + UnrecognizedExtraField u = new UnrecognizedExtraField(); + u.setHeaderId( headerId ); + return u; + } + + /** + * Merges the central directory fields of the given ZipExtraFields. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public static byte[] mergeCentralDirectoryData( ZipExtraField[] data ) + { + int sum = 4 * data.length; + for( int i = 0; i < data.length; i++ ) + { + sum += data[i].getCentralDirectoryLength().getValue(); + } + byte[] result = new byte[sum]; + int start = 0; + for( int i = 0; i < data.length; i++ ) + { + System.arraycopy( data[i].getHeaderId().getBytes(), + 0, result, start, 2 ); + System.arraycopy( data[i].getCentralDirectoryLength().getBytes(), + 0, result, start + 2, 2 ); + byte[] local = data[i].getCentralDirectoryData(); + System.arraycopy( local, 0, result, start + 4, local.length ); + start += ( local.length + 4 ); + } + return result; + } + + /** + * Merges the local file data fields of the given ZipExtraFields. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public static byte[] mergeLocalFileDataData( ZipExtraField[] data ) + { + int sum = 4 * data.length; + for( int i = 0; i < data.length; i++ ) + { + sum += data[i].getLocalFileDataLength().getValue(); + } + byte[] result = new byte[sum]; + int start = 0; + for( int i = 0; i < data.length; i++ ) + { + System.arraycopy( data[i].getHeaderId().getBytes(), + 0, result, start, 2 ); + System.arraycopy( data[i].getLocalFileDataLength().getBytes(), + 0, result, start + 2, 2 ); + byte[] local = data[i].getLocalFileDataData(); + System.arraycopy( local, 0, result, start + 4, local.length ); + start += ( local.length + 4 ); + } + return result; + } + + /** + * Split the array into ExtraFields and populate them with the give data. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @exception ZipException Description of Exception + * @since 1.1 + */ + public static ZipExtraField[] parse( byte[] data ) + throws ZipException + { + Vector v = new Vector(); + int start = 0; + while( start <= data.length - 4 ) + { + ZipShort headerId = new ZipShort( data, start ); + int length = ( new ZipShort( data, start + 2 ) ).getValue(); + if( start + 4 + length > data.length ) + { + throw new ZipException( "data starting at " + start + " is in unknown format" ); + } + try + { + ZipExtraField ze = createExtraField( headerId ); + ze.parseFromLocalFileData( data, start + 4, length ); + v.addElement( ze ); + } + catch( InstantiationException ie ) + { + throw new ZipException( ie.getMessage() ); + } + catch( IllegalAccessException iae ) + { + throw new ZipException( iae.getMessage() ); + } + start += ( length + 4 ); + } + if( start != data.length ) + {// array not exhausted + throw new ZipException( "data starting at " + start + " is in unknown format" ); + } + + ZipExtraField[] result = new ZipExtraField[v.size()]; + v.copyInto( result ); + return result; + } + + /** + * Register a ZipExtraField implementation.

          + * + * The given class must have a no-arg constructor and implement the {@link + * ZipExtraField ZipExtraField interface}.

          + * + * @param c Description of Parameter + * @since 1.1 + */ + public static void register( Class c ) + { + try + { + ZipExtraField ze = ( ZipExtraField )c.newInstance(); + implementations.put( ze.getHeaderId(), c ); + } + catch( ClassCastException cc ) + { + throw new RuntimeException( c + + " doesn\'t implement ZipExtraField" ); + } + catch( InstantiationException ie ) + { + throw new RuntimeException( c + " is not a concrete class" ); + } + catch( IllegalAccessException ie ) + { + throw new RuntimeException( c + + "\'s no-arg constructor is not public" ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/UnixStat.java b/proposal/myrmidon/src/main/org/apache/tools/zip/UnixStat.java new file mode 100644 index 000000000..e238ed32e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/UnixStat.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Constants from stat.h on Unix systems. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface UnixStat +{ + + /** + * Bits used for permissions (and sticky bit) + * + * @since 1.1 + */ + int PERM_MASK = 07777; + /** + * Indicates symbolic links. + * + * @since 1.1 + */ + int LINK_FLAG = 0120000; + /** + * Indicates plain files. + * + * @since 1.1 + */ + int FILE_FLAG = 0100000; + /** + * Indicates directories. + * + * @since 1.1 + */ + int DIR_FLAG = 040000; + + // ---------------------------------------------------------- + // somewhat arbitrary choices that are quite common for shared + // installations + // ----------------------------------------------------------- + + /** + * Default permissions for symbolic links. + * + * @since 1.1 + */ + int DEFAULT_LINK_PERM = 0777; + /** + * Default permissions for directories. + * + * @since 1.1 + */ + int DEFAULT_DIR_PERM = 0755; + /** + * Default permissions for plain files. + * + * @since 1.1 + */ + int DEFAULT_FILE_PERM = 0644; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/UnrecognizedExtraField.java b/proposal/myrmidon/src/main/org/apache/tools/zip/UnrecognizedExtraField.java new file mode 100644 index 000000000..092e1c60b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/UnrecognizedExtraField.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Simple placeholder for all those extra fields we don't want to deal with.

          + * + * Assumes local file data and central directory entries are identical - unless + * told the opposite.

          + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class UnrecognizedExtraField implements ZipExtraField +{ + + /** + * Extra field data in central directory - without Header-ID or length + * specifier. + * + * @since 1.1 + */ + private byte[] centralData; + + /** + * The Header-ID. + * + * @since 1.1 + */ + private ZipShort headerId; + + /** + * Extra field data in local file data - without Header-ID or length + * specifier. + * + * @since 1.1 + */ + private byte[] localData; + + public void setCentralDirectoryData( byte[] data ) + { + centralData = data; + } + + public void setHeaderId( ZipShort headerId ) + { + this.headerId = headerId; + } + + public void setLocalFileDataData( byte[] data ) + { + localData = data; + } + + public byte[] getCentralDirectoryData() + { + if( centralData != null ) + { + return centralData; + } + return getLocalFileDataData(); + } + + public ZipShort getCentralDirectoryLength() + { + if( centralData != null ) + { + return new ZipShort( centralData.length ); + } + return getLocalFileDataLength(); + } + + public ZipShort getHeaderId() + { + return headerId; + } + + public byte[] getLocalFileDataData() + { + return localData; + } + + public ZipShort getLocalFileDataLength() + { + return new ZipShort( localData.length ); + } + + public void parseFromLocalFileData( byte[] data, int offset, int length ) + { + byte[] tmp = new byte[length]; + System.arraycopy( data, offset, tmp, 0, length ); + setLocalFileDataData( tmp ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ZipEntry.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipEntry.java new file mode 100644 index 000000000..078203ae2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipEntry.java @@ -0,0 +1,435 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Vector; +import java.util.zip.ZipException; + +/** + * Extension that adds better handling of extra fields and provides access to + * the internal and external file attributes. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipEntry extends java.util.zip.ZipEntry +{ + + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static Method setCompressedSizeMethod = null; + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static Object lockReflection = new Object(); + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static boolean triedToGetMethod = false; + + private int internalAttributes = 0; + private long externalAttributes = 0; + private Vector extraFields = new Vector(); + + /** + * Helper for JDK 1.1 <-> 1.2 incompatibility. + * + * @since 1.2 + */ + private Long compressedSize = null; + + /** + * Creates a new zip entry with the specified name. + * + * @param name Description of Parameter + * @since 1.1 + */ + public ZipEntry( String name ) + { + super( name ); + } + + /** + * Creates a new zip entry with fields taken from the specified zip entry. + * + * @param entry Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public ZipEntry( java.util.zip.ZipEntry entry ) + throws ZipException + { + /* + * REVISIT: call super(entry) instead of this stuff in Ant2, + * "copy constructor" has not been available in JDK 1.1 + */ + super( entry.getName() ); + + setComment( entry.getComment() ); + setMethod( entry.getMethod() ); + setTime( entry.getTime() ); + + long size = entry.getSize(); + if( size > 0 ) + { + setSize( size ); + } + long cSize = entry.getCompressedSize(); + if( cSize > 0 ) + { + setComprSize( cSize ); + } + long crc = entry.getCrc(); + if( crc > 0 ) + { + setCrc( crc ); + } + + byte[] extra = entry.getExtra(); + if( extra != null ) + { + setExtraFields( ExtraFieldUtils.parse( extra ) ); + } + else + { + // initializes extra data to an empty byte array + setExtra(); + } + } + + /** + * Creates a new zip entry with fields taken from the specified zip entry. + * + * @param entry Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public ZipEntry( ZipEntry entry ) + throws ZipException + { + this( ( java.util.zip.ZipEntry )entry ); + setInternalAttributes( entry.getInternalAttributes() ); + setExternalAttributes( entry.getExternalAttributes() ); + setExtraFields( entry.getExtraFields() ); + } + + /** + * Try to get a handle to the setCompressedSize method. + * + * @since 1.2 + */ + private static void checkSCS() + { + if( !triedToGetMethod ) + { + synchronized( lockReflection ) + { + triedToGetMethod = true; + try + { + setCompressedSizeMethod = + java.util.zip.ZipEntry.class.getMethod( "setCompressedSize", + new Class[]{Long.TYPE} ); + } + catch( NoSuchMethodException nse ) + { + } + } + } + } + + /** + * Are we running JDK 1.2 or higher? + * + * @return Description of the Returned Value + * @since 1.2 + */ + private static boolean haveSetCompressedSize() + { + checkSCS(); + return setCompressedSizeMethod != null; + } + + /** + * Invoke setCompressedSize via reflection. + * + * @param ze Description of Parameter + * @param size Description of Parameter + * @since 1.2 + */ + private static void performSetCompressedSize( ZipEntry ze, long size ) + { + Long[] s = {new Long( size )}; + try + { + setCompressedSizeMethod.invoke( ze, s ); + } + catch( InvocationTargetException ite ) + { + Throwable nested = ite.getTargetException(); + throw new RuntimeException( "Exception setting the compressed size " + + "of " + ze + ": " + + nested.getMessage() ); + } + catch( Throwable other ) + { + throw new RuntimeException( "Exception setting the compressed size " + + "of " + ze + ": " + + other.getMessage() ); + } + } + + /** + * Make this class work in JDK 1.1 like a 1.2 class.

          + * + * This either stores the size for later usage or invokes setCompressedSize + * via reflection.

          + * + * @param size The new ComprSize value + * @since 1.2 + */ + public void setComprSize( long size ) + { + if( haveSetCompressedSize() ) + { + performSetCompressedSize( this, size ); + } + else + { + compressedSize = new Long( size ); + } + } + + /** + * Sets the external file attributes. + * + * @param value The new ExternalAttributes value + * @since 1.1 + */ + public void setExternalAttributes( long value ) + { + externalAttributes = value; + } + + /** + * Throws an Exception if extra data cannot be parsed into extra fields. + * + * @param extra The new Extra value + * @exception RuntimeException Description of Exception + * @since 1.1 + */ + public void setExtra( byte[] extra ) + throws RuntimeException + { + try + { + setExtraFields( ExtraFieldUtils.parse( extra ) ); + } + catch( Exception e ) + { + throw new RuntimeException( e.getMessage() ); + } + } + + /** + * Replaces all currently attached extra fields with the new array. + * + * @param fields The new ExtraFields value + * @since 1.1 + */ + public void setExtraFields( ZipExtraField[] fields ) + { + extraFields.removeAllElements(); + for( int i = 0; i < fields.length; i++ ) + { + extraFields.addElement( fields[i] ); + } + setExtra(); + } + + /** + * Sets the internal file attributes. + * + * @param value The new InternalAttributes value + * @since 1.1 + */ + public void setInternalAttributes( int value ) + { + internalAttributes = value; + } + + /** + * Retrieves the extra data for the central directory. + * + * @return The CentralDirectoryExtra value + * @since 1.1 + */ + public byte[] getCentralDirectoryExtra() + { + return ExtraFieldUtils.mergeCentralDirectoryData( getExtraFields() ); + } + + /** + * Override to make this class work in JDK 1.1 like a 1.2 class. + * + * @return The CompressedSize value + * @since 1.2 + */ + public long getCompressedSize() + { + if( compressedSize != null ) + { + // has been set explicitly and we are running in a 1.1 VM + return compressedSize.longValue(); + } + return super.getCompressedSize(); + } + + /** + * Retrieves the external file attributes. + * + * @return The ExternalAttributes value + * @since 1.1 + */ + public long getExternalAttributes() + { + return externalAttributes; + } + + /** + * Retrieves extra fields. + * + * @return The ExtraFields value + * @since 1.1 + */ + public ZipExtraField[] getExtraFields() + { + ZipExtraField[] result = new ZipExtraField[extraFields.size()]; + extraFields.copyInto( result ); + return result; + } + + /** + * Retrieves the internal file attributes. + * + * @return The InternalAttributes value + * @since 1.1 + */ + public int getInternalAttributes() + { + return internalAttributes; + } + + /** + * Retrieves the extra data for the local file data. + * + * @return The LocalFileDataExtra value + * @since 1.1 + */ + public byte[] getLocalFileDataExtra() + { + byte[] extra = getExtra(); + return extra != null ? extra : new byte[0]; + } + + /** + * Adds an extra fields - replacing an already present extra field of the + * same type. + * + * @param ze The feature to be added to the ExtraField attribute + * @since 1.1 + */ + public void addExtraField( ZipExtraField ze ) + { + ZipShort type = ze.getHeaderId(); + boolean done = false; + for( int i = 0; !done && i < extraFields.size(); i++ ) + { + if( ( ( ZipExtraField )extraFields.elementAt( i ) ).getHeaderId().equals( type ) ) + { + extraFields.setElementAt( ze, i ); + done = true; + } + } + if( !done ) + { + extraFields.addElement( ze ); + } + setExtra(); + } + + /** + * Overwrite clone + * + * @return Description of the Returned Value + * @since 1.1 + */ + public Object clone() + { + ZipEntry e = null; + try + { + e = new ZipEntry( ( java.util.zip.ZipEntry )super.clone() ); + } + catch( Exception ex ) + { + // impossible as extra data is in correct format + ex.printStackTrace(); + } + e.setInternalAttributes( getInternalAttributes() ); + e.setExternalAttributes( getExternalAttributes() ); + e.setExtraFields( getExtraFields() ); + return e; + } + + /** + * Remove an extra fields. + * + * @param type Description of Parameter + * @since 1.1 + */ + public void removeExtraField( ZipShort type ) + { + boolean done = false; + for( int i = 0; !done && i < extraFields.size(); i++ ) + { + if( ( ( ZipExtraField )extraFields.elementAt( i ) ).getHeaderId().equals( type ) ) + { + extraFields.removeElementAt( i ); + done = true; + } + } + if( !done ) + { + throw new java.util.NoSuchElementException(); + } + setExtra(); + } + + /** + * Unfortunately {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} seems to access the extra data directly, + * so overriding getExtra doesn't help - we need to modify super's data + * directly. + * + * @since 1.1 + */ + protected void setExtra() + { + super.setExtra( ExtraFieldUtils.mergeLocalFileDataData( getExtraFields() ) ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ZipExtraField.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipExtraField.java new file mode 100644 index 000000000..534530706 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipExtraField.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.zip.ZipException; + +/** + * General format of extra field data.

          + * + * Extra fields usually appear twice per file, once in the local file data and + * once in the central directory. Usually they are the same, but they don't have + * to be. {@link java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} + * will only use the local file data in both places.

          + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface ZipExtraField +{ + + /** + * The Header-ID. + * + * @return The HeaderId value + * @since 1.1 + */ + ZipShort getHeaderId(); + + /** + * Length of the extra field in the local file data - without Header-ID or + * length specifier. + * + * @return The LocalFileDataLength value + * @since 1.1 + */ + ZipShort getLocalFileDataLength(); + + /** + * Length of the extra field in the central directory - without Header-ID or + * length specifier. + * + * @return The CentralDirectoryLength value + * @since 1.1 + */ + ZipShort getCentralDirectoryLength(); + + /** + * The actual data to put into local file data - without Header-ID or length + * specifier. + * + * @return The LocalFileDataData value + * @since 1.1 + */ + byte[] getLocalFileDataData(); + + /** + * The actual data to put central directory - without Header-ID or length + * specifier. + * + * @return The CentralDirectoryData value + * @since 1.1 + */ + byte[] getCentralDirectoryData(); + + /** + * Populate data from this array as if it was in local file data. + * + * @param data Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + void parseFromLocalFileData( byte[] data, int offset, int length ) + throws ZipException; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ZipLong.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipLong.java new file mode 100644 index 000000000..4b3573770 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipLong.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Utility class that represents a four byte integer with conversion rules for + * the big endian byte order of ZIP files. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipLong implements Cloneable +{ + + private long value; + + /** + * Create instance from a number. + * + * @param value Description of Parameter + * @since 1.1 + */ + public ZipLong( long value ) + { + this.value = value; + } + + /** + * Create instance from bytes. + * + * @param bytes Description of Parameter + * @since 1.1 + */ + public ZipLong( byte[] bytes ) + { + this( bytes, 0 ); + } + + /** + * Create instance from the four bytes starting at offset. + * + * @param bytes Description of Parameter + * @param offset Description of Parameter + * @since 1.1 + */ + public ZipLong( byte[] bytes, int offset ) + { + value = ( bytes[offset + 3] << 24 ) & 0xFF000000l; + value += ( bytes[offset + 2] << 16 ) & 0xFF0000; + value += ( bytes[offset + 1] << 8 ) & 0xFF00; + value += ( bytes[offset] & 0xFF ); + } + + /** + * Get value as two bytes in big endian byte order. + * + * @return The Bytes value + * @since 1.1 + */ + public byte[] getBytes() + { + byte[] result = new byte[4]; + result[0] = ( byte )( ( value & 0xFF ) ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + result[2] = ( byte )( ( value & 0xFF0000 ) >> 16 ); + result[3] = ( byte )( ( value & 0xFF000000l ) >> 24 ); + return result; + } + + /** + * Get value as Java int. + * + * @return The Value value + * @since 1.1 + */ + public long getValue() + { + return value; + } + + /** + * Override to make two instances with same value equal. + * + * @param o Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public boolean equals( Object o ) + { + if( o == null || !( o instanceof ZipLong ) ) + { + return false; + } + return value == ( ( ZipLong )o ).getValue(); + } + + /** + * Override to make two instances with same value equal. + * + * @return Description of the Returned Value + * @since 1.1 + */ + public int hashCode() + { + return ( int )value; + } + +}// ZipLong diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ZipOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipOutputStream.java new file mode 100644 index 000000000..5ab382814 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipOutputStream.java @@ -0,0 +1,712 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Date; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.ZipException; + +/** + * Reimplementation of {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} that does handle the extended functionality of + * this package, especially internal/external file attributes and extra fields + * with different layouts for local file data and central directory entries.

          + * + * This implementation will use a Data Descriptor to store size and CRC + * information for DEFLATED entries, this means, you don't need to calculate + * them yourself. Unfortunately this is not possible for the STORED method, here + * setting the CRC and uncompressed size information is required before {@link + * #putNextEntry putNextEntry} will be called.

          + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipOutputStream extends DeflaterOutputStream +{ + + /** + * Helper, a 0 as ZipShort. + * + * @since 1.1 + */ + private final static byte[] ZERO = {0, 0}; + + /** + * Helper, a 0 as ZipLong. + * + * @since 1.1 + */ + private final static byte[] LZERO = {0, 0, 0, 0}; + + /** + * Compression method for deflated entries. + * + * @since 1.1 + */ + public final static int DEFLATED = ZipEntry.DEFLATED; + + /** + * Compression method for deflated entries. + * + * @since 1.1 + */ + public final static int STORED = ZipEntry.STORED; + + /* + * Various ZIP constants + */ + /** + * local file header signature + * + * @since 1.1 + */ + protected final static ZipLong LFH_SIG = new ZipLong( 0X04034B50L ); + /** + * data descriptor signature + * + * @since 1.1 + */ + protected final static ZipLong DD_SIG = new ZipLong( 0X08074B50L ); + /** + * central file header signature + * + * @since 1.1 + */ + protected final static ZipLong CFH_SIG = new ZipLong( 0X02014B50L ); + /** + * end of central dir signature + * + * @since 1.1 + */ + protected final static ZipLong EOCD_SIG = new ZipLong( 0X06054B50L ); + + /** + * Smallest date/time ZIP can handle. + * + * @since 1.1 + */ + private final static ZipLong DOS_TIME_MIN = new ZipLong( 0x00002100L ); + + /** + * The file comment. + * + * @since 1.1 + */ + private String comment = ""; + + /** + * Compression level for next entry. + * + * @since 1.1 + */ + private int level = Deflater.DEFAULT_COMPRESSION; + + /** + * Default compression method for next entry. + * + * @since 1.1 + */ + private int method = DEFLATED; + + /** + * List of ZipEntries written so far. + * + * @since 1.1 + */ + private Vector entries = new Vector(); + + /** + * CRC instance to avoid parsing DEFLATED data twice. + * + * @since 1.1 + */ + private CRC32 crc = new CRC32(); + + /** + * Count the bytes written to out. + * + * @since 1.1 + */ + private long written = 0; + + /** + * Data for current entry started here. + * + * @since 1.1 + */ + private long dataStart = 0; + + /** + * Start of central directory. + * + * @since 1.1 + */ + private ZipLong cdOffset = new ZipLong( 0 ); + + /** + * Length of central directory. + * + * @since 1.1 + */ + private ZipLong cdLength = new ZipLong( 0 ); + + /** + * Holds the offsets of the LFH starts for each entry + * + * @since 1.1 + */ + private Hashtable offsets = new Hashtable(); + + /** + * The encoding to use for filenames and the file comment.

          + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * . Defaults to the platform's default character encoding.

          + * + * @since 1.3 + */ + private String encoding = null; + + /** + * Current entry. + * + * @since 1.1 + */ + private ZipEntry entry; + + /** + * Creates a new ZIP OutputStream filtering the underlying stream. + * + * @param out Description of Parameter + * @since 1.1 + */ + public ZipOutputStream( OutputStream out ) + { + super( out, new Deflater( Deflater.DEFAULT_COMPRESSION, true ) ); + } + + /** + * Convert a Date object to a DOS date/time field.

          + * + * Stolen from InfoZip's fileio.c

          + * + * @param time Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + protected static ZipLong toDosTime( Date time ) + { + int year = time.getYear() + 1900; + int month = time.getMonth() + 1; + if( year < 1980 ) + { + return DOS_TIME_MIN; + } + long value = ( ( year - 1980 ) << 25 ) + | ( month << 21 ) + | ( time.getDate() << 16 ) + | ( time.getHours() << 11 ) + | ( time.getMinutes() << 5 ) + | ( time.getSeconds() >> 1 ); + + byte[] result = new byte[4]; + result[0] = ( byte )( ( value & 0xFF ) ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + result[2] = ( byte )( ( value & 0xFF0000 ) >> 16 ); + result[3] = ( byte )( ( value & 0xFF000000l ) >> 24 ); + return new ZipLong( result ); + } + + /** + * Set the file comment. + * + * @param comment The new Comment value + * @since 1.1 + */ + public void setComment( String comment ) + { + this.comment = comment; + } + + /** + * The encoding to use for filenames and the file comment.

          + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * . Defaults to the platform's default character encoding.

          + * + * @param encoding The new Encoding value + * @since 1.3 + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Sets the compression level for subsequent entries.

          + * + * Default is Deflater.DEFAULT_COMPRESSION.

          + * + * @param level The new Level value + * @since 1.1 + */ + public void setLevel( int level ) + { + this.level = level; + } + + /** + * Sets the default compression method for subsequent entries.

          + * + * Default is DEFLATED.

          + * + * @param method The new Method value + * @since 1.1 + */ + public void setMethod( int method ) + { + this.method = method; + } + + /** + * The encoding to use for filenames and the file comment. + * + * @return null if using the platform's default character encoding. + * @since 1.3 + */ + public String getEncoding() + { + return encoding; + } + + /** + * Writes all necessary data for this entry. + * + * @exception IOException Description of Exception + * @since 1.1 + */ + public void closeEntry() + throws IOException + { + if( entry == null ) + { + return; + } + + long realCrc = crc.getValue(); + crc.reset(); + + if( entry.getMethod() == DEFLATED ) + { + def.finish(); + while( !def.finished() ) + { + deflate(); + } + + entry.setSize( def.getTotalIn() ); + entry.setComprSize( def.getTotalOut() ); + entry.setCrc( realCrc ); + + def.reset(); + + written += entry.getCompressedSize(); + } + else + { + if( entry.getCrc() != realCrc ) + { + throw new ZipException( "bad CRC checksum for entry " + + entry.getName() + ": " + + Long.toHexString( entry.getCrc() ) + + " instead of " + + Long.toHexString( realCrc ) ); + } + + if( entry.getSize() != written - dataStart ) + { + throw new ZipException( "bad size for entry " + + entry.getName() + ": " + + entry.getSize() + + " instead of " + + ( written - dataStart ) ); + } + + } + + writeDataDescriptor( entry ); + entry = null; + } + + /* + * Found out by experiment, that DeflaterOutputStream.close() + * will call finish() - so we don't need to override close + * ourselves. + */ + /** + * Finishs writing the contents and closes this as well as the underlying + * stream. + * + * @exception IOException Description of Exception + * @since 1.1 + */ + public void finish() + throws IOException + { + closeEntry(); + cdOffset = new ZipLong( written ); + for( int i = 0; i < entries.size(); i++ ) + { + writeCentralFileHeader( ( ZipEntry )entries.elementAt( i ) ); + } + cdLength = new ZipLong( written - cdOffset.getValue() ); + writeCentralDirectoryEnd(); + offsets.clear(); + entries.removeAllElements(); + } + + /** + * Begin writing next entry. + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + public void putNextEntry( ZipEntry ze ) + throws IOException + { + closeEntry(); + + entry = ze; + entries.addElement( entry ); + + if( entry.getMethod() == -1 ) + {// not specified + entry.setMethod( method ); + } + + if( entry.getTime() == -1 ) + {// not specified + entry.setTime( System.currentTimeMillis() ); + } + + if( entry.getMethod() == STORED ) + { + if( entry.getSize() == -1 ) + { + throw new ZipException( "uncompressed size is required for STORED method" ); + } + if( entry.getCrc() == -1 ) + { + throw new ZipException( "crc checksum is required for STORED method" ); + } + entry.setComprSize( entry.getSize() ); + } + else + { + def.setLevel( level ); + } + writeLocalFileHeader( entry ); + } + + /** + * Writes bytes to ZIP entry.

          + * + * Override is necessary to support STORED entries, as well as calculationg + * CRC automatically for DEFLATED entries.

          + * + * @param b Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception IOException Description of Exception + */ + public void write( byte[] b, int offset, int length ) + throws IOException + { + if( entry.getMethod() == DEFLATED ) + { + super.write( b, offset, length ); + } + else + { + out.write( b, offset, length ); + written += length; + } + crc.update( b, offset, length ); + } + + /** + * Retrieve the bytes for the given String in the encoding set for this + * Stream. + * + * @param name Description of Parameter + * @return The Bytes value + * @exception ZipException Description of Exception + * @since 1.3 + */ + protected byte[] getBytes( String name ) + throws ZipException + { + if( encoding == null ) + { + return name.getBytes(); + } + else + { + try + { + return name.getBytes( encoding ); + } + catch( UnsupportedEncodingException uee ) + { + throw new ZipException( uee.getMessage() ); + } + } + } + + /** + * Writes the "End of central dir record" + * + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeCentralDirectoryEnd() + throws IOException + { + out.write( EOCD_SIG.getBytes() ); + + // disk numbers + out.write( ZERO ); + out.write( ZERO ); + + // number of entries + byte[] num = ( new ZipShort( entries.size() ) ).getBytes(); + out.write( num ); + out.write( num ); + + // length and location of CD + out.write( cdLength.getBytes() ); + out.write( cdOffset.getBytes() ); + + // ZIP file comment + byte[] data = getBytes( comment ); + out.write( ( new ZipShort( data.length ) ).getBytes() ); + out.write( data ); + } + + /** + * Writes the central file header entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeCentralFileHeader( ZipEntry ze ) + throws IOException + { + out.write( CFH_SIG.getBytes() ); + written += 4; + + // version made by + out.write( ( new ZipShort( 20 ) ).getBytes() ); + written += 2; + + // version needed to extract + // general purpose bit flag + if( ze.getMethod() == DEFLATED ) + { + // requires version 2 as we are going to store length info + // in the data descriptor + out.write( ( new ZipShort( 20 ) ).getBytes() ); + + // bit3 set to signal, we use a data descriptor + out.write( ( new ZipShort( 8 ) ).getBytes() ); + } + else + { + out.write( ( new ZipShort( 10 ) ).getBytes() ); + out.write( ZERO ); + } + written += 4; + + // compression method + out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() ); + written += 2; + + // last mod. time and date + out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() ); + written += 4; + + // CRC + // compressed length + // uncompressed length + out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getCompressedSize() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + written += 12; + + // file name length + byte[] name = getBytes( ze.getName() ); + out.write( ( new ZipShort( name.length ) ).getBytes() ); + written += 2; + + // extra field length + byte[] extra = ze.getCentralDirectoryExtra(); + out.write( ( new ZipShort( extra.length ) ).getBytes() ); + written += 2; + + // file comment length + String comm = ze.getComment(); + if( comm == null ) + { + comm = ""; + } + byte[] comment = getBytes( comm ); + out.write( ( new ZipShort( comment.length ) ).getBytes() ); + written += 2; + + // disk number start + out.write( ZERO ); + written += 2; + + // internal file attributes + out.write( ( new ZipShort( ze.getInternalAttributes() ) ).getBytes() ); + written += 2; + + // external file attributes + out.write( ( new ZipLong( ze.getExternalAttributes() ) ).getBytes() ); + written += 4; + + // relative offset of LFH + out.write( ( ( ZipLong )offsets.get( ze ) ).getBytes() ); + written += 4; + + // file name + out.write( name ); + written += name.length; + + // extra field + out.write( extra ); + written += extra.length; + + // file comment + out.write( comment ); + written += comment.length; + } + + /** + * Writes the data descriptor entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeDataDescriptor( ZipEntry ze ) + throws IOException + { + if( ze.getMethod() != DEFLATED ) + { + return; + } + out.write( DD_SIG.getBytes() ); + out.write( ( new ZipLong( entry.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( entry.getCompressedSize() ) ).getBytes() ); + out.write( ( new ZipLong( entry.getSize() ) ).getBytes() ); + written += 16; + } + + /** + * Writes the local file header entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeLocalFileHeader( ZipEntry ze ) + throws IOException + { + offsets.put( ze, new ZipLong( written ) ); + + out.write( LFH_SIG.getBytes() ); + written += 4; + + // version needed to extract + // general purpose bit flag + if( ze.getMethod() == DEFLATED ) + { + // requires version 2 as we are going to store length info + // in the data descriptor + out.write( ( new ZipShort( 20 ) ).getBytes() ); + + // bit3 set to signal, we use a data descriptor + out.write( ( new ZipShort( 8 ) ).getBytes() ); + } + else + { + out.write( ( new ZipShort( 10 ) ).getBytes() ); + out.write( ZERO ); + } + written += 4; + + // compression method + out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() ); + written += 2; + + // last mod. time and date + out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() ); + written += 4; + + // CRC + // compressed length + // uncompressed length + if( ze.getMethod() == DEFLATED ) + { + out.write( LZERO ); + out.write( LZERO ); + out.write( LZERO ); + } + else + { + out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + } + written += 12; + + // file name length + byte[] name = getBytes( ze.getName() ); + out.write( ( new ZipShort( name.length ) ).getBytes() ); + written += 2; + + // extra field length + byte[] extra = ze.getLocalFileDataExtra(); + out.write( ( new ZipShort( extra.length ) ).getBytes() ); + written += 2; + + // file name + out.write( name ); + written += name.length; + + // extra field + out.write( extra ); + written += extra.length; + + dataStart = written; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ZipShort.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipShort.java new file mode 100644 index 000000000..b06f040e1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipShort.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Utility class that represents a two byte integer with conversion rules for + * the big endian byte order of ZIP files. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipShort implements Cloneable +{ + + private int value; + + /** + * Create instance from a number. + * + * @param value Description of Parameter + * @since 1.1 + */ + public ZipShort( int value ) + { + this.value = value; + } + + /** + * Create instance from bytes. + * + * @param bytes Description of Parameter + * @since 1.1 + */ + public ZipShort( byte[] bytes ) + { + this( bytes, 0 ); + } + + /** + * Create instance from the two bytes starting at offset. + * + * @param bytes Description of Parameter + * @param offset Description of Parameter + * @since 1.1 + */ + public ZipShort( byte[] bytes, int offset ) + { + value = ( bytes[offset + 1] << 8 ) & 0xFF00; + value += ( bytes[offset] & 0xFF ); + } + + /** + * Get value as two bytes in big endian byte order. + * + * @return The Bytes value + * @since 1.1 + */ + public byte[] getBytes() + { + byte[] result = new byte[2]; + result[0] = ( byte )( value & 0xFF ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + return result; + } + + /** + * Get value as Java int. + * + * @return The Value value + * @since 1.1 + */ + public int getValue() + { + return value; + } + + /** + * Override to make two instances with same value equal. + * + * @param o Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public boolean equals( Object o ) + { + if( o == null || !( o instanceof ZipShort ) ) + { + return false; + } + return value == ( ( ZipShort )o ).getValue(); + } + + /** + * Override to make two instances with same value equal. + * + * @return Description of the Returned Value + * @since 1.1 + */ + public int hashCode() + { + return value; + } + +}// ZipShort diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/AntClassLoader.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/AntClassLoader.java new file mode 100644 index 000000000..d2ef10528 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/AntClassLoader.java @@ -0,0 +1,1088 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.tools.ant.types.Path; + +/** + * Used to load classes within ant with a different claspath from that used to + * start ant. Note that it is possible to force a class into this loader even + * when that class is on the system classpath by using the forceLoadClass + * method. Any subsequent classes loaded by that class will then use this loader + * rather than the system class loader. + * + * @author Conor MacNeill + * @author Jesse Glick + */ +public class AntClassLoader extends ClassLoader implements BuildListener +{ + + /** + * The size of buffers to be used in this classloader. + */ + private final static int BUFFER_SIZE = 8192; + + private static Method getProtectionDomain = null; + private static Method defineClassProtectionDomain = null; + private static Method getContextClassLoader = null; + private static Method setContextClassLoader = null; + + /** + * The components of the classpath that the classloader searches for classes + */ + Vector pathComponents = new Vector(); + + /** + * Indicates whether the parent class loader should be consulted before + * trying to load with this class loader. + */ + private boolean parentFirst = true; + + /** + * These are the package roots that are to be loaded by the parent class + * loader regardless of whether the parent class loader is being searched + * first or not. + */ + private Vector systemPackages = new Vector(); + + /** + * These are the package roots that are to be loaded by this class loader + * regardless of whether the parent class loader is being searched first or + * not. + */ + private Vector loaderPackages = new Vector(); + + /** + * This flag indicates that the classloader will ignore the base classloader + * if it can't find a class. + */ + private boolean ignoreBase = false; + + /** + * The parent class loader, if one is given or can be determined + */ + private ClassLoader parent = null; + + /** + * A hashtable of zip files opened by the classloader + */ + private Hashtable zipFiles = new Hashtable(); + + /** + * The context loader saved when setting the thread's current context + * loader. + */ + private ClassLoader savedContextLoader = null; + private boolean isContextLoaderSaved = false; + + /** + * The project to which this class loader belongs. + */ + private Project project; + static + { + try + { + getProtectionDomain = Class.class.getMethod( "getProtectionDomain", new Class[0] ); + Class protectionDomain = Class.forName( "java.security.ProtectionDomain" ); + Class[] args = new Class[]{String.class, byte[].class, Integer.TYPE, Integer.TYPE, protectionDomain}; + defineClassProtectionDomain = ClassLoader.class.getDeclaredMethod( "defineClass", args ); + + getContextClassLoader = Thread.class.getMethod( "getContextClassLoader", new Class[0] ); + args = new Class[]{ClassLoader.class}; + setContextClassLoader = Thread.class.getMethod( "setContextClassLoader", args ); + } + catch( Exception e ) + {} + } + + + /** + * Create a classloader for the given project using the classpath given. + * + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. This is + * combined with the system classpath in a manner determined by the + * value of ${build.sysclasspath} + */ + public AntClassLoader( Project project, Path classpath ) + { + parent = AntClassLoader.class.getClassLoader(); + this.project = project; + project.addBuildListener( this ); + if( classpath != null ) + { + Path actualClasspath = classpath.concatSystemClasspath( "ignore" ); + String[] pathElements = actualClasspath.list(); + for( int i = 0; i < pathElements.length; ++i ) + { + try + { + addPathElement( ( String )pathElements[i] ); + } + catch( BuildException e ) + { + // ignore path elements which are invalid relative to the project + } + } + } + } + + /** + * Create a classloader for the given project using the classpath given. + * + * @param parent the parent classloader to which unsatisfied loading + * attempts are delgated + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( ClassLoader parent, Project project, Path classpath, + boolean parentFirst ) + { + this( project, classpath ); + if( parent != null ) + { + this.parent = parent; + } + this.parentFirst = parentFirst; + addSystemPackageRoot( "java" ); + addSystemPackageRoot( "javax" ); + } + + + /** + * Create a classloader for the given project using the classpath given. + * + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( Project project, Path classpath, boolean parentFirst ) + { + this( null, project, classpath, parentFirst ); + } + + /** + * Create an empty class loader. The classloader should be configured with + * path elements to specify where the loader is to look for classes. + * + * @param parent the parent classloader to which unsatisfied loading + * attempts are delgated + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( ClassLoader parent, boolean parentFirst ) + { + if( parent != null ) + { + this.parent = parent; + } + else + { + parent = AntClassLoader.class.getClassLoader(); + } + project = null; + this.parentFirst = parentFirst; + } + + /** + * Force initialization of a class in a JDK 1.1 compatible, albeit hacky way + * + * @param theClass Description of Parameter + */ + public static void initializeClass( Class theClass ) + { + // ***HACK*** We try to create an instance to force the VM to run the + // class' static initializer. We don't care if the instance can't + // be created - we are just interested in the side effect. + try + { + theClass.newInstance(); + } + catch( Throwable t ) + { + //ignore - our work is done + } + } + + /** + * Set this classloader to run in isolated mode. In isolated mode, classes + * not found on the given classpath will not be referred to the base class + * loader but will cause a classNotFoundException. + * + * @param isolated The new Isolated value + */ + public void setIsolated( boolean isolated ) + { + ignoreBase = isolated; + } + + /** + * Set the current thread's context loader to this classloader, storing the + * current loader value for later resetting + */ + public void setThreadContextLoader() + { + if( isContextLoaderSaved ) + { + throw new BuildException( "Context loader has not been reset" ); + } + if( getContextClassLoader != null && setContextClassLoader != null ) + { + try + { + savedContextLoader + = ( ClassLoader )getContextClassLoader.invoke( Thread.currentThread(), new Object[0] ); + Object[] args = new Object[]{this}; + setContextClassLoader.invoke( Thread.currentThread(), args ); + isContextLoaderSaved = true; + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t.toString() ); + } + catch( Exception e ) + { + throw new BuildException( e.toString() ); + } + } + } + + /** + * Finds the resource with the given name. A resource is some data (images, + * audio, text, etc) that can be accessed by class code in a way that is + * independent of the location of the code. + * + * @param name the name of the resource for which a stream is required. + * @return a URL for reading the resource, or null if the resource could not + * be found or the caller doesn't have adequate privileges to get the + * resource. + */ + public URL getResource( String name ) + { + // we need to search the components of the path to see if we can find the + // class we want. + URL url = null; + if( isParentFirst( name ) ) + { + url = ( parent == null ) ? super.getResource( name ) : parent.getResource( name ); + } + + if( url != null ) + { + log( "Resource " + name + " loaded from parent loader", + Project.MSG_DEBUG ); + + } + else + { + // try and load from this loader if the parent either didn't find + // it or wasn't consulted. + for( Enumeration e = pathComponents.elements(); e.hasMoreElements() && url == null; ) + { + File pathComponent = ( File )e.nextElement(); + url = getResourceURL( pathComponent, name ); + if( url != null ) + { + log( "Resource " + name + + " loaded from ant loader", + Project.MSG_DEBUG ); + } + } + } + + if( url == null && !isParentFirst( name ) ) + { + // this loader was first but it didn't find it - try the parent + + url = ( parent == null ) ? super.getResource( name ) : parent.getResource( name ); + if( url != null ) + { + log( "Resource " + name + " loaded from parent loader", + Project.MSG_DEBUG ); + } + } + + if( url == null ) + { + log( "Couldn't load Resource " + name, Project.MSG_DEBUG ); + } + + return url; + } + + /** + * Get a stream to read the requested resource name. + * + * @param name the name of the resource for which a stream is required. + * @return a stream to the required resource or null if the resource cannot + * be found on the loader's classpath. + */ + public InputStream getResourceAsStream( String name ) + { + + InputStream resourceStream = null; + if( isParentFirst( name ) ) + { + resourceStream = loadBaseResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from parent loader", Project.MSG_DEBUG ); + + } + else + { + resourceStream = loadResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from ant loader", Project.MSG_DEBUG ); + } + } + } + else + { + resourceStream = loadResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from ant loader", Project.MSG_DEBUG ); + + } + else + { + resourceStream = loadBaseResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from parent loader", Project.MSG_DEBUG ); + } + } + } + + if( resourceStream == null ) + { + log( "Couldn't load ResourceStream for " + name, + Project.MSG_DEBUG ); + } + + return resourceStream; + } + + /** + * Add a package root to the list of packages which must be loaded using + * this loader. All subpackages are also included. + * + * @param packageRoot the root of akll packages to be included. + */ + public void addLoaderPackageRoot( String packageRoot ) + { + loaderPackages.addElement( packageRoot + "." ); + } + + + /** + * Add an element to the classpath to be searched + * + * @param pathElement The feature to be added to the PathElement attribute + * @exception BuildException Description of Exception + */ + public void addPathElement( String pathElement ) + throws BuildException + { + File pathComponent + = project != null ? project.resolveFile( pathElement ) + : new File( pathElement ); + pathComponents.addElement( pathComponent ); + } + + /** + * Add a package root to the list of packages which must be loaded on the + * parent loader. All subpackages are also included. + * + * @param packageRoot the root of all packages to be included. + */ + public void addSystemPackageRoot( String packageRoot ) + { + systemPackages.addElement( packageRoot + "." ); + } + + public void buildFinished( BuildEvent event ) + { + cleanup(); + } + + public void buildStarted( BuildEvent event ) { } + + public void cleanup() + { + pathComponents = null; + project = null; + for( Enumeration e = zipFiles.elements(); e.hasMoreElements(); ) + { + ZipFile zipFile = ( ZipFile )e.nextElement(); + try + { + zipFile.close(); + } + catch( IOException ioe ) + { + // ignore + } + } + zipFiles = new Hashtable(); + } + + /** + * Search for and load a class on the classpath of this class loader. + * + * @param name the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class findClass( String name ) + throws ClassNotFoundException + { + log( "Finding class " + name, Project.MSG_DEBUG ); + + return findClassInComponents( name ); + } + + + /** + * Load a class through this class loader even if that class is available on + * the parent classpath. This ensures that any classes which are loaded by + * the returned class will use this classloader. + * + * @param classname the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class forceLoadClass( String classname ) + throws ClassNotFoundException + { + log( "force loading " + classname, Project.MSG_DEBUG ); + + Class theClass = findLoadedClass( classname ); + + if( theClass == null ) + { + theClass = findClass( classname ); + } + + return theClass; + } + + /** + * Load a class through this class loader but defer to the parent class + * loader This ensures that instances of the returned class will be + * compatible with instances which which have already been loaded on the + * parent loader. + * + * @param classname the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class forceLoadSystemClass( String classname ) + throws ClassNotFoundException + { + log( "force system loading " + classname, Project.MSG_DEBUG ); + + Class theClass = findLoadedClass( classname ); + + if( theClass == null ) + { + theClass = findBaseClass( classname ); + } + + return theClass; + } + + public void messageLogged( BuildEvent event ) { } + + /** + * Reset the current thread's context loader to its original value + */ + public void resetThreadContextLoader() + { + if( isContextLoaderSaved && + getContextClassLoader != null && setContextClassLoader != null ) + { + try + { + Object[] args = new Object[]{savedContextLoader}; + setContextClassLoader.invoke( Thread.currentThread(), args ); + savedContextLoader = null; + isContextLoaderSaved = false; + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t.toString() ); + } + catch( Exception e ) + { + throw new BuildException( e.toString() ); + } + } + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) { } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + /** + * Returns an enumeration of URLs representing all the resources with the + * given name by searching the class loader's classpath. + * + * @param name the resource name. + * @return an enumeration of URLs for the resources. + * @throws IOException if I/O errors occurs (can't happen) + */ + protected Enumeration findResources( String name ) + throws IOException + { + return new ResourceEnumeration( name ); + } + + + /** + * Load a class with this class loader. This method will load a class. This + * class attempts to load the class firstly using the parent class loader. + * For JDK 1.1 compatability, this uses the findSystemClass method. + * + * @param classname the name of the class to be loaded. + * @param resolve true if all classes upon which this class depends are to + * be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * the system classpath or this loader's classpath. + */ + protected Class loadClass( String classname, boolean resolve ) + throws ClassNotFoundException + { + + Class theClass = findLoadedClass( classname ); + if( theClass != null ) + { + return theClass; + } + + if( isParentFirst( classname ) ) + { + try + { + theClass = findBaseClass( classname ); + log( "Class " + classname + " loaded from parent loader", Project.MSG_DEBUG ); + } + catch( ClassNotFoundException cnfe ) + { + theClass = findClass( classname ); + log( "Class " + classname + " loaded from ant loader", Project.MSG_DEBUG ); + } + } + else + { + try + { + theClass = findClass( classname ); + log( "Class " + classname + " loaded from ant loader", Project.MSG_DEBUG ); + } + catch( ClassNotFoundException cnfe ) + { + if( ignoreBase ) + { + throw cnfe; + } + theClass = findBaseClass( classname ); + log( "Class " + classname + " loaded from parent loader", Project.MSG_DEBUG ); + } + } + + if( resolve ) + { + resolveClass( theClass ); + } + + return theClass; + } + + /** + * Log a message through the project object if one has been provided. + * + * @param message the message to log + * @param priority the logging priority of the message + */ + protected void log( String message, int priority ) + { + if( project != null ) + { + project.log( message, priority ); + } +// else { +// System.out.println(message); +// } + } + + /** + * Convert the class dot notation to a filesystem equivalent for searching + * purposes. + * + * @param classname the class name in dot format (ie java.lang.Integer) + * @return the classname in filesystem format (ie java/lang/Integer.class) + */ + private String getClassFilename( String classname ) + { + return classname.replace( '.', '/' ) + ".class"; + } + + /** + * Read a class definition from a stream. + * + * @param stream the stream from which the class is to be read. + * @param classname the class name of the class in the stream. + * @return the Class object read from the stream. + * @throws IOException if there is a problem reading the class from the + * stream. + */ + private Class getClassFromStream( InputStream stream, String classname ) + throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int bytesRead = -1; + byte[] buffer = new byte[BUFFER_SIZE]; + + while( ( bytesRead = stream.read( buffer, 0, BUFFER_SIZE ) ) != -1 ) + { + baos.write( buffer, 0, bytesRead ); + } + + byte[] classData = baos.toByteArray(); + + // Simply put: + // defineClass(classname, classData, 0, classData.length, Project.class.getProtectionDomain()); + // Made more elaborate to be 1.1-safe. + if( defineClassProtectionDomain != null ) + { + try + { + Object domain = getProtectionDomain.invoke( Project.class, new Object[0] ); + Object[] args = new Object[]{classname, classData, new Integer( 0 ), new Integer( classData.length ), domain}; + return ( Class )defineClassProtectionDomain.invoke( this, args ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof ClassFormatError ) + { + throw ( ClassFormatError )t; + } + else if( t instanceof NoClassDefFoundError ) + { + throw ( NoClassDefFoundError )t; + } + else + { + throw new IOException( t.toString() ); + } + } + catch( Exception e ) + { + throw new IOException( e.toString() ); + } + } + else + { + return defineClass( classname, classData, 0, classData.length ); + } + } + + /** + * Get an inputstream to a given resource in the given file which may either + * be a directory or a zip file. + * + * @param file the file (directory or jar) in which to search for the + * resource. + * @param resourceName the name of the resource for which a stream is + * required. + * @return a stream to the required resource or null if the resource cannot + * be found in the given file object + */ + private InputStream getResourceStream( File file, String resourceName ) + { + try + { + if( !file.exists() ) + { + return null; + } + + if( file.isDirectory() ) + { + File resource = new File( file, resourceName ); + + if( resource.exists() ) + { + return new FileInputStream( resource ); + } + } + else + { + // is the zip file in the cache + ZipFile zipFile = ( ZipFile )zipFiles.get( file ); + if( zipFile == null ) + { + zipFile = new ZipFile( file ); + zipFiles.put( file, zipFile ); + } + ZipEntry entry = zipFile.getEntry( resourceName ); + if( entry != null ) + { + return zipFile.getInputStream( entry ); + } + } + } + catch( Exception e ) + { + log( "Ignoring Exception " + e.getClass().getName() + ": " + e.getMessage() + + " reading resource " + resourceName + " from " + file, Project.MSG_VERBOSE ); + } + + return null; + } + + /** + * Get an inputstream to a given resource in the given file which may either + * be a directory or a zip file. + * + * @param file the file (directory or jar) in which to search for the + * resource. + * @param resourceName the name of the resource for which a stream is + * required. + * @return a stream to the required resource or null if the resource cannot + * be found in the given file object + */ + private URL getResourceURL( File file, String resourceName ) + { + try + { + if( !file.exists() ) + { + return null; + } + + if( file.isDirectory() ) + { + File resource = new File( file, resourceName ); + + if( resource.exists() ) + { + try + { + return new URL( "file:" + resource.toString() ); + } + catch( MalformedURLException ex ) + { + return null; + } + } + } + else + { + ZipFile zipFile = ( ZipFile )zipFiles.get( file ); + if( zipFile == null ) + { + zipFile = new ZipFile( file ); + zipFiles.put( file, zipFile ); + } + + ZipEntry entry = zipFile.getEntry( resourceName ); + if( entry != null ) + { + try + { + return new URL( "jar:file:" + file.toString() + "!/" + entry ); + } + catch( MalformedURLException ex ) + { + return null; + } + } + } + } + catch( Exception e ) + { + e.printStackTrace(); + } + + return null; + } + + private boolean isParentFirst( String resourceName ) + { + // default to the global setting and then see + // if this class belongs to a package which has been + // designated to use a specific loader first (this one or the parent one) + boolean useParentFirst = parentFirst; + + for( Enumeration e = systemPackages.elements(); e.hasMoreElements(); ) + { + String packageName = ( String )e.nextElement(); + if( resourceName.startsWith( packageName ) ) + { + useParentFirst = true; + break; + } + } + + for( Enumeration e = loaderPackages.elements(); e.hasMoreElements(); ) + { + String packageName = ( String )e.nextElement(); + if( resourceName.startsWith( packageName ) ) + { + useParentFirst = false; + break; + } + } + + return useParentFirst; + } + + /** + * Find a system class (which should be loaded from the same classloader as + * the Ant core). + * + * @param name Description of Parameter + * @return Description of the Returned Value + * @exception ClassNotFoundException Description of Exception + */ + private Class findBaseClass( String name ) + throws ClassNotFoundException + { + if( parent == null ) + { + return findSystemClass( name ); + } + else + { + return parent.loadClass( name ); + } + } + + + /** + * Find a class on the given classpath. + * + * @param name Description of Parameter + * @return Description of the Returned Value + * @exception ClassNotFoundException Description of Exception + */ + private Class findClassInComponents( String name ) + throws ClassNotFoundException + { + // we need to search the components of the path to see if we can find the + // class we want. + InputStream stream = null; + String classFilename = getClassFilename( name ); + try + { + for( Enumeration e = pathComponents.elements(); e.hasMoreElements(); ) + { + File pathComponent = ( File )e.nextElement(); + try + { + stream = getResourceStream( pathComponent, classFilename ); + if( stream != null ) + { + return getClassFromStream( stream, name ); + } + } + catch( IOException ioe ) + { + // ioe.printStackTrace(); + log( "Exception reading component " + pathComponent, Project.MSG_VERBOSE ); + } + } + + throw new ClassNotFoundException( name ); + } + finally + { + try + { + if( stream != null ) + { + stream.close(); + } + } + catch( IOException e ) + {} + } + } + + /** + * Find a system resource (which should be loaded from the parent + * classloader). + * + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private InputStream loadBaseResource( String name ) + { + if( parent == null ) + { + return getSystemResourceAsStream( name ); + } + else + { + return parent.getResourceAsStream( name ); + } + } + + + /** + * Get a stream to read the requested resource name from this loader. + * + * @param name the name of the resource for which a stream is required. + * @return a stream to the required resource or null if the resource cannot + * be found on the loader's classpath. + */ + private InputStream loadResource( String name ) + { + // we need to search the components of the path to see if we can find the + // class we want. + InputStream stream = null; + + for( Enumeration e = pathComponents.elements(); e.hasMoreElements() && stream == null; ) + { + File pathComponent = ( File )e.nextElement(); + stream = getResourceStream( pathComponent, name ); + } + return stream; + } + + /** + * An enumeration of all resources of a given name found within the + * classpath of this class loader. This enumeration is used by the {@link + * #findResources(String) findResources} method, which is in turn used by + * the {@link ClassLoader#getResources ClassLoader.getResources} method. + * + * @author David A. Herman + * @see AntClassLoader#findResources(String) + * @see java.lang.ClassLoader#getResources(String) + */ + private class ResourceEnumeration implements Enumeration + { + + /** + * The URL of the next resource to return in the enumeration. If this + * field is null then the enumeration has been completed, + * i.e., there are no more elements to return. + */ + private URL nextResource; + + /** + * The index of the next classpath element to search. + */ + private int pathElementsIndex; + + /** + * The name of the resource being searched for. + */ + private String resourceName; + + /** + * Construct a new enumeration of resources of the given name found + * within this class loader's classpath. + * + * @param name the name of the resource to search for. + */ + ResourceEnumeration( String name ) + { + this.resourceName = name; + this.pathElementsIndex = 0; + findNextResource(); + } + + /** + * Indicates whether there are more elements in the enumeration to + * return. + * + * @return true if there are more elements in the + * enumeration; false otherwise. + */ + public boolean hasMoreElements() + { + return ( this.nextResource != null ); + } + + /** + * Returns the next resource in the enumeration. + * + * @return the next resource in the enumeration. + */ + public Object nextElement() + { + URL ret = this.nextResource; + findNextResource(); + return ret; + } + + /** + * Locates the next resource of the correct name in the classpath and + * sets nextResource to the URL of that resource. If no + * more resources can be found, nextResource is set to + * null. + */ + private void findNextResource() + { + URL url = null; + while( ( pathElementsIndex < pathComponents.size() ) && + ( url == null ) ) + { + try + { + File pathComponent + = ( File )pathComponents.elementAt( pathElementsIndex ); + url = getResourceURL( pathComponent, this.resourceName ); + pathElementsIndex++; + } + catch( BuildException e ) + { + // ignore path elements which are not valid relative to the project + } + } + this.nextResource = url; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildEvent.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildEvent.java new file mode 100644 index 000000000..feef6ff96 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildEvent.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.EventObject; + +public class BuildEvent extends EventObject +{ + private int priority = Project.MSG_VERBOSE; + private Throwable exception; + private String message; + private Project project; + private Target target; + private Task task; + + /** + * Construct a BuildEvent for a project level event + * + * @param project the project that emitted the event. + */ + public BuildEvent( Project project ) + { + super( project ); + this.project = project; + this.target = null; + this.task = null; + } + + /** + * Construct a BuildEvent for a target level event + * + * @param target the target that emitted the event. + */ + public BuildEvent( Target target ) + { + super( target ); + this.project = target.getProject(); + this.target = target; + this.task = null; + } + + /** + * Construct a BuildEvent for a task level event + * + * @param task the task that emitted the event. + */ + public BuildEvent( Task task ) + { + super( task ); + this.project = task.getProject(); + this.target = task.getOwningTarget(); + this.task = task; + } + + public void setException( Throwable exception ) + { + this.exception = exception; + } + + public void setMessage( String message, int priority ) + { + this.message = message; + this.priority = priority; + } + + /** + * Returns the exception that was thrown, if any. This field will only be + * set for "taskFinished", "targetFinished", and "buildFinished" events. + * + * @return The Exception value + * @see BuildListener#taskFinished(BuildEvent) + * @see BuildListener#targetFinished(BuildEvent) + * @see BuildListener#buildFinished(BuildEvent) + */ + public Throwable getException() + { + return exception; + } + + /** + * Returns the logging message. This field will only be set for + * "messageLogged" events. + * + * @return The Message value + * @see BuildListener#messageLogged(BuildEvent) + */ + public String getMessage() + { + return message; + } + + /** + * Returns the priority of the logging message. This field will only be set + * for "messageLogged" events. + * + * @return The Priority value + * @see BuildListener#messageLogged(BuildEvent) + */ + public int getPriority() + { + return priority; + } + + /** + * Returns the project that fired this event. + * + * @return The Project value + */ + public Project getProject() + { + return project; + } + + /** + * Returns the target that fired this event. + * + * @return The Target value + */ + public Target getTarget() + { + + return target; + } + + /** + * Returns the task that fired this event. + * + * @return The Task value + */ + public Task getTask() + { + return task; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildException.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildException.java new file mode 100644 index 000000000..a22ef8933 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildException.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.TaskException; + +/** + * Signals an error condition during a build. + * + * @author James Duncan Davidson + */ +public class BuildException + extends TaskException +{ + /** + * Location in the build file where the exception occured + */ + private Location location = Location.UNKNOWN_LOCATION; + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of or information about the exception. + */ + public BuildException( String msg ) + { + super( msg ); + } + + /** + * Constructs an exception with the given message and exception as a root + * cause. + * + * @param msg Description of or information about the exception. + * @param cause Throwable that might have cause this one. + */ + public BuildException( String msg, Throwable cause ) + { + super( msg, cause ); + } + + /** + * Constructs an exception with the given message and exception as a root + * cause and a location in a file. + * + * @param msg Description of or information about the exception. + * @param cause Exception that might have cause this one. + * @param location Location in the project file where the error occured. + */ + public BuildException( String msg, Throwable cause, Location location ) + { + this( msg, cause ); + this.location = location; + } + + /** + * Constructs an exception with the given exception as a root cause. + * + * @param cause Exception that might have caused this one. + */ + public BuildException( Throwable cause ) + { + super( cause.toString(), cause ); + } + + /** + * Constructs an exception with the given descriptive message and a location + * in a file. + * + * @param msg Description of or information about the exception. + * @param location Location in the project file where the error occured. + */ + public BuildException( String msg, Location location ) + { + super( msg ); + this.location = location; + } + + /** + * Sets the file location where the error occured. + * + * @param location The new Location value + */ + public void setLocation( Location location ) + { + this.location = location; + } + + /** + * Returns the file location where the error occured. + * + * @return The Location value + */ + public Location getLocation() + { + return location; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildListener.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildListener.java new file mode 100644 index 000000000..f6055badb --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildListener.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.EventListener; + +/** + * Classes that implement this interface will be notified when things happend + * during a build. + * + * @author RT + * @see BuildEvent + * @see Project#addBuildListener(BuildListener) + */ +public interface BuildListener extends EventListener +{ + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + void buildStarted( BuildEvent event ); + + /** + * Fired after the last target has finished. This event will still be thrown + * if an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void buildFinished( BuildEvent event ); + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTarget() + */ + void targetStarted( BuildEvent event ); + + /** + * Fired when a target has finished. This event will still be thrown if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void targetFinished( BuildEvent event ); + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTask() + */ + void taskStarted( BuildEvent event ); + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void taskFinished( BuildEvent event ); + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + * @see BuildEvent#getMessage() + * @see BuildEvent#getPriority() + */ + void messageLogged( BuildEvent event ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildLogger.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildLogger.java new file mode 100644 index 000000000..9e3879d14 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildLogger.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.PrintStream; + +/** + * Interface used by Ant to log the build output. A build logger is a build + * listener which has the 'right' to send output to the ant log, which is + * usually System.out unles redirected by the -logfile option. + * + * @author Conor MacNeill + */ +public interface BuildLogger extends BuildListener +{ + /** + * Set the msgOutputLevel this logger is to respond to. Only messages with a + * message level lower than or equal to the given level are output to the + * log.

          + * + * Constants for the message levels are in Project.java. The order of the + * levels, from least to most verbose, is MSG_ERR, MSG_WARN, MSG_INFO, + * MSG_VERBOSE, MSG_DEBUG. + * + * @param level the logging level for the logger. + */ + void setMessageOutputLevel( int level ); + + /** + * Set the output stream to which this logger is to send its output. + * + * @param output the output stream for the logger. + */ + void setOutputPrintStream( PrintStream output ); + + /** + * Set this logger to produce emacs (and other editor) friendly output. + * + * @param emacsMode true if output is to be unadorned so that emacs and + * other editors can parse files names, etc. + */ + void setEmacsMode( boolean emacsMode ); + + /** + * Set the output stream to which this logger is to send error messages. + * + * @param err the error stream for the logger. + */ + void setErrorPrintStream( PrintStream err ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Constants.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Constants.java new file mode 100644 index 000000000..7f26d5f4e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Constants.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Abstract interface to hold constants. + * + * @author Peter Donald + */ +interface Constants +{ +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/DefaultLogger.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/DefaultLogger.java new file mode 100644 index 000000000..eb578b54e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/DefaultLogger.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import org.apache.tools.ant.util.StringUtils; + +/** + * Writes build event to a PrintStream. Currently, it only writes which targets + * are being executed, and any messages that get logged. + * + * @author RT + */ +public class DefaultLogger implements BuildLogger +{ + private static int LEFT_COLUMN_SIZE = 12; + protected int msgOutputLevel = Project.MSG_ERR; + private long startTime = System.currentTimeMillis(); + + protected boolean emacsMode = false; + protected PrintStream err; + + protected PrintStream out; + + protected static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + + } + + /** + * Set this logger to produce emacs (and other editor) friendly output. + * + * @param emacsMode true if output is to be unadorned so that emacs and + * other editors can parse files names, etc. + */ + public void setEmacsMode( boolean emacsMode ) + { + this.emacsMode = emacsMode; + } + + /** + * Set the output stream to which this logger is to send error messages. + * + * @param err the error stream for the logger. + */ + public void setErrorPrintStream( PrintStream err ) + { + this.err = new PrintStream( err, true ); + } + + /** + * Set the msgOutputLevel this logger is to respond to. Only messages with a + * message level lower than or equal to the given level are output to the + * log.

          + * + * Constants for the message levels are in Project.java. The order of the + * levels, from least to most verbose, is MSG_ERR, MSG_WARN, MSG_INFO, + * MSG_VERBOSE, MSG_DEBUG. The default message level for DefaultLogger is + * Project.MSG_ERR. + * + * @param level the logging level for the logger. + */ + public void setMessageOutputLevel( int level ) + { + this.msgOutputLevel = level; + } + + /** + * Set the output stream to which this logger is to send its output. + * + * @param output the output stream for the logger. + */ + public void setOutputPrintStream( PrintStream output ) + { + this.out = new PrintStream( output, true ); + } + + /** + * Prints whether the build succeeded or failed, and any errors the occured + * during the build. + * + * @param event Description of Parameter + */ + public void buildFinished( BuildEvent event ) + { + Throwable error = event.getException(); + StringBuffer message = new StringBuffer(); + + if( error == null ) + { + message.append( StringUtils.LINE_SEP ); + message.append( "BUILD SUCCESSFUL" ); + } + else + { + message.append( StringUtils.LINE_SEP ); + message.append( "BUILD FAILED" ); + message.append( StringUtils.LINE_SEP ); + + if( Project.MSG_VERBOSE <= msgOutputLevel || + !( error instanceof BuildException ) ) + { + message.append( StringUtils.getStackTrace( error ) ); + } + else + { + if( error instanceof BuildException ) + { + message.append( error.toString() ).append( StringUtils.LINE_SEP ); + } + else + { + message.append( error.getMessage() ).append( StringUtils.LINE_SEP ); + } + } + } + message.append( StringUtils.LINE_SEP ); + message.append( "Total time: " + + formatTime( System.currentTimeMillis() - startTime ) ); + + String msg = message.toString(); + if( error == null ) + { + out.println( msg ); + } + else + { + err.println( msg ); + } + log( msg ); + } + + public void buildStarted( BuildEvent event ) + { + startTime = System.currentTimeMillis(); + } + + public void messageLogged( BuildEvent event ) + { + // Filter out messages based on priority + if( event.getPriority() <= msgOutputLevel ) + { + + StringBuffer message = new StringBuffer(); + // Print out the name of the task if we're in one + if( event.getTask() != null ) + { + String name = event.getTask().getTaskName(); + + if( !emacsMode ) + { + String label = "[" + name + "] "; + for( int i = 0; i < ( LEFT_COLUMN_SIZE - label.length() ); i++ ) + { + message.append( " " ); + } + message.append( label ); + } + } + + message.append( event.getMessage() ); + String msg = message.toString(); + if( event.getPriority() != Project.MSG_ERR ) + { + out.println( msg ); + } + else + { + err.println( msg ); + } + log( msg ); + } + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) + { + if( Project.MSG_INFO <= msgOutputLevel ) + { + String msg = StringUtils.LINE_SEP + event.getTarget().getName() + ":"; + out.println( msg ); + log( msg ); + } + } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + /** + * Empty implementation which allows subclasses to receive the same output + * that is generated here. + * + * @param message Description of Parameter + */ + protected void log( String message ) { } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/DemuxOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/DemuxOutputStream.java new file mode 100644 index 000000000..ef0e5472f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/DemuxOutputStream.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Hashtable; + + +/** + * Logs content written by a thread and forwards the buffers onto the project + * object which will forward the content to the appropriate task + * + * @author Conor MacNeill + */ +public class DemuxOutputStream extends OutputStream +{ + + private final static int MAX_SIZE = 1024; + + private Hashtable buffers = new Hashtable(); +// private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private boolean skip = false; + private boolean isErrorStream; + private Project project; + + /** + * Creates a new instance of this class. + * + * @param project Description of Parameter + * @param isErrorStream Description of Parameter + */ + public DemuxOutputStream( Project project, boolean isErrorStream ) + { + this.project = project; + this.isErrorStream = isErrorStream; + } + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + flush(); + } + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void flush() + throws IOException + { + if( getBuffer().size() > 0 ) + { + processBuffer(); + } + } + + /** + * Write the data to the buffer and flush the buffer, if a line separator is + * detected. + * + * @param cc data to log (byte). + * @exception IOException Description of Exception + */ + public void write( int cc ) + throws IOException + { + final byte c = ( byte )cc; + if( ( c == '\n' ) || ( c == '\r' ) ) + { + if( !skip ) + { + processBuffer(); + } + } + else + { + ByteArrayOutputStream buffer = getBuffer(); + buffer.write( cc ); + if( buffer.size() > MAX_SIZE ) + { + processBuffer(); + } + } + skip = ( c == '\r' ); + } + + + /** + * Converts the buffer to a string and sends it to processLine + */ + protected void processBuffer() + { + String output = getBuffer().toString(); + project.demuxOutput( output, isErrorStream ); + resetBuffer(); + } + + private ByteArrayOutputStream getBuffer() + { + Thread current = Thread.currentThread(); + ByteArrayOutputStream buffer = ( ByteArrayOutputStream )buffers.get( current ); + if( buffer == null ) + { + buffer = new ByteArrayOutputStream(); + buffers.put( current, buffer ); + } + return buffer; + } + + private void resetBuffer() + { + Thread current = Thread.currentThread(); + buffers.remove( current ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/DesirableFilter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/DesirableFilter.java new file mode 100644 index 000000000..a53315fa2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/DesirableFilter.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FilenameFilter; + + +/** + * Filters filenames to determine whether or not the file is desirable. + * + * @author Jason Hunter [jhunter@servlets.com] + * @author james@x180.com + */ +public class DesirableFilter implements FilenameFilter +{ + + /** + * Test the given filename to determine whether or not it's desirable. This + * helps tasks filter temp files and files used by CVS. + * + * @param dir Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + + public boolean accept( File dir, String name ) + { + + // emacs save file + if( name.endsWith( "~" ) ) + { + return false; + } + + // emacs autosave file + if( name.startsWith( "#" ) && name.endsWith( "#" ) ) + { + return false; + } + + // openwindows text editor does this I think + if( name.startsWith( "%" ) && name.endsWith( "%" ) ) + { + return false; + } + + /* + * CVS stuff -- hopefully there won't be a case with + * an all cap file/dir named "CVS" that somebody wants + * to keep around... + */ + if( name.equals( "CVS" ) ) + { + return false; + } + + /* + * If we are going to ignore CVS might as well ignore + * this one as well... + */ + if( name.equals( ".cvsignore" ) ) + { + return false; + } + + // CVS merge autosaves. + if( name.startsWith( ".#" ) ) + { + return false; + } + + // SCCS/CSSC/TeamWare: + if( name.equals( "SCCS" ) ) + { + return false; + } + + // Visual Source Save + if( name.equals( "vssver.scc" ) ) + { + return false; + } + + // default + return true; + } +} + + + + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/DirectoryScanner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/DirectoryScanner.java new file mode 100644 index 000000000..7d95dd304 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/DirectoryScanner.java @@ -0,0 +1,1177 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * Class for scanning a directory for files/directories that match a certain + * criteria.

          + * + * These criteria consist of a set of include and exclude patterns. With these + * patterns, you can select which files you want to have included, and which + * files you want to have excluded.

          + * + * The idea is simple. A given directory is recursively scanned for all files + * and directories. Each file/directory is matched against a set of include and + * exclude patterns. Only files/directories that match at least one pattern of + * the include pattern list, and don't match a pattern of the exclude pattern + * list will be placed in the list of files/directories found.

          + * + * When no list of include patterns is supplied, "**" will be used, which means + * that everything will be matched. When no list of exclude patterns is + * supplied, an empty list is used, such that nothing will be excluded.

          + * + * The pattern matching is done as follows: The name to be matched is split up + * in path segments. A path segment is the name of a directory or file, which is + * bounded by File.separator ('/' under UNIX, '\' under Windows). + * E.g. "abc/def/ghi/xyz.java" is split up in the segments "abc", "def", "ghi" + * and "xyz.java". The same is done for the pattern against which should be + * matched.

          + * + * Then the segments of the name and the pattern will be matched against each + * other. When '**' is used for a path segment in the pattern, then it matches + * zero or more path segments of the name.

          + * + * There are special case regarding the use of File.separators at + * the beginningof the pattern and the string to match:
          + * When a pattern starts with a File.separator, the string to match + * must also start with a File.separator. When a pattern does not + * start with a File.separator, the string to match may not start + * with a File.separator. When one of these rules is not obeyed, + * the string will not match.

          + * + * When a name path segment is matched against a pattern path segment, the + * following special characters can be used: '*' matches zero or more + * characters, '?' matches one character.

          + * + * Examples:

          + * + * "**\*.class" matches all .class files/dirs in a directory tree.

          + * + * "test\a??.java" matches all files/dirs which start with an 'a', then two more + * characters and then ".java", in a directory called test.

          + * + * "**" matches everything in a directory tree.

          + * + * "**\test\**\XYZ*" matches all files/dirs that start with "XYZ" and where + * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").

          + * + * Case sensitivity may be turned off if necessary. By default, it is turned on. + *

          + * + * Example of usage:

          + *   String[] includes = {"**\\*.class"};
          + *   String[] excludes = {"modules\\*\\**"};
          + *   ds.setIncludes(includes);
          + *   ds.setExcludes(excludes);
          + *   ds.setBasedir(new File("test"));
          + *   ds.setCaseSensitive(true);
          + *   ds.scan();
          + *
          + *   System.out.println("FILES:");
          + *   String[] files = ds.getIncludedFiles();
          + *   for (int i = 0; i < files.length;i++) {
          + *     System.out.println(files[i]);
          + *   }
          + * 
          This will scan a directory called test for .class files, but excludes + * all .class files in all directories under a directory called "modules" + * + * @author Arnout J. Kuiper ajkuiper@wxs.nl + * @author Magesh Umasankar + */ +public class DirectoryScanner implements FileScanner +{ + + /** + * Patterns that should be excluded by default. + * + * @see #addDefaultExcludes() + */ + protected final static String[] DEFAULTEXCLUDES = { + "**/*~", + "**/#*#", + "**/.#*", + "**/%*%", + "**/CVS", + "**/CVS/**", + "**/.cvsignore", + "**/SCCS", + "**/SCCS/**", + "**/vssver.scc" + }; + + /** + * Have the Vectors holding our results been built by a slow scan? + */ + protected boolean haveSlowResults = false; + + /** + * Should the file system be treated as a case sensitive one? + */ + protected boolean isCaseSensitive = true; + + /** + * Is everything we've seen so far included? + */ + protected boolean everythingIncluded = true; + + /** + * The base directory which should be scanned. + */ + protected File basedir; + + /** + * The files that where found and matched at least one includes, and also + * matched at least one excludes. + */ + protected Vector dirsExcluded; + + /** + * The directories that where found and matched at least one includes, and + * matched no excludes. + */ + protected Vector dirsIncluded; + + /** + * The directories that where found and did not match any includes. + */ + protected Vector dirsNotIncluded; + + /** + * The patterns for the files that should be excluded. + */ + protected String[] excludes; + + /** + * The files that where found and matched at least one includes, and also + * matched at least one excludes. + */ + protected Vector filesExcluded; + + /** + * The files that where found and matched at least one includes, and matched + * no excludes. + */ + protected Vector filesIncluded; + + /** + * The files that where found and did not match any includes. + */ + protected Vector filesNotIncluded; + + /** + * The patterns for the files that should be included. + */ + protected String[] includes; + + /** + * Constructor. + */ + public DirectoryScanner() { } + + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @return true when the string matches against the pattern, + * false otherwise. + */ + public static boolean match( String pattern, String str ) + { + return match( pattern, str, true ); + } + + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @param isCaseSensitive Description of Parameter + * @return true when the string matches against the pattern, + * false otherwise. + */ + protected static boolean match( String pattern, String str, boolean isCaseSensitive ) + { + char[] patArr = pattern.toCharArray(); + char[] strArr = str.toCharArray(); + int patIdxStart = 0; + int patIdxEnd = patArr.length - 1; + int strIdxStart = 0; + int strIdxEnd = strArr.length - 1; + char ch; + + boolean containsStar = false; + for( int i = 0; i < patArr.length; i++ ) + { + if( patArr[i] == '*' ) + { + containsStar = true; + break; + } + } + + if( !containsStar ) + { + // No '*'s, so we make a shortcut + if( patIdxEnd != strIdxEnd ) + { + return false;// Pattern and string do not have the same size + } + for( int i = 0; i <= patIdxEnd; i++ ) + { + ch = patArr[i]; + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[i] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[i] ) ) + { + return false;// Character mismatch + } + } + } + return true;// String matches against pattern + } + + if( patIdxEnd == 0 ) + { + return true;// Pattern contains only '*', which matches anything + } + + // Process characters before first star + while( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd ) + { + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxStart] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxStart] ) ) + { + return false;// Character mismatch + } + } + patIdxStart++; + strIdxStart++; + } + if( strIdxStart > strIdxEnd ) + { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + // Process characters after last star + while( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd ) + { + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxEnd] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxEnd] ) ) + { + return false;// Character mismatch + } + } + patIdxEnd--; + strIdxEnd--; + } + if( strIdxStart > strIdxEnd ) + { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + // process pattern between stars. padIdxStart and patIdxEnd point + // always to a '*'. + while( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd ) + { + int patIdxTmp = -1; + for( int i = patIdxStart + 1; i <= patIdxEnd; i++ ) + { + if( patArr[i] == '*' ) + { + patIdxTmp = i; + break; + } + } + if( patIdxTmp == patIdxStart + 1 ) + { + // Two stars next to each other, skip the first one. + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = ( patIdxTmp - patIdxStart - 1 ); + int strLength = ( strIdxEnd - strIdxStart + 1 ); + int foundIdx = -1; + strLoop : + for( int i = 0; i <= strLength - patLength; i++ ) + { + for( int j = 0; j < patLength; j++ ) + { + ch = patArr[patIdxStart + j + 1]; + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxStart + i + j] ) + { + continue strLoop; + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxStart + i + j] ) ) + { + continue strLoop; + } + } + } + + foundIdx = strIdxStart + i; + break; + } + + if( foundIdx == -1 ) + { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + // All characters in the string are used. Check if only '*'s are left + // in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + /** + * Matches a path against a pattern. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @return true when the pattern matches against the string. + * false otherwise. + */ + protected static boolean matchPath( String pattern, String str ) + { + return matchPath( pattern, str, true ); + } + + /** + * Matches a path against a pattern. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @param isCaseSensitive must a case sensitive match be done? + * @return true when the pattern matches against the string. + * false otherwise. + */ + protected static boolean matchPath( String pattern, String str, boolean isCaseSensitive ) + { + // When str starts with a File.separator, pattern has to start with a + // File.separator. + // When pattern starts with a File.separator, str has to start with a + // File.separator. + if( str.startsWith( File.separator ) != + pattern.startsWith( File.separator ) ) + { + return false; + } + + Vector patDirs = new Vector(); + StringTokenizer st = new StringTokenizer( pattern, File.separator ); + while( st.hasMoreTokens() ) + { + patDirs.addElement( st.nextToken() ); + } + + Vector strDirs = new Vector(); + st = new StringTokenizer( str, File.separator ); + while( st.hasMoreTokens() ) + { + strDirs.addElement( st.nextToken() ); + } + + int patIdxStart = 0; + int patIdxEnd = patDirs.size() - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.size() - 1; + + // up to first '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxStart ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxStart ), isCaseSensitive ) ) + { + return false; + } + patIdxStart++; + strIdxStart++; + } + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + return true; + } + else + { + if( patIdxStart > patIdxEnd ) + { + // String not exhausted, but pattern is. Failure. + return false; + } + } + + // up to last '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxEnd ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxEnd ), isCaseSensitive ) ) + { + return false; + } + patIdxEnd--; + strIdxEnd--; + } + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + return true; + } + + while( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd ) + { + int patIdxTmp = -1; + for( int i = patIdxStart + 1; i <= patIdxEnd; i++ ) + { + if( patDirs.elementAt( i ).equals( "**" ) ) + { + patIdxTmp = i; + break; + } + } + if( patIdxTmp == patIdxStart + 1 ) + { + // '**/**' situation, so skip one + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = ( patIdxTmp - patIdxStart - 1 ); + int strLength = ( strIdxEnd - strIdxStart + 1 ); + int foundIdx = -1; + strLoop : + for( int i = 0; i <= strLength - patLength; i++ ) + { + for( int j = 0; j < patLength; j++ ) + { + String subPat = ( String )patDirs.elementAt( patIdxStart + j + 1 ); + String subStr = ( String )strDirs.elementAt( strIdxStart + i + j ); + if( !match( subPat, subStr, isCaseSensitive ) ) + { + continue strLoop; + } + } + + foundIdx = strIdxStart + i; + break; + } + + if( foundIdx == -1 ) + { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + + return true; + } + + + /** + * Does the path match the start of this pattern up to the first "**".

          + * + * This is not a general purpose test and should only be used if you can + * live with false positives.

          + * + * pattern=**\\a and str=b will yield true. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @return Description of the Returned Value + */ + protected static boolean matchPatternStart( String pattern, String str ) + { + return matchPatternStart( pattern, str, true ); + } + + /** + * Does the path match the start of this pattern up to the first "**".

          + * + * This is not a general purpose test and should only be used if you can + * live with false positives.

          + * + * pattern=**\\a and str=b will yield true. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @param isCaseSensitive must matches be case sensitive? + * @return Description of the Returned Value + */ + protected static boolean matchPatternStart( String pattern, String str, + boolean isCaseSensitive ) + { + // When str starts with a File.separator, pattern has to start with a + // File.separator. + // When pattern starts with a File.separator, str has to start with a + // File.separator. + if( str.startsWith( File.separator ) != + pattern.startsWith( File.separator ) ) + { + return false; + } + + Vector patDirs = new Vector(); + StringTokenizer st = new StringTokenizer( pattern, File.separator ); + while( st.hasMoreTokens() ) + { + patDirs.addElement( st.nextToken() ); + } + + Vector strDirs = new Vector(); + st = new StringTokenizer( str, File.separator ); + while( st.hasMoreTokens() ) + { + strDirs.addElement( st.nextToken() ); + } + + int patIdxStart = 0; + int patIdxEnd = patDirs.size() - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.size() - 1; + + // up to first '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxStart ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxStart ), isCaseSensitive ) ) + { + return false; + } + patIdxStart++; + strIdxStart++; + } + + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + return true; + } + else if( patIdxStart > patIdxEnd ) + { + // String not exhausted, but pattern is. Failure. + return false; + } + else + { + // pattern now holds ** while string is not exhausted + // this will generate false positives but we can live with that. + return true; + } + } + + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. All '/' and '\' characters are replaced by File.separatorChar + * . So the separator used need not match File.separatorChar. + * + * @param basedir the (non-null) basedir for scanning + */ + public void setBasedir( String basedir ) + { + setBasedir( new File( basedir.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ) ) ); + } + + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the basedir for scanning + */ + public void setBasedir( File basedir ) + { + this.basedir = basedir; + } + + + /** + * Sets the case sensitivity of the file system + * + * @param isCaseSensitive The new CaseSensitive value + */ + public void setCaseSensitive( boolean isCaseSensitive ) + { + this.isCaseSensitive = isCaseSensitive; + } + + + /** + * Sets the set of exclude patterns to use. All '/' and '\' characters are + * replaced by File.separatorChar. So the separator used need + * not match File.separatorChar.

          + * + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param excludes list of exclude patterns + */ + public void setExcludes( String[] excludes ) + { + if( excludes == null ) + { + this.excludes = null; + } + else + { + this.excludes = new String[excludes.length]; + for( int i = 0; i < excludes.length; i++ ) + { + String pattern; + pattern = excludes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + if( pattern.endsWith( File.separator ) ) + { + pattern += "**"; + } + this.excludes[i] = pattern; + } + } + } + + /** + * Sets the set of include patterns to use. All '/' and '\' characters are + * replaced by File.separatorChar. So the separator used need + * not match File.separatorChar.

          + * + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param includes list of include patterns + */ + public void setIncludes( String[] includes ) + { + if( includes == null ) + { + this.includes = null; + } + else + { + this.includes = new String[includes.length]; + for( int i = 0; i < includes.length; i++ ) + { + String pattern; + pattern = includes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + if( pattern.endsWith( File.separator ) ) + { + pattern += "**"; + } + this.includes[i] = pattern; + } + } + } + + + /** + * Gets the basedir that is used for scanning. This is the directory that is + * scanned recursively. + * + * @return the basedir that is used for scanning + */ + public File getBasedir() + { + return basedir; + } + + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the directories + */ + public String[] getExcludedDirectories() + { + slowScan(); + int count = dirsExcluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsExcluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the files + */ + public String[] getExcludedFiles() + { + slowScan(); + int count = filesExcluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesExcluded.elementAt( i ); + } + return files; + } + + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the directories + */ + public String[] getIncludedDirectories() + { + int count = dirsIncluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsIncluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at least one of the include + * patterns, and matched none of the exclude patterns. The names are + * relative to the basedir. + * + * @return the names of the files + */ + public String[] getIncludedFiles() + { + int count = filesIncluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesIncluded.elementAt( i ); + } + return files; + } + + + /** + * Get the names of the directories that matched at none of the include + * patterns. The names are relative to the basedir. + * + * @return the names of the directories + */ + public String[] getNotIncludedDirectories() + { + slowScan(); + int count = dirsNotIncluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsNotIncluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at none of the include patterns. + * The names are relative to the basedir. + * + * @return the names of the files + */ + public String[] getNotIncludedFiles() + { + slowScan(); + int count = filesNotIncluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesNotIncluded.elementAt( i ); + } + return files; + } + + /** + * Has the scanner excluded or omitted any files or directories it came + * accross? + * + * @return true if all files and directories that have been found, are + * included. + */ + public boolean isEverythingIncluded() + { + return everythingIncluded; + } + + + /** + * Adds the array with default exclusions to the current exclusions set. + */ + public void addDefaultExcludes() + { + int excludesLength = excludes == null ? 0 : excludes.length; + String[] newExcludes; + newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length]; + if( excludesLength > 0 ) + { + System.arraycopy( excludes, 0, newExcludes, 0, excludesLength ); + } + for( int i = 0; i < DEFAULTEXCLUDES.length; i++ ) + { + newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + } + excludes = newExcludes; + } + + + /** + * Scans the base directory for files that match at least one include + * pattern, and don't match any exclude patterns. + * + */ + public void scan() + { + if( basedir == null ) + { + throw new IllegalStateException( "No basedir set" ); + } + if( !basedir.exists() ) + { + throw new IllegalStateException( "basedir " + basedir + + " does not exist" ); + } + if( !basedir.isDirectory() ) + { + throw new IllegalStateException( "basedir " + basedir + + " is not a directory" ); + } + + if( includes == null ) + { + // No includes supplied, so set it to 'matches all' + includes = new String[1]; + includes[0] = "**"; + } + if( excludes == null ) + { + excludes = new String[0]; + } + + filesIncluded = new Vector(); + filesNotIncluded = new Vector(); + filesExcluded = new Vector(); + dirsIncluded = new Vector(); + dirsNotIncluded = new Vector(); + dirsExcluded = new Vector(); + + if( isIncluded( "" ) ) + { + if( !isExcluded( "" ) ) + { + dirsIncluded.addElement( "" ); + } + else + { + dirsExcluded.addElement( "" ); + } + } + else + { + dirsNotIncluded.addElement( "" ); + } + scandir( basedir, "", true ); + } + + /** + * Tests whether a name matches against at least one exclude pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * exclude pattern, false otherwise. + */ + protected boolean isExcluded( String name ) + { + for( int i = 0; i < excludes.length; i++ ) + { + if( matchPath( excludes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + + /** + * Tests whether a name matches against at least one include pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * include pattern, false otherwise. + */ + protected boolean isIncluded( String name ) + { + for( int i = 0; i < includes.length; i++ ) + { + if( matchPath( includes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + /** + * Tests whether a name matches the start of at least one include pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * include pattern, false otherwise. + */ + protected boolean couldHoldIncluded( String name ) + { + for( int i = 0; i < includes.length; i++ ) + { + if( matchPatternStart( includes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + + /** + * Scans the passed dir for files and directories. Found files and + * directories are placed in their respective collections, based on the + * matching of includes and excludes. When a directory is found, it is + * scanned recursively. + * + * @param dir the directory to scan + * @param vpath the path relative to the basedir (needed to prevent problems + * with an absolute path when using dir) + * @param fast Description of Parameter + * @see #filesIncluded + * @see #filesNotIncluded + * @see #filesExcluded + * @see #dirsIncluded + * @see #dirsNotIncluded + * @see #dirsExcluded + */ + protected void scandir( File dir, String vpath, boolean fast ) + { + String[] newfiles = dir.list(); + + if( newfiles == null ) + { + /* + * two reasons are mentioned in the API docs for File.list + * (1) dir is not a directory. This is impossible as + * we wouldn't get here in this case. + * (2) an IO error occurred (why doesn't it throw an exception + * then???) + */ + throw new BuildException( "IO error scanning directory " + + dir.getAbsolutePath() ); + } + + for( int i = 0; i < newfiles.length; i++ ) + { + String name = vpath + newfiles[i]; + File file = new File( dir, newfiles[i] ); + if( file.isDirectory() ) + { + if( isIncluded( name ) ) + { + if( !isExcluded( name ) ) + { + dirsIncluded.addElement( name ); + if( fast ) + { + scandir( file, name + File.separator, fast ); + } + } + else + { + everythingIncluded = false; + dirsExcluded.addElement( name ); + if( fast && couldHoldIncluded( name ) ) + { + scandir( file, name + File.separator, fast ); + } + } + } + else + { + everythingIncluded = false; + dirsNotIncluded.addElement( name ); + if( fast && couldHoldIncluded( name ) ) + { + scandir( file, name + File.separator, fast ); + } + } + if( !fast ) + { + scandir( file, name + File.separator, fast ); + } + } + else if( file.isFile() ) + { + if( isIncluded( name ) ) + { + if( !isExcluded( name ) ) + { + filesIncluded.addElement( name ); + } + else + { + everythingIncluded = false; + filesExcluded.addElement( name ); + } + } + else + { + everythingIncluded = false; + filesNotIncluded.addElement( name ); + } + } + } + } + + /** + * Toplevel invocation for the scan.

          + * + * Returns immediately if a slow scan has already been requested. + */ + protected void slowScan() + { + if( haveSlowResults ) + { + return; + } + + String[] excl = new String[dirsExcluded.size()]; + dirsExcluded.copyInto( excl ); + + String[] notIncl = new String[dirsNotIncluded.size()]; + dirsNotIncluded.copyInto( notIncl ); + + for( int i = 0; i < excl.length; i++ ) + { + if( !couldHoldIncluded( excl[i] ) ) + { + scandir( new File( basedir, excl[i] ), + excl[i] + File.separator, false ); + } + } + + for( int i = 0; i < notIncl.length; i++ ) + { + if( !couldHoldIncluded( notIncl[i] ) ) + { + scandir( new File( basedir, notIncl[i] ), + notIncl[i] + File.separator, false ); + } + } + + haveSlowResults = true; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/ExitException.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/ExitException.java new file mode 100644 index 000000000..8e812b1f2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/ExitException.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Used to report exit status of classes which call System.exit() + * + * @author Conor MacNeill + * @see NoExitSecurityManager + */ +public class ExitException extends SecurityException +{ + + private int status; + + /** + * Constructs an exit exception. + * + * @param status the status code returned via System.exit() + */ + public ExitException( int status ) + { + super( "ExitException: status " + status ); + this.status = status; + } + + /** + * @return the status code return via System.exit() + */ + public int getStatus() + { + return status; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/FileScanner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/FileScanner.java new file mode 100644 index 000000000..a23d95e26 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/FileScanner.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; + +/** + * An interface used to describe the actions required by any type of directory + * scanner. + * + * @author RT + */ +public interface FileScanner +{ + /** + * Adds an array with default exclusions to the current exclusions set. + */ + void addDefaultExcludes(); + + /** + * Gets the basedir that is used for scanning. This is the directory that is + * scanned recursively. + * + * @return the basedir that is used for scanning + */ + File getBasedir(); + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the directories + */ + String[] getExcludedDirectories(); + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the files + */ + String[] getExcludedFiles(); + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the directories + */ + String[] getIncludedDirectories(); + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the files + */ + String[] getIncludedFiles(); + + /** + * Get the names of the directories that matched at none of the include + * patterns. The names are relative to the basedir. + * + * @return the names of the directories + */ + String[] getNotIncludedDirectories(); + + /** + * Get the names of the files that matched at none of the include patterns. + * The names are relative to the basedir. + * + * @return the names of the files + */ + String[] getNotIncludedFiles(); + + /** + * Scans the base directory for files that match at least one include + * pattern, and don't match any exclude patterns. + * + */ + void scan(); + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the (non-null) basedir for scanning + */ + void setBasedir( String basedir ); + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the basedir for scanning + */ + void setBasedir( File basedir ); + + /** + * Sets the set of exclude patterns to use. + * + * @param excludes list of exclude patterns + */ + void setExcludes( String[] excludes ); + + /** + * Sets the set of include patterns to use. + * + * @param includes list of include patterns + */ + void setIncludes( String[] includes ); + + /** + * Sets the case sensitivity of the file system + * + * @param isCaseSensitive The new CaseSensitive value + */ + void setCaseSensitive( boolean isCaseSensitive ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/IntrospectionHelper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/IntrospectionHelper.java new file mode 100644 index 000000000..8b7160c9f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/IntrospectionHelper.java @@ -0,0 +1,848 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; + +/** + * Helper class that collects the methods a task or nested element holds to set + * attributes, create nested elements or hold PCDATA elements. + * + * @author Stefan Bodewig + */ +public class IntrospectionHelper implements BuildListener +{ + + /** + * instances we've already created + */ + private static Hashtable helpers = new Hashtable(); + + /** + * The method to add PCDATA stuff. + */ + private Method addText = null; + + /** + * holds the attribute setter methods. + */ + private Hashtable attributeSetters; + + /** + * holds the types of the attributes that could be set. + */ + private Hashtable attributeTypes; + + /** + * The Class that's been introspected. + */ + private Class bean; + + /** + * Holds methods to create nested elements. + */ + private Hashtable nestedCreators; + + /** + * Holds methods to store configured nested elements. + */ + private Hashtable nestedStorers; + + /** + * Holds the types of nested elements that could be created. + */ + private Hashtable nestedTypes; + + private IntrospectionHelper( final Class bean ) + { + attributeTypes = new Hashtable(); + attributeSetters = new Hashtable(); + nestedTypes = new Hashtable(); + nestedCreators = new Hashtable(); + nestedStorers = new Hashtable(); + + this.bean = bean; + + Method[] methods = bean.getMethods(); + for( int i = 0; i < methods.length; i++ ) + { + final Method m = methods[i]; + final String name = m.getName(); + Class returnType = m.getReturnType(); + Class[] args = m.getParameterTypes(); + + // not really user settable properties on tasks + if( org.apache.tools.ant.Task.class.isAssignableFrom( bean ) + && args.length == 1 && + ( + ( + "setLocation".equals( name ) && org.apache.tools.ant.Location.class.equals( args[0] ) + ) || ( + "setTaskType".equals( name ) && java.lang.String.class.equals( args[0] ) + ) + ) ) + { + continue; + } + + // hide addTask for TaskContainers + if( org.apache.tools.ant.TaskContainer.class.isAssignableFrom( bean ) + && args.length == 1 && "addTask".equals( name ) + && org.apache.tools.ant.Task.class.equals( args[0] ) ) + { + continue; + } + + if( "addText".equals( name ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && java.lang.String.class.equals( args[0] ) ) + { + + addText = methods[i]; + + } + else if( name.startsWith( "set" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !args[0].isArray() ) + { + + String propName = getPropertyName( name, "set" ); + if( attributeSetters.get( propName ) != null ) + { + if( java.lang.String.class.equals( args[0] ) ) + { + /* + * Ignore method m, as there is an overloaded + * form of this method that takes in a + * non-string argument, which gains higher + * priority. + */ + continue; + } + /* + * If the argument is not a String, and if there + * is an overloaded form of this method already defined, + * we just override that with the new one. + * This mechanism does not guarantee any specific order + * in which the methods will be selected: so any code + * that depends on the order in which "set" methods have + * been defined, is not guaranteed to be selected in any + * particular order. + */ + } + AttributeSetter as = createAttributeSetter( m, args[0] ); + if( as != null ) + { + attributeTypes.put( propName, args[0] ); + attributeSetters.put( propName, as ); + } + + } + else if( name.startsWith( "create" ) + && !returnType.isArray() + && !returnType.isPrimitive() + && args.length == 0 ) + { + + String propName = getPropertyName( name, "create" ); + nestedTypes.put( propName, returnType ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, + IllegalAccessException + { + + return m.invoke( parent, new Object[]{} ); + } + + } ); + + } + else if( name.startsWith( "addConfigured" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !java.lang.String.class.equals( args[0] ) + && !args[0].isArray() + && !args[0].isPrimitive() ) + { + + try + { + final Constructor c = + args[0].getConstructor( new Class[]{} ); + String propName = getPropertyName( name, "addConfigured" ); + nestedTypes.put( propName, args[0] ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + Object o = c.newInstance( new Object[]{} ); + return o; + } + + } ); + nestedStorers.put( propName, + new NestedStorer() + { + + public void store( Object parent, Object child ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + m.invoke( parent, new Object[]{child} ); + } + + } ); + } + catch( NoSuchMethodException nse ) + { + } + } + else if( name.startsWith( "add" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !java.lang.String.class.equals( args[0] ) + && !args[0].isArray() + && !args[0].isPrimitive() ) + { + + try + { + final Constructor c = + args[0].getConstructor( new Class[]{} ); + String propName = getPropertyName( name, "add" ); + nestedTypes.put( propName, args[0] ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + Object o = c.newInstance( new Object[]{} ); + m.invoke( parent, new Object[]{o} ); + return o; + } + + } ); + } + catch( NoSuchMethodException nse ) + { + } + } + } + } + + /** + * Factory method for helper objects. + * + * @param c Description of Parameter + * @return The Helper value + */ + public static synchronized IntrospectionHelper getHelper( Class c ) + { + IntrospectionHelper ih = ( IntrospectionHelper )helpers.get( c ); + if( ih == null ) + { + ih = new IntrospectionHelper( c ); + helpers.put( c, ih ); + } + return ih; + } + + /** + * Sets the named attribute. + * + * @param p The new Attribute value + * @param element The new Attribute value + * @param attributeName The new Attribute value + * @param value The new Attribute value + * @exception BuildException Description of Exception + */ + public void setAttribute( Project p, Object element, String attributeName, + String value ) + throws BuildException + { + AttributeSetter as = ( AttributeSetter )attributeSetters.get( attributeName ); + if( as == null ) + { + String msg = getElementName( p, element ) + + //String msg = "Class " + element.getClass().getName() + + " doesn't support the \"" + attributeName + "\" attribute."; + throw new BuildException( msg ); + } + try + { + as.set( p, element, value ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + /** + * returns the type of a named attribute. + * + * @param attributeName Description of Parameter + * @return The AttributeType value + * @exception BuildException Description of Exception + */ + public Class getAttributeType( String attributeName ) + throws BuildException + { + Class at = ( Class )attributeTypes.get( attributeName ); + if( at == null ) + { + String msg = "Class " + bean.getName() + + " doesn't support the \"" + attributeName + "\" attribute."; + throw new BuildException( msg ); + } + return at; + } + + /** + * Return all attribues supported by the introspected class. + * + * @return The Attributes value + */ + public Enumeration getAttributes() + { + return attributeSetters.keys(); + } + + /** + * returns the type of a named nested element. + * + * @param elementName Description of Parameter + * @return The ElementType value + * @exception BuildException Description of Exception + */ + public Class getElementType( String elementName ) + throws BuildException + { + Class nt = ( Class )nestedTypes.get( elementName ); + if( nt == null ) + { + String msg = "Class " + bean.getName() + + " doesn't support the nested \"" + elementName + "\" element."; + throw new BuildException( msg ); + } + return nt; + } + + /** + * Return all nested elements supported by the introspected class. + * + * @return The NestedElements value + */ + public Enumeration getNestedElements() + { + return nestedTypes.keys(); + } + + /** + * Adds PCDATA areas. + * + * @param project The feature to be added to the Text attribute + * @param element The feature to be added to the Text attribute + * @param text The feature to be added to the Text attribute + */ + public void addText( Project project, Object element, String text ) + { + if( addText == null ) + { + String msg = getElementName( project, element ) + + //String msg = "Class " + element.getClass().getName() + + " doesn't support nested text data."; + throw new BuildException( msg ); + } + try + { + addText.invoke( element, new String[]{text} ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + public void buildFinished( BuildEvent event ) + { + attributeTypes.clear(); + attributeSetters.clear(); + nestedTypes.clear(); + nestedCreators.clear(); + addText = null; + helpers.clear(); + } + + public void buildStarted( BuildEvent event ) { } + + /** + * Creates a named nested element. + * + * @param project Description of Parameter + * @param element Description of Parameter + * @param elementName Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Object createElement( Project project, Object element, String elementName ) + throws BuildException + { + NestedCreator nc = ( NestedCreator )nestedCreators.get( elementName ); + if( nc == null ) + { + String msg = getElementName( project, element ) + + " doesn't support the nested \"" + elementName + "\" element."; + throw new BuildException( msg ); + } + try + { + Object nestedElement = nc.create( element ); + if( nestedElement instanceof ProjectComponent ) + { + ( ( ProjectComponent )nestedElement ).setProject( project ); + } + return nestedElement; + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InstantiationException ine ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ine ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + public void messageLogged( BuildEvent event ) { } + + /** + * Creates a named nested element. + * + * @param project Description of Parameter + * @param element Description of Parameter + * @param child Description of Parameter + * @param elementName Description of Parameter + * @exception BuildException Description of Exception + */ + public void storeElement( Project project, Object element, Object child, String elementName ) + throws BuildException + { + if( elementName == null ) + { + return; + } + NestedStorer ns = ( NestedStorer )nestedStorers.get( elementName ); + if( ns == null ) + { + return; + } + try + { + ns.store( element, child ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InstantiationException ine ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ine ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + /** + * Does the introspected class support PCDATA? + * + * @return Description of the Returned Value + */ + public boolean supportsCharacters() + { + return addText != null; + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) { } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + protected String getElementName( Project project, Object element ) + { + Hashtable elements = project.getTaskDefinitions(); + String typeName = "task"; + if( !elements.contains( element.getClass() ) ) + { + elements = project.getDataTypeDefinitions(); + typeName = "data type"; + if( !elements.contains( element.getClass() ) ) + { + elements = null; + } + } + + if( elements != null ) + { + Enumeration e = elements.keys(); + while( e.hasMoreElements() ) + { + String elementName = ( String )e.nextElement(); + Class elementClass = ( Class )elements.get( elementName ); + if( element.getClass().equals( elementClass ) ) + { + return "The <" + elementName + "> " + typeName; + } + } + } + + return "Class " + element.getClass().getName(); + } + + /** + * extract the name of a property from a method name - subtracting a given + * prefix. + * + * @param methodName Description of Parameter + * @param prefix Description of Parameter + * @return The PropertyName value + */ + private String getPropertyName( String methodName, String prefix ) + { + int start = prefix.length(); + return methodName.substring( start ).toLowerCase( Locale.US ); + } + + /** + * Create a proper implementation of AttributeSetter for the given attribute + * type. + * + * @param m Description of Parameter + * @param arg Description of Parameter + * @return Description of the Returned Value + */ + private AttributeSetter createAttributeSetter( final Method m, + final Class arg ) + { + + // simplest case - setAttribute expects String + if( java.lang.String.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new String[]{value} ); + } + }; + // now for the primitive types, use their wrappers + } + else if( java.lang.Character.class.equals( arg ) + || java.lang.Character.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Character[]{new Character( value.charAt( 0 ) )} ); + } + + }; + } + else if( java.lang.Byte.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Byte[]{new Byte( value )} ); + } + + }; + } + else if( java.lang.Short.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Short[]{new Short( value )} ); + } + + }; + } + else if( java.lang.Integer.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Integer[]{new Integer( value )} ); + } + + }; + } + else if( java.lang.Long.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Long[]{new Long( value )} ); + } + + }; + } + else if( java.lang.Float.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Float[]{new Float( value )} ); + } + + }; + } + else if( java.lang.Double.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Double[]{new Double( value )} ); + } + + }; + // boolean gets an extra treatment, because we have a nice method + // in Project + } + else if( java.lang.Boolean.class.equals( arg ) + || java.lang.Boolean.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, + new Boolean[]{new Boolean( Project.toBoolean( value ) )} ); + } + + }; + // Class doesn't have a String constructor but a decent factory method + } + else if( java.lang.Class.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + m.invoke( parent, new Class[]{Class.forName( value )} ); + } + catch( ClassNotFoundException ce ) + { + throw new BuildException( ce ); + } + } + }; + // resolve relative paths through Project + } + else if( java.io.File.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new File[]{p.resolveFile( value )} ); + } + + }; + // resolve relative paths through Project + } + else if( org.apache.tools.ant.types.Path.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Path[]{new Path( p, value )} ); + } + + }; + // EnumeratedAttributes have their own helper class + } + else if( org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + org.apache.tools.ant.types.EnumeratedAttribute ea = ( org.apache.tools.ant.types.EnumeratedAttribute )arg.newInstance(); + ea.setValue( value ); + m.invoke( parent, new EnumeratedAttribute[]{ea} ); + } + catch( InstantiationException ie ) + { + throw new BuildException( ie ); + } + } + }; + + // worst case. look for a public String constructor and use it + } + else + { + + try + { + final Constructor c = + arg.getConstructor( new Class[]{java.lang.String.class} ); + + return + new AttributeSetter() + { + public void set( Project p, Object parent, + String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + Object attribute = c.newInstance( new String[]{value} ); + if( attribute instanceof ProjectComponent ) + { + ( ( ProjectComponent )attribute ).setProject( p ); + } + m.invoke( parent, new Object[]{attribute} ); + } + catch( InstantiationException ie ) + { + throw new BuildException( ie ); + } + } + }; + } + catch( NoSuchMethodException nme ) + { + } + } + + return null; + } + + private interface AttributeSetter + { + void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, + BuildException; + } + + private interface NestedCreator + { + Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException; + } + + private interface NestedStorer + { + void store( Object parent, Object child ) + throws InvocationTargetException, IllegalAccessException, InstantiationException; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Launcher.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Launcher.java new file mode 100644 index 000000000..0a220ec89 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Launcher.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FilenameFilter; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + * This is the Ant command line front end to end. This front end works out where + * ant is installed and loads the ant libraries before starting Ant proper. + * + * @author Conor MacNeill + */ +public class Launcher +{ + + public static void main( String[] args ) + { + File antHome = null; + ClassLoader systemClassLoader = Launcher.class.getClassLoader(); + if( systemClassLoader == null ) + { + antHome = determineAntHome11(); + } + else + { + antHome = determineAntHome( systemClassLoader ); + } + if( antHome == null ) + { + System.err.println( "Unable to determine ANT_HOME" ); + System.exit( 1 ); + } + + System.out.println( "ANT_HOME is " + antHome ); + + // We now create the class loader with which we are going to launch ant + AntClassLoader antLoader = new AntClassLoader( systemClassLoader, false ); + + // need to find tools.jar + addToolsJar( antLoader ); + + // add everything in the lib directory to this classloader + File libDir = new File( antHome, "lib" ); + addDirJars( antLoader, libDir ); + + File optionalDir = new File( antHome, "lib/optional" ); + addDirJars( antLoader, optionalDir ); + + Properties launchProperties = new Properties(); + launchProperties.put( "ant.home", antHome.getAbsolutePath() ); + + try + { + Class mainClass = antLoader.loadClass( "org.apache.tools.ant.Main" ); + antLoader.initializeClass( mainClass ); + + final Class[] param = {Class.forName( "[Ljava.lang.String;" ), + Properties.class, ClassLoader.class}; + final Method startMethod = mainClass.getMethod( "start", param ); + final Object[] argument = {args, launchProperties, systemClassLoader}; + startMethod.invoke( null, argument ); + } + catch( Exception e ) + { + System.out.println( "Exception running Ant: " + e.getClass().getName() + ": " + e.getMessage() ); + e.printStackTrace(); + } + } + + private static void addDirJars( AntClassLoader classLoader, File jarDir ) + { + String[] fileList = jarDir.list( + new FilenameFilter() + { + public boolean accept( File dir, String name ) + { + return name.endsWith( ".jar" ); + } + } ); + + if( fileList != null ) + { + for( int i = 0; i < fileList.length; ++i ) + { + File jarFile = new File( jarDir, fileList[i] ); + classLoader.addPathElement( jarFile.getAbsolutePath() ); + } + } + } + + private static void addToolsJar( AntClassLoader antLoader ) + { + String javaHome = System.getProperty( "java.home" ); + if( javaHome.endsWith( "jre" ) ) + { + javaHome = javaHome.substring( 0, javaHome.length() - 4 ); + } + System.out.println( "Java home is " + javaHome ); + File toolsJar = new File( javaHome, "lib/tools.jar" ); + if( !toolsJar.exists() ) + { + System.out.println( "Unable to find tools.jar at " + toolsJar.getPath() ); + } + else + { + antLoader.addPathElement( toolsJar.getAbsolutePath() ); + } + } + + private static File determineAntHome( ClassLoader systemClassLoader ) + { + try + { + String className = Launcher.class.getName().replace( '.', '/' ) + ".class"; + URL classResource = systemClassLoader.getResource( className ); + String fileComponent = classResource.getFile(); + if( classResource.getProtocol().equals( "file" ) ) + { + // Class comes from a directory of class files rather than + // from a jar. + int classFileIndex = fileComponent.lastIndexOf( className ); + if( classFileIndex != -1 ) + { + fileComponent = fileComponent.substring( 0, classFileIndex ); + } + File classFilesDir = new File( fileComponent ); + File buildDir = new File( classFilesDir.getParent() ); + File devAntHome = new File( buildDir.getParent() ); + return devAntHome; + } + else if( classResource.getProtocol().equals( "jar" ) ) + { + // Class is coming from a jar. The file component of the URL + // is actually the URL of the jar file + int classSeparatorIndex = fileComponent.lastIndexOf( "!" ); + if( classSeparatorIndex != -1 ) + { + fileComponent = fileComponent.substring( 0, classSeparatorIndex ); + } + URL antJarURL = new URL( fileComponent ); + File antJarFile = new File( antJarURL.getFile() ); + File libDirectory = new File( antJarFile.getParent() ); + File antHome = new File( libDirectory.getParent() ); + return antHome; + } + } + catch( MalformedURLException e ) + { + e.printStackTrace(); + } + return null; + } + + private static File determineAntHome11() + { + String classpath = System.getProperty( "java.class.path" ); + StringTokenizer tokenizer = new StringTokenizer( classpath, System.getProperty( "path.separator" ) ); + while( tokenizer.hasMoreTokens() ) + { + String path = tokenizer.nextToken(); + if( path.endsWith( "ant.jar" ) ) + { + File antJarFile = new File( path ); + File libDirectory = new File( antJarFile.getParent() ); + File antHome = new File( libDirectory.getParent() ); + return antHome; + } + } + return null; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Location.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Location.java new file mode 100644 index 000000000..e48a844dc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Location.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Stores the file name and line number in a file. + * + * @author RT + */ +public class Location +{ + + public final static Location UNKNOWN_LOCATION = new Location(); + private int columnNumber; + private String fileName; + private int lineNumber; + + /** + * Creates a location consisting of a file name but no line number. + * + * @param fileName Description of Parameter + */ + public Location( String fileName ) + { + this( fileName, 0, 0 ); + } + + /** + * Creates a location consisting of a file name and line number. + * + * @param fileName Description of Parameter + * @param lineNumber Description of Parameter + * @param columnNumber Description of Parameter + */ + public Location( String fileName, int lineNumber, int columnNumber ) + { + this.fileName = fileName; + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + } + + /** + * Creates an "unknown" location. + */ + private Location() + { + this( null, 0, 0 ); + } + + /** + * Returns the file name, line number and a trailing space. An error message + * can be appended easily. For unknown locations, returns an empty string. + * + * @return Description of the Returned Value + */ + public String toString() + { + StringBuffer buf = new StringBuffer(); + + if( fileName != null ) + { + buf.append( fileName ); + + if( lineNumber != 0 ) + { + buf.append( ":" ); + buf.append( lineNumber ); + } + + buf.append( ": " ); + } + + return buf.toString(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Main.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Main.java new file mode 100644 index 000000000..0c605f732 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Main.java @@ -0,0 +1,842 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Enumeration; +import java.util.Properties; +import java.util.Vector; + +/** + * Command line entry point into Ant. This class is entered via the cannonical + * `public static void main` entry point and reads the command line arguments. + * It then assembles and executes an Ant project.

          + * + * If you integrating Ant into some other tool, this is not the class to use as + * an entry point. Please see the source code of this class to see how it + * manipulates the Ant project classes. + * + * @author duncan@x180.com + */ +public class Main +{ + + /** + * The default build file name + */ + public final static String DEFAULT_BUILD_FILENAME = "build.xml"; + + private static String antVersion = null; + + /** + * Our current message output status. Follows Project.MSG_XXX + */ + private int msgOutputLevel = Project.MSG_INFO; + /** + * null + */ + + /** + * Stream that we are using for logging + */ + private PrintStream out = System.out; + + /** + * Stream that we are using for logging error messages + */ + private PrintStream err = System.err; + + /** + * The build targets + */ + private Vector targets = new Vector( 5 ); + + /** + * Set of properties that can be used by tasks + */ + private Properties definedProps = new Properties(); + + /** + * Names of classes to add as listeners to project + */ + private Vector listeners = new Vector( 5 ); + + /** + * The Ant logger class. There may be only one logger. It will have the + * right to use the 'out' PrintStream. The class must implements the + * BuildLogger interface + */ + private String loggerClassname = null; + + /** + * Indicates whether output to the log is to be unadorned. + */ + private boolean emacsMode = false; + + /** + * Indicates if this ant should be run. + */ + private boolean readyToRun = false; + + /** + * Indicates we should only parse and display the project help information + */ + private boolean projectHelp = false; + + /** + * File that we are using for configuration + */ + private File buildFile; + + protected Main( String[] args ) + throws BuildException + { + + String searchForThis = null; + + // cycle through given args + + for( int i = 0; i < args.length; i++ ) + { + String arg = args[i]; + + if( arg.equals( "-help" ) ) + { + printUsage(); + return; + } + else if( arg.equals( "-version" ) ) + { + printVersion(); + return; + } + else if( arg.equals( "-quiet" ) || arg.equals( "-q" ) ) + { + msgOutputLevel = Project.MSG_WARN; + } + else if( arg.equals( "-verbose" ) || arg.equals( "-v" ) ) + { + printVersion(); + msgOutputLevel = Project.MSG_VERBOSE; + } + else if( arg.equals( "-debug" ) ) + { + printVersion(); + msgOutputLevel = Project.MSG_DEBUG; + } + else if( arg.equals( "-logfile" ) || arg.equals( "-l" ) ) + { + try + { + File logFile = new File( args[i + 1] ); + i++; + out = new PrintStream( new FileOutputStream( logFile ) ); + err = out; + System.setOut( out ); + System.setErr( out ); + } + catch( IOException ioe ) + { + String msg = "Cannot write on the specified log file. " + + "Make sure the path exists and you have write permissions."; + System.out.println( msg ); + return; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a log file when " + + "using the -log argument"; + System.out.println( msg ); + return; + } + } + else if( arg.equals( "-buildfile" ) || arg.equals( "-file" ) || arg.equals( "-f" ) ) + { + try + { + buildFile = new File( args[i + 1] ); + i++; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a buildfile when " + + "using the -buildfile argument"; + System.out.println( msg ); + return; + } + } + else if( arg.equals( "-listener" ) ) + { + try + { + listeners.addElement( args[i + 1] ); + i++; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a classname when " + + "using the -listener argument"; + System.out.println( msg ); + return; + } + } + else if( arg.startsWith( "-D" ) ) + { + + /* + * Interestingly enough, we get to here when a user + * uses -Dname=value. However, in some cases, the JDK + * goes ahead * and parses this out to args + * {"-Dname", "value"} + * so instead of parsing on "=", we just make the "-D" + * characters go away and skip one argument forward. + * + * I don't know how to predict when the JDK is going + * to help or not, so we simply look for the equals sign. + */ + String name = arg.substring( 2, arg.length() ); + String value = null; + int posEq = name.indexOf( "=" ); + if( posEq > 0 ) + { + value = name.substring( posEq + 1 ); + name = name.substring( 0, posEq ); + } + else if( i < args.length - 1 ) + value = args[++i]; + + definedProps.put( name, value ); + } + else if( arg.equals( "-logger" ) ) + { + if( loggerClassname != null ) + { + System.out.println( "Only one logger class may be specified." ); + return; + } + try + { + loggerClassname = args[++i]; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + System.out.println( "You must specify a classname when " + + "using the -logger argument" ); + return; + } + } + else if( arg.equals( "-emacs" ) ) + { + emacsMode = true; + } + else if( arg.equals( "-projecthelp" ) ) + { + // set the flag to display the targets and quit + projectHelp = true; + } + else if( arg.equals( "-find" ) ) + { + // eat up next arg if present, default to build.xml + if( i < args.length - 1 ) + { + searchForThis = args[++i]; + } + else + { + searchForThis = DEFAULT_BUILD_FILENAME; + } + } + else if( arg.startsWith( "-" ) ) + { + // we don't have any more args to recognize! + String msg = "Unknown argument: " + arg; + System.out.println( msg ); + printUsage(); + return; + } + else + { + // if it's no other arg, it may be the target + targets.addElement( arg ); + } + + } + + // if buildFile was not specified on the command line, + if( buildFile == null ) + { + // but -find then search for it + if( searchForThis != null ) + { + buildFile = findBuildFile( System.getProperty( "user.dir" ), + searchForThis ); + } + else + { + buildFile = new File( DEFAULT_BUILD_FILENAME ); + } + } + + // make sure buildfile exists + if( !buildFile.exists() ) + { + System.out.println( "Buildfile: " + buildFile + " does not exist!" ); + throw new BuildException( "Build failed" ); + } + + // make sure it's not a directory (this falls into the ultra + // paranoid lets check everything catagory + + if( buildFile.isDirectory() ) + { + System.out.println( "What? Buildfile: " + buildFile + " is a dir!" ); + throw new BuildException( "Build failed" ); + } + + readyToRun = true; + } + + public static synchronized String getAntVersion() + throws BuildException + { + if( antVersion == null ) + { + try + { + Properties props = new Properties(); + InputStream in = + Main.class.getResourceAsStream( "/org/apache/tools/ant/version.txt" ); + props.load( in ); + in.close(); + + String lSep = System.getProperty( "line.separator" ); + StringBuffer msg = new StringBuffer(); + msg.append( "Apache Ant version " ); + msg.append( props.getProperty( "VERSION" ) ); + msg.append( " compiled on " ); + msg.append( props.getProperty( "DATE" ) ); + antVersion = msg.toString(); + } + catch( IOException ioe ) + { + throw new BuildException( "Could not load the version information:" + + ioe.getMessage() ); + } + catch( NullPointerException npe ) + { + throw new BuildException( "Could not load the version information." ); + } + } + return antVersion; + } + + + /** + * Command line entry point. This method kicks off the building of a project + * object and executes a build using either a given target or the default + * target. + * + * @param args Command line args. + */ + public static void main( String[] args ) + { + start( args, null, null ); + } + + /** + * Entry point method. + * + * @param args Description of Parameter + * @param additionalUserProperties Description of Parameter + * @param coreLoader Description of Parameter + */ + public static void start( String[] args, Properties additionalUserProperties, + ClassLoader coreLoader ) + { + Main m = null; + + try + { + m = new Main( args ); + } + catch( Throwable exc ) + { + printMessage( exc ); + System.exit( 1 ); + } + + if( additionalUserProperties != null ) + { + for( Enumeration e = additionalUserProperties.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + String property = additionalUserProperties.getProperty( key ); + m.definedProps.put( key, property ); + } + } + + try + { + m.runBuild( coreLoader ); + System.exit( 0 ); + } + catch( BuildException be ) + { + if( m.err != System.err ) + { + printMessage( be ); + } + System.exit( 1 ); + } + catch( Throwable exc ) + { + exc.printStackTrace(); + printMessage( exc ); + System.exit( 1 ); + } + } + + /** + * Search for the insert position to keep names a sorted list of Strings + * + * @param names Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private static int findTargetPosition( Vector names, String name ) + { + int res = names.size(); + for( int i = 0; i < names.size() && res == names.size(); i++ ) + { + if( name.compareTo( ( String )names.elementAt( i ) ) < 0 ) + { + res = i; + } + } + return res; + } + + /** + * Print the project description, if any + * + * @param project Description of Parameter + */ + private static void printDescription( Project project ) + { + if( project.getDescription() != null ) + { + System.out.println( project.getDescription() ); + } + } + + /** + * Prints the message of the Throwable if it's not null. + * + * @param t Description of Parameter + */ + private static void printMessage( Throwable t ) + { + String message = t.getMessage(); + if( message != null ) + { + System.err.println( message ); + } + } + + /** + * Print out a list of all targets in the current buildfile + * + * @param project Description of Parameter + * @param printSubTargets Description of Parameter + */ + private static void printTargets( Project project, boolean printSubTargets ) + { + // find the target with the longest name + int maxLength = 0; + Enumeration ptargets = project.getTargets().elements(); + String targetName; + String targetDescription; + Target currentTarget; + // split the targets in top-level and sub-targets depending + // on the presence of a description + Vector topNames = new Vector(); + Vector topDescriptions = new Vector(); + Vector subNames = new Vector(); + + while( ptargets.hasMoreElements() ) + { + currentTarget = ( Target )ptargets.nextElement(); + targetName = currentTarget.getName(); + targetDescription = currentTarget.getDescription(); + // maintain a sorted list of targets + if( targetDescription == null ) + { + int pos = findTargetPosition( subNames, targetName ); + subNames.insertElementAt( targetName, pos ); + } + else + { + int pos = findTargetPosition( topNames, targetName ); + topNames.insertElementAt( targetName, pos ); + topDescriptions.insertElementAt( targetDescription, pos ); + if( targetName.length() > maxLength ) + { + maxLength = targetName.length(); + } + } + } + + printTargets( topNames, topDescriptions, "Main targets:", maxLength ); + + if( printSubTargets ) + { + printTargets( subNames, null, "Subtargets:", 0 ); + } + + String defaultTarget = project.getDefaultTarget(); + if( defaultTarget != null && !"".equals( defaultTarget ) ) + {// shouldn't need to check but... + System.out.println( "Default target: " + defaultTarget ); + } + } + + /** + * Output a formatted list of target names with an optional description + * + * @param names Description of Parameter + * @param descriptions Description of Parameter + * @param heading Description of Parameter + * @param maxlen Description of Parameter + */ + private static void printTargets( Vector names, Vector descriptions, String heading, int maxlen ) + { + // now, start printing the targets and their descriptions + String lSep = System.getProperty( "line.separator" ); + // got a bit annoyed that I couldn't find a pad function + String spaces = " "; + while( spaces.length() < maxlen ) + { + spaces += spaces; + } + StringBuffer msg = new StringBuffer(); + msg.append( heading + lSep + lSep ); + for( int i = 0; i < names.size(); i++ ) + { + msg.append( " " ); + msg.append( names.elementAt( i ) ); + if( descriptions != null ) + { + msg.append( spaces.substring( 0, maxlen - ( ( String )names.elementAt( i ) ).length() + 2 ) ); + msg.append( descriptions.elementAt( i ) ); + } + msg.append( lSep ); + } + System.out.println( msg.toString() ); + } + + /** + * Prints the usage of how to use this class to System.out + */ + private static void printUsage() + { + String lSep = System.getProperty( "line.separator" ); + StringBuffer msg = new StringBuffer(); + msg.append( "ant [options] [target [target2 [target3] ...]]" + lSep ); + msg.append( "Options: " + lSep ); + msg.append( " -help print this message" + lSep ); + msg.append( " -projecthelp print project help information" + lSep ); + msg.append( " -version print the version information and exit" + lSep ); + msg.append( " -quiet be extra quiet" + lSep ); + msg.append( " -verbose be extra verbose" + lSep ); + msg.append( " -debug print debugging information" + lSep ); + msg.append( " -emacs produce logging information without adornments" + lSep ); + msg.append( " -logfile use given file for log" + lSep ); + msg.append( " -logger the class which is to perform logging" + lSep ); + msg.append( " -listener add an instance of class as a project listener" + lSep ); + msg.append( " -buildfile use given buildfile" + lSep ); + msg.append( " -D= use value for given property" + lSep ); + msg.append( " -find search for buildfile towards the root of the" + lSep ); + msg.append( " filesystem and use it" + lSep ); + System.out.println( msg.toString() ); + } + + private static void printVersion() + throws BuildException + { + System.out.println( getAntVersion() ); + } + + protected void addBuildListeners( Project project ) + { + + // Add the default listener + project.addBuildListener( createLogger() ); + + for( int i = 0; i < listeners.size(); i++ ) + { + String className = ( String )listeners.elementAt( i ); + try + { + BuildListener listener = + ( BuildListener )Class.forName( className ).newInstance(); + project.addBuildListener( listener ); + } + catch( Throwable exc ) + { + throw new BuildException( "Unable to instantiate listener " + className, exc ); + } + } + } + + /** + * Helper to get the parent file for a given file.

          + * + * Added to simulate File.getParentFile() from JDK 1.2. + * + * @param file File + * @return Parent file or null if none + */ + private File getParentFile( File file ) + { + String filename = file.getAbsolutePath(); + file = new File( filename ); + filename = file.getParent(); + + if( filename != null && msgOutputLevel >= Project.MSG_VERBOSE ) + { + System.out.println( "Searching in " + filename ); + } + + return ( filename == null ) ? null : new File( filename ); + } + + /** + * Creates the default build logger for sending build events to the ant log. + * + * @return Description of the Returned Value + */ + private BuildLogger createLogger() + { + BuildLogger logger = null; + if( loggerClassname != null ) + { + try + { + logger = ( BuildLogger )( Class.forName( loggerClassname ).newInstance() ); + } + catch( ClassCastException e ) + { + System.err.println( "The specified logger class " + loggerClassname + + " does not implement the BuildLogger interface" ); + throw new RuntimeException(); + } + catch( Exception e ) + { + System.err.println( "Unable to instantiate specified logger class " + + loggerClassname + " : " + e.getClass().getName() ); + throw new RuntimeException(); + } + } + else + { + logger = new DefaultLogger(); + } + + logger.setMessageOutputLevel( msgOutputLevel ); + logger.setOutputPrintStream( out ); + logger.setErrorPrintStream( err ); + logger.setEmacsMode( emacsMode ); + + return logger; + } + + /** + * Search parent directories for the build file.

          + * + * Takes the given target as a suffix to append to each parent directory in + * seach of a build file. Once the root of the file-system has been reached + * an exception is thrown. + * + * @param suffix Suffix filename to look for in parents. + * @param start Description of Parameter + * @return A handle to the build file + * @exception BuildException Failed to locate a build file + */ + private File findBuildFile( String start, String suffix ) + throws BuildException + { + if( msgOutputLevel >= Project.MSG_INFO ) + { + System.out.println( "Searching for " + suffix + " ..." ); + } + + File parent = new File( new File( start ).getAbsolutePath() ); + File file = new File( parent, suffix ); + + // check if the target file exists in the current directory + while( !file.exists() ) + { + // change to parent directory + parent = getParentFile( parent ); + + // if parent is null, then we are at the root of the fs, + // complain that we can't find the build file. + if( parent == null ) + { + throw new BuildException( "Could not locate a build file!" ); + } + + // refresh our file handle + file = new File( parent, suffix ); + } + + return file; + } + + /** + * Executes the build. + * + * @param coreLoader Description of Parameter + * @exception BuildException Description of Exception + */ + private void runBuild( ClassLoader coreLoader ) + throws BuildException + { + + if( !readyToRun ) + { + return; + } + + // track when we started + + if( msgOutputLevel >= Project.MSG_INFO ) + { + System.out.println( "Buildfile: " + buildFile ); + } + + final Project project = new Project(); + project.setCoreLoader( coreLoader ); + + Throwable error = null; + + try + { + addBuildListeners( project ); + + PrintStream err = System.err; + PrintStream out = System.out; + + // use a system manager that prevents from System.exit() + // only in JDK > 1.1 + SecurityManager oldsm = null; + if( !Project.JAVA_1_0.equals( Project.getJavaVersion() ) && + !Project.JAVA_1_1.equals( Project.getJavaVersion() ) ) + { + oldsm = System.getSecurityManager(); + + //SecurityManager can not be installed here for backwards + //compatability reasons (PD). Needs to be loaded prior to + //ant class if we are going to implement it. + //System.setSecurityManager(new NoExitSecurityManager()); + } + try + { + System.setOut( new PrintStream( new DemuxOutputStream( project, false ) ) ); + System.setErr( new PrintStream( new DemuxOutputStream( project, true ) ) ); + + if( !projectHelp ) + { + project.fireBuildStarted(); + } + project.init(); + project.setUserProperty( "ant.version", getAntVersion() ); + + // set user-define properties + Enumeration e = definedProps.keys(); + while( e.hasMoreElements() ) + { + String arg = ( String )e.nextElement(); + String value = ( String )definedProps.get( arg ); + project.setUserProperty( arg, value ); + } + + project.setUserProperty( "ant.file", buildFile.getAbsolutePath() ); + + // first use the ProjectHelper to create the project object + // from the given build file. + String noParserMessage = + "No JAXP compliant XML parser found. Please visit http://xml.apache.org for a suitable parser"; + try + { + Class.forName( "javax.xml.parsers.SAXParserFactory" ); + ProjectHelper.configureProject( project, buildFile ); + } + catch( NoClassDefFoundError ncdfe ) + { + throw new BuildException( noParserMessage, ncdfe ); + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( noParserMessage, cnfe ); + } + catch( NullPointerException npe ) + { + throw new BuildException( noParserMessage, npe ); + } + + if( projectHelp ) + { + printDescription( project ); + printTargets( project, msgOutputLevel > Project.MSG_INFO ); + return; + } + + // make sure that we have a target to execute + if( targets.size() == 0 ) + { + targets.addElement( project.getDefaultTarget() ); + } + + project.executeTargets( targets ); + } + finally + { + // put back the original security manager + //The following will never eval to true. (PD) + if( oldsm != null ) + { + System.setSecurityManager( oldsm ); + } + + System.setOut( out ); + System.setErr( err ); + } + } + catch( RuntimeException exc ) + { + error = exc; + throw exc; + } + catch( Error err ) + { + error = err; + throw err; + } + finally + { + if( !projectHelp ) + { + project.fireBuildFinished( error ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/NoBannerLogger.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/NoBannerLogger.java new file mode 100644 index 000000000..9311265f5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/NoBannerLogger.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Extends DefaultLogger to strip out empty targets. + * + * @author Peter Donald + */ +public class NoBannerLogger extends DefaultLogger +{ + + private final static String lSep = System.getProperty( "line.separator" ); + + protected String targetName; + + public void messageLogged( BuildEvent event ) + { + + if( event.getPriority() > msgOutputLevel || + null == event.getMessage() || + "".equals( event.getMessage().trim() ) ) + { + return; + } + + if( null != targetName ) + { + out.println( lSep + targetName + ":" ); + targetName = null; + } + + super.messageLogged( event ); + } + + public void targetFinished( BuildEvent event ) + { + targetName = null; + } + + public void targetStarted( BuildEvent event ) + { + targetName = event.getTarget().getName(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/PathTokenizer.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/PathTokenizer.java new file mode 100644 index 000000000..c66d94a07 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/PathTokenizer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +/** + * A Path tokenizer takes a path and returns the components that make up that + * path. The path can use path separators of either ':' or ';' and file + * separators of either '/' or '\' + * + * @author Conor MacNeill (conor@ieee.org) + */ +public class PathTokenizer +{ + + /** + * A String which stores any path components which have been read ahead. + */ + private String lookahead = null; + + /** + * Flag to indicate whether we are running on a platform with a DOS style + * filesystem + */ + private boolean dosStyleFilesystem; + /** + * A tokenizer to break the string up based on the ':' or ';' separators. + */ + private StringTokenizer tokenizer; + + public PathTokenizer( String path ) + { + tokenizer = new StringTokenizer( path, ":;", false ); + dosStyleFilesystem = File.pathSeparatorChar == ';'; + } + + public boolean hasMoreTokens() + { + if( lookahead != null ) + { + return true; + } + + return tokenizer.hasMoreTokens(); + } + + public String nextToken() + throws NoSuchElementException + { + String token = null; + if( lookahead != null ) + { + token = lookahead; + lookahead = null; + } + else + { + token = tokenizer.nextToken().trim(); + } + + if( token.length() == 1 && Character.isLetter( token.charAt( 0 ) ) + && dosStyleFilesystem + && tokenizer.hasMoreTokens() ) + { + // we are on a dos style system so this path could be a drive + // spec. We look at the next token + String nextToken = tokenizer.nextToken().trim(); + if( nextToken.startsWith( "\\" ) || nextToken.startsWith( "/" ) ) + { + // we know we are on a DOS style platform and the next path starts with a + // slash or backslash, so we know this is a drive spec + token += ":" + nextToken; + } + else + { + // store the token just read for next time + lookahead = nextToken; + } + } + + return token; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Project.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Project.java new file mode 100644 index 000000000..c509b8795 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Project.java @@ -0,0 +1,1575 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Modifier; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Stack; +import java.util.Vector; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; +import org.apache.tools.ant.util.FileUtils; + +/** + * Central representation of an Ant project. This class defines a Ant project + * with all of it's targets and tasks. It also provides the mechanism to kick + * off a build using a particular target name.

          + * + * This class also encapsulates methods which allow Files to be refered to using + * abstract path names which are translated to native system file paths at + * runtime as well as defining various project properties. + * + * @author duncan@x180.com + */ + +public class Project +{ + + public final static int MSG_ERR = 0; + public final static int MSG_WARN = 1; + public final static int MSG_INFO = 2; + public final static int MSG_VERBOSE = 3; + public final static int MSG_DEBUG = 4; + + // private set of constants to represent the state + // of a DFS of the Target dependencies + private final static String VISITING = "VISITING"; + private final static String VISITED = "VISITED"; + + public final static String JAVA_1_0 = "1.0"; + public final static String JAVA_1_1 = "1.1"; + public final static String JAVA_1_2 = "1.2"; + public final static String JAVA_1_3 = "1.3"; + public final static String JAVA_1_4 = "1.4"; + + public final static String TOKEN_START = FilterSet.DEFAULT_TOKEN_START; + public final static String TOKEN_END = FilterSet.DEFAULT_TOKEN_END; + + private static String javaVersion; + + private Hashtable properties = new Hashtable(); + private Hashtable userProperties = new Hashtable(); + private Hashtable references = new Hashtable(); + private Hashtable dataClassDefinitions = new Hashtable(); + private Hashtable taskClassDefinitions = new Hashtable(); + private Hashtable createdTasks = new Hashtable(); + private Hashtable targets = new Hashtable(); + private FilterSet globalFilterSet = new FilterSet(); + private FilterSetCollection globalFilters = new FilterSetCollection( globalFilterSet ); + + private Vector listeners = new Vector(); + + /** + * The Ant core classloader - may be null if using system loader + */ + private ClassLoader coreLoader = null; + + /** + * Records the latest task on a thread + */ + private Hashtable threadTasks = new Hashtable(); + private File baseDir; + private String defaultTarget; + private String description; + + private FileUtils fileUtils; + + private String name; + + static + { + + // Determine the Java version by looking at available classes + // java.lang.StrictMath was introduced in JDK 1.3 + // java.lang.ThreadLocal was introduced in JDK 1.2 + // java.lang.Void was introduced in JDK 1.1 + // Count up version until a NoClassDefFoundError ends the try + + try + { + javaVersion = JAVA_1_0; + Class.forName( "java.lang.Void" ); + javaVersion = JAVA_1_1; + Class.forName( "java.lang.ThreadLocal" ); + javaVersion = JAVA_1_2; + Class.forName( "java.lang.StrictMath" ); + javaVersion = JAVA_1_3; + Class.forName( "java.lang.CharSequence" ); + javaVersion = JAVA_1_4; + } + catch( ClassNotFoundException cnfe ) + { + // swallow as we've hit the max class version that + // we have + } + } + + /** + * create a new ant project + */ + public Project() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * static query of the java version + * + * @return something like "1.1" or "1.3" + */ + public static String getJavaVersion() + { + return javaVersion; + } + + /** + * returns the boolean equivalent of a string, which is considered true if + * either "on", "true", or "yes" is found, ignoring case. + * + * @param s Description of Parameter + * @return Description of the Returned Value + */ + public static boolean toBoolean( String s ) + { + return ( s.equalsIgnoreCase( "on" ) || + s.equalsIgnoreCase( "true" ) || + s.equalsIgnoreCase( "yes" ) ); + } + + /** + * Translate a path into its native (platform specific) format.

          + * + * This method uses the PathTokenizer class to separate the input path into + * its components. This handles DOS style paths in a relatively sensible + * way. The file separators are then converted to their platform specific + * versions. + * + * @param to_process the path to be converted + * @return the native version of to_process or an empty string if to_process + * is null or empty + */ + public static String translatePath( String to_process ) + { + if( to_process == null || to_process.length() == 0 ) + { + return ""; + } + + StringBuffer path = new StringBuffer( to_process.length() + 50 ); + PathTokenizer tokenizer = new PathTokenizer( to_process ); + while( tokenizer.hasMoreTokens() ) + { + String pathComponent = tokenizer.nextToken(); + pathComponent = pathComponent.replace( '/', File.separatorChar ); + pathComponent = pathComponent.replace( '\\', File.separatorChar ); + if( path.length() != 0 ) + { + path.append( File.pathSeparatorChar ); + } + path.append( pathComponent ); + } + + return path.toString(); + } + + private static BuildException makeCircularException( String end, Stack stk ) + { + StringBuffer sb = new StringBuffer( "Circular dependency: " ); + sb.append( end ); + String c; + do + { + c = ( String )stk.pop(); + sb.append( " <- " ); + sb.append( c ); + }while ( !c.equals( end ) ); + return new BuildException( new String( sb ) ); + } + + /** + * set the base directory; XML attribute. checks for the directory existing + * and being a directory type + * + * @param baseDir project base directory. + * @throws BuildException if the directory was invalid + */ + public void setBaseDir( File baseDir ) + throws BuildException + { + baseDir = fileUtils.normalize( baseDir.getAbsolutePath() ); + if( !baseDir.exists() ) + throw new BuildException( "Basedir " + baseDir.getAbsolutePath() + " does not exist" ); + if( !baseDir.isDirectory() ) + throw new BuildException( "Basedir " + baseDir.getAbsolutePath() + " is not a directory" ); + this.baseDir = baseDir; + setPropertyInternal( "basedir", this.baseDir.getPath() ); + String msg = "Project base dir set to: " + this.baseDir; + log( msg, MSG_VERBOSE ); + } + + /** + * match basedir attribute in xml + * + * @param baseD project base directory. + * @throws BuildException if the directory was invalid + */ + public void setBasedir( String baseD ) + throws BuildException + { + setBaseDir( new File( baseD ) ); + } + + public void setCoreLoader( ClassLoader coreLoader ) + { + this.coreLoader = coreLoader; + } + + + /** + * set the default target of the project XML attribute name. + * + * @param defaultTarget The new Default value + */ + public void setDefault( String defaultTarget ) + { + this.defaultTarget = defaultTarget; + } + + /** + * set the default target of the project + * + * @param defaultTarget The new DefaultTarget value + * @see #setDefault(String) + * @deprecated, use setDefault + */ + public void setDefaultTarget( String defaultTarget ) + { + this.defaultTarget = defaultTarget; + } + + /** + * set the project description + * + * @param description text + */ + public void setDescription( String description ) + { + this.description = description; + } + + /** + * Calls File.setLastModified(long time) in a Java 1.1 compatible way. + * + * @param file The new FileLastModified value + * @param time The new FileLastModified value + * @exception BuildException Description of Exception + * @deprecated + */ + public void setFileLastModified( File file, long time ) + throws BuildException + { + if( getJavaVersion() == JAVA_1_1 ) + { + log( "Cannot change the modification time of " + file + + " in JDK 1.1", Project.MSG_WARN ); + return; + } + fileUtils.setFileLastModified( file, time ); + log( "Setting modification time for " + file, MSG_VERBOSE ); + } + + /** + * set the ant.java.version property, also tests for unsupported JVM + * versions, prints the verbose log messages + * + * @throws BuildException if this Java version is not supported + */ + public void setJavaVersionProperty() + throws BuildException + { + setPropertyInternal( "ant.java.version", javaVersion ); + + // sanity check + if( javaVersion == JAVA_1_0 ) + { + throw new BuildException( "Ant cannot work on Java 1.0" ); + } + + log( "Detected Java version: " + javaVersion + " in: " + System.getProperty( "java.home" ), MSG_VERBOSE ); + + log( "Detected OS: " + System.getProperty( "os.name" ), MSG_VERBOSE ); + } + + /** + * ant xml property. Set the project name as an attribute of this class, and + * of the property ant.project.name + * + * @param name The new Name value + */ + public void setName( String name ) + { + setUserProperty( "ant.project.name", name ); + this.name = name; + } + + /** + * set a property. An existing property of the same name will not be + * overwritten. + * + * @param name name of property + * @param value new value of the property + * @since 1.5 + */ + public void setNewProperty( String name, String value ) + { + if( null != properties.get( name ) ) + { + log( "Override ignored for property " + name, MSG_VERBOSE ); + return; + } + log( "Setting project property: " + name + " -> " + + value, MSG_DEBUG ); + properties.put( name, value ); + } + + /** + * set a property. Any existing property of the same name is overwritten, + * unless it is a user property. + * + * @param name name of property + * @param value new value of the property + */ + public void setProperty( String name, String value ) + { + // command line properties take precedence + if( null != userProperties.get( name ) ) + { + log( "Override ignored for user property " + name, MSG_VERBOSE ); + return; + } + + if( null != properties.get( name ) ) + { + log( "Overriding previous definition of property " + name, + MSG_VERBOSE ); + } + + log( "Setting project property: " + name + " -> " + + value, MSG_DEBUG ); + properties.put( name, value ); + } + + /** + * turn all the system properties into ant properties. user properties still + * override these values + */ + public void setSystemProperties() + { + Properties systemP = System.getProperties(); + Enumeration e = systemP.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + String value = systemP.get( name ).toString(); + this.setPropertyInternal( name.toString(), value ); + } + } + + /** + * set a user property, which can not be overwritten by set/unset property + * calls + * + * @param name The new UserProperty value + * @param value The new UserProperty value + * @see #setProperty(String,String) + */ + public void setUserProperty( String name, String value ) + { + log( "Setting ro project property: " + name + " -> " + + value, MSG_DEBUG ); + userProperties.put( name, value ); + properties.put( name, value ); + } + + /** + * get the base directory of the project as a file object + * + * @return the base directory. If this is null, then the base dir is not + * valid + */ + public File getBaseDir() + { + if( baseDir == null ) + { + try + { + setBasedir( "." ); + } + catch( BuildException ex ) + { + ex.printStackTrace(); + } + } + return baseDir; + } + + public Vector getBuildListeners() + { + return listeners; + } + + public ClassLoader getCoreLoader() + { + return coreLoader; + } + + /** + * get the current task definition hashtable + * + * @return The DataTypeDefinitions value + */ + public Hashtable getDataTypeDefinitions() + { + return dataClassDefinitions; + } + + /** + * get the default target of the project + * + * @return default target or null + */ + public String getDefaultTarget() + { + return defaultTarget; + } + + /** + * get the project description + * + * @return description or null if no description has been set + */ + public String getDescription() + { + return description; + } + + /** + * @return The Filters value + * @deprecated + */ + public Hashtable getFilters() + { + // we need to build the hashtable dynamically + return globalFilterSet.getFilterHash(); + } + + + public FilterSet getGlobalFilterSet() + { + return globalFilterSet; + } + + /** + * get the project name + * + * @return name string + */ + public String getName() + { + return name; + } + + /** + * get a copy of the property hashtable + * + * @return the hashtable containing all properties, user included + */ + public Hashtable getProperties() + { + Hashtable propertiesCopy = new Hashtable(); + + Enumeration e = properties.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + Object value = properties.get( name ); + propertiesCopy.put( name, value ); + } + + return propertiesCopy; + } + + /** + * query a property. + * + * @param name the name of the property + * @return the property value, or null for no match + */ + public String getProperty( String name ) + { + if( name == null ) + return null; + String property = ( String )properties.get( name ); + return property; + } + + /** + * @param key Description of Parameter + * @return The object with the "id" key. + */ + public Object getReference( String key ) + { + return references.get( key ); + } + + public Hashtable getReferences() + { + return references; + } + + /** + * get the target hashtable + * + * @return hashtable, the contents of which can be cast to Target + */ + public Hashtable getTargets() + { + return targets; + } + + /** + * get the current task definition hashtable + * + * @return The TaskDefinitions value + */ + public Hashtable getTaskDefinitions() + { + return taskClassDefinitions; + } + + /** + * get a copy of the user property hashtable + * + * @return the hashtable user properties only + */ + public Hashtable getUserProperties() + { + Hashtable propertiesCopy = new Hashtable(); + + Enumeration e = userProperties.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + Object value = properties.get( name ); + propertiesCopy.put( name, value ); + } + + return propertiesCopy; + } + + /** + * query a user property. + * + * @param name the name of the property + * @return the property value, or null for no match + */ + public String getUserProperty( String name ) + { + if( name == null ) + return null; + String property = ( String )userProperties.get( name ); + return property; + } + + /** + * Topologically sort a set of Targets. + * + * @param root is the (String) name of the root Target. The sort is created + * in such a way that the sequence of Targets uptil the root target is + * the minimum possible such sequence. + * @param targets is a Hashtable representing a "name to Target" mapping + * @return a Vector of Strings with the names of the targets in sorted + * order. + * @exception BuildException if there is a cyclic dependency among the + * Targets, or if a Target does not exist. + */ + public final Vector topoSort( String root, Hashtable targets ) + throws BuildException + { + Vector ret = new Vector(); + Hashtable state = new Hashtable(); + Stack visiting = new Stack(); + + // We first run a DFS based sort using the root as the starting node. + // This creates the minimum sequence of Targets to the root node. + // We then do a sort on any remaining unVISITED targets. + // This is unnecessary for doing our build, but it catches + // circular dependencies or missing Targets on the entire + // dependency tree, not just on the Targets that depend on the + // build Target. + + tsort( root, targets, state, visiting, ret ); + log( "Build sequence for target `" + root + "' is " + ret, MSG_VERBOSE ); + for( Enumeration en = targets.keys(); en.hasMoreElements(); ) + { + String curTarget = ( String )( en.nextElement() ); + String st = ( String )state.get( curTarget ); + if( st == null ) + { + tsort( curTarget, targets, state, visiting, ret ); + } + else if( st == VISITING ) + { + throw new RuntimeException( "Unexpected node in visiting state: " + curTarget ); + } + } + log( "Complete build sequence is " + ret, MSG_VERBOSE ); + return ret; + } + + public void addBuildListener( BuildListener listener ) + { + listeners.addElement( listener ); + } + + /** + * add a new datatype + * + * @param typeName name of the datatype + * @param typeClass full datatype classname + */ + public void addDataTypeDefinition( String typeName, Class typeClass ) + { + if( null != dataClassDefinitions.get( typeName ) ) + { + log( "Trying to override old definition of datatype " + typeName, + MSG_WARN ); + } + + String msg = " +User datatype: " + typeName + " " + typeClass.getName(); + log( msg, MSG_DEBUG ); + dataClassDefinitions.put( typeName, typeClass ); + } + + /** + * @param token The feature to be added to the Filter attribute + * @param value The feature to be added to the Filter attribute + * @deprecated + */ + public void addFilter( String token, String value ) + { + if( token == null ) + { + return; + } + + globalFilterSet.addFilter( new FilterSet.Filter( token, value ) ); + } + + /** + * @param target is the Target to be added or replaced in the current + * Project. + */ + public void addOrReplaceTarget( Target target ) + { + addOrReplaceTarget( target.getName(), target ); + } + + /** + * @param target is the Target to be added/replaced in the current Project. + * @param targetName is the name to use for the Target + */ + public void addOrReplaceTarget( String targetName, Target target ) + { + String msg = " +Target: " + targetName; + log( msg, MSG_DEBUG ); + target.setProject( this ); + targets.put( targetName, target ); + } + + public void addReference( String name, Object value ) + { + if( null != references.get( name ) ) + { + log( "Overriding previous definition of reference to " + name, + MSG_WARN ); + } + log( "Adding reference: " + name + " -> " + value, MSG_DEBUG ); + references.put( name, value ); + } + + /** + * This call expects to add a new Target. + * + * @param target is the Target to be added to the current Project. + * @see Project#addOrReplaceTarget to replace existing Targets. + */ + public void addTarget( Target target ) + { + String name = target.getName(); + if( targets.get( name ) != null ) + { + throw new BuildException( "Duplicate target: `" + name + "'" ); + } + addOrReplaceTarget( name, target ); + } + + /** + * This call expects to add a new Target. + * + * @param target is the Target to be added to the current Project. + * @param targetName is the name to use for the Target + * @exception BuildException if the Target already exists in the project. + * @see Project#addOrReplaceTarget to replace existing Targets. + */ + public void addTarget( String targetName, Target target ) + throws BuildException + { + if( targets.get( targetName ) != null ) + { + throw new BuildException( "Duplicate target: `" + targetName + "'" ); + } + addOrReplaceTarget( targetName, target ); + } + + /** + * add a new task definition, complain if there is an overwrite attempt + * + * @param taskName name of the task + * @param taskClass full task classname + * @throws BuildException and logs as Project.MSG_ERR for conditions, that + * will cause the task execution to fail. + */ + public void addTaskDefinition( String taskName, Class taskClass ) + throws BuildException + { + Class old = ( Class )taskClassDefinitions.get( taskName ); + if( null != old ) + { + if( old.equals( taskClass ) ) + { + log( "Ignoring override for task " + taskName + + ", it is already defined by the same class.", + MSG_VERBOSE ); + return; + } + else + { + log( "Trying to override old definition of task " + taskName, + MSG_WARN ); + invalidateCreatedTasks( taskName ); + } + } + + String msg = " +User task: " + taskName + " " + taskClass.getName(); + log( msg, MSG_DEBUG ); + checkTaskClass( taskClass ); + taskClassDefinitions.put( taskName, taskClass ); + } + + /** + * Checks a class, whether it is suitable for serving as ant task. + * + * @param taskClass Description of Parameter + * @throws BuildException and logs as Project.MSG_ERR for conditions, that + * will cause the task execution to fail. + */ + public void checkTaskClass( final Class taskClass ) + throws BuildException + { + if( !Modifier.isPublic( taskClass.getModifiers() ) ) + { + final String message = taskClass + " is not public"; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + if( Modifier.isAbstract( taskClass.getModifiers() ) ) + { + final String message = taskClass + " is abstract"; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + try + { + taskClass.getConstructor( null ); + // don't have to check for public, since + // getConstructor finds public constructors only. + } + catch( NoSuchMethodException e ) + { + final String message = "No public default constructor in " + taskClass; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + if( !Task.class.isAssignableFrom( taskClass ) ) + TaskAdapter.checkTaskClass( taskClass, this ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering, + boolean overwrite ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, overwrite ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, + overwrite, preserveLastModified ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering, + boolean overwrite ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, overwrite ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, + overwrite, preserveLastModified ); + } + + /** + * create a new DataType instance + * + * @param typeName name of the datatype + * @return null if the datatype name is unknown + * @throws BuildException when datatype creation goes bad + */ + public Object createDataType( String typeName ) + throws BuildException + { + Class c = ( Class )dataClassDefinitions.get( typeName ); + + if( c == null ) + return null; + + try + { + java.lang.reflect.Constructor ctor = null; + boolean noArg = false; + // DataType can have a "no arg" constructor or take a single + // Project argument. + try + { + ctor = c.getConstructor( new Class[0] ); + noArg = true; + } + catch( NoSuchMethodException nse ) + { + ctor = c.getConstructor( new Class[]{Project.class} ); + noArg = false; + } + + Object o = null; + if( noArg ) + { + o = ctor.newInstance( new Object[0] ); + } + else + { + o = ctor.newInstance( new Object[]{this} ); + } + if( o instanceof ProjectComponent ) + { + ( ( ProjectComponent )o ).setProject( this ); + } + String msg = " +DataType: " + typeName; + log( msg, MSG_DEBUG ); + return o; + } + catch( java.lang.reflect.InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + String msg = "Could not create datatype of type: " + + typeName + " due to " + t; + throw new BuildException( msg, t ); + } + catch( Throwable t ) + { + String msg = "Could not create datatype of type: " + + typeName + " due to " + t; + throw new BuildException( msg, t ); + } + } + + /** + * create a new task instance + * + * @param taskType name of the task + * @return null if the task name is unknown + * @throws BuildException when task creation goes bad + */ + public Task createTask( String taskType ) + throws BuildException + { + Class c = ( Class )taskClassDefinitions.get( taskType ); + + if( c == null ) + return null; + try + { + Object o = c.newInstance(); + Task task = null; + if( o instanceof Task ) + { + task = ( Task )o; + } + else + { + // "Generic" Bean - use the setter pattern + // and an Adapter + TaskAdapter taskA = new TaskAdapter(); + taskA.setProxy( o ); + task = taskA; + } + task.setProject( this ); + task.setTaskType( taskType ); + + // set default value, can be changed by the user + task.setTaskName( taskType ); + + String msg = " +Task: " + taskType; + log( msg, MSG_DEBUG ); + addCreatedTask( taskType, task ); + return task; + } + catch( Throwable t ) + { + String msg = "Could not create task of type: " + + taskType + " due to " + t; + throw new BuildException( msg, t ); + } + } + + public void demuxOutput( String line, boolean isError ) + { + Task task = ( Task )threadTasks.get( Thread.currentThread() ); + if( task == null ) + { + fireMessageLogged( this, line, isError ? MSG_ERR : MSG_INFO ); + } + else + { + if( isError ) + { + task.handleErrorOutput( line ); + } + else + { + task.handleOutput( line ); + } + } + } + + /** + * execute the targets and any targets it depends on + * + * @param targetName the target to execute + * @throws BuildException if the build failed + */ + public void executeTarget( String targetName ) + throws BuildException + { + + // sanity check ourselves, if we've been asked to build nothing + // then we should complain + + if( targetName == null ) + { + String msg = "No target specified"; + throw new BuildException( msg ); + } + + // Sort the dependency tree, and run everything from the + // beginning until we hit our targetName. + // Sorting checks if all the targets (and dependencies) + // exist, and if there is any cycle in the dependency + // graph. + Vector sortedTargets = topoSort( targetName, targets ); + + int curidx = 0; + Target curtarget; + + do + { + curtarget = ( Target )sortedTargets.elementAt( curidx++ ); + curtarget.performTasks(); + }while ( !curtarget.getName().equals( targetName ) ); + } + + /** + * execute the sequence of targets, and the targets they depend on + * + * @param targetNames Description of Parameter + * @throws BuildException if the build failed + */ + public void executeTargets( Vector targetNames ) + throws BuildException + { + Throwable error = null; + + for( int i = 0; i < targetNames.size(); i++ ) + { + executeTarget( ( String )targetNames.elementAt( i ) ); + } + } + + /** + * Initialise the project. This involves setting the default task + * definitions and loading the system properties. + * + * @exception BuildException Description of Exception + */ + public void init() + throws BuildException + { + setJavaVersionProperty(); + + String defs = "/org/apache/tools/ant/taskdefs/defaults.properties"; + + try + { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream( defs ); + if( in == null ) + { + throw new BuildException( "Can't load default task list" ); + } + props.load( in ); + in.close(); + + Enumeration enum = props.propertyNames(); + while( enum.hasMoreElements() ) + { + String key = ( String )enum.nextElement(); + String value = props.getProperty( key ); + try + { + Class taskClass = Class.forName( value ); + addTaskDefinition( key, taskClass ); + } + catch( NoClassDefFoundError ncdfe ) + { + // ignore... + } + catch( ClassNotFoundException cnfe ) + { + // ignore... + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Can't load default task list" ); + } + + String dataDefs = "/org/apache/tools/ant/types/defaults.properties"; + + try + { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream( dataDefs ); + if( in == null ) + { + throw new BuildException( "Can't load default datatype list" ); + } + props.load( in ); + in.close(); + + Enumeration enum = props.propertyNames(); + while( enum.hasMoreElements() ) + { + String key = ( String )enum.nextElement(); + String value = props.getProperty( key ); + try + { + Class dataClass = Class.forName( value ); + addDataTypeDefinition( key, dataClass ); + } + catch( NoClassDefFoundError ncdfe ) + { + // ignore... + } + catch( ClassNotFoundException cnfe ) + { + // ignore... + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Can't load default datatype list" ); + } + + setSystemProperties(); + } + + /** + * Output a message to the log with the default log level of MSG_INFO + * + * @param msg text to log + */ + + public void log( String msg ) + { + log( msg, MSG_INFO ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of project + * + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( String msg, int msgLevel ) + { + fireMessageLogged( this, msg, msgLevel ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of a task + * + * @param task task to use in the log + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( Task task, String msg, int msgLevel ) + { + fireMessageLogged( task, msg, msgLevel ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of a target + * + * @param target target to use in the log + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( Target target, String msg, int msgLevel ) + { + fireMessageLogged( target, msg, msgLevel ); + } + + public void removeBuildListener( BuildListener listener ) + { + listeners.removeElement( listener ); + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public String replaceProperties( String value ) + throws BuildException + { + return ProjectHelper.replaceProperties( this, value ); + } + + /** + * Return the canonical form of fileName as an absolute path.

          + * + * If fileName is a relative file name, resolve it relative to rootDir.

          + * + * @param fileName Description of Parameter + * @param rootDir Description of Parameter + * @return Description of the Returned Value + * @deprecated + */ + public File resolveFile( String fileName, File rootDir ) + { + return fileUtils.resolveFile( rootDir, fileName ); + } + + public File resolveFile( String fileName ) + { + return fileUtils.resolveFile( baseDir, fileName ); + } + + /** + * send build finished event to the listeners + * + * @param exception exception which indicates failure if not null + */ + protected void fireBuildFinished( Throwable exception ) + { + BuildEvent event = new BuildEvent( this ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.buildFinished( event ); + } + } + + /** + * send build started event to the listeners + */ + protected void fireBuildStarted() + { + BuildEvent event = new BuildEvent( this ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.buildStarted( event ); + } + } + + protected void fireMessageLogged( Project project, String message, int priority ) + { + BuildEvent event = new BuildEvent( project ); + fireMessageLoggedEvent( event, message, priority ); + } + + protected void fireMessageLogged( Target target, String message, int priority ) + { + BuildEvent event = new BuildEvent( target ); + fireMessageLoggedEvent( event, message, priority ); + } + + protected void fireMessageLogged( Task task, String message, int priority ) + { + BuildEvent event = new BuildEvent( task ); + fireMessageLoggedEvent( event, message, priority ); + } + + /** + * send build finished event to the listeners + * + * @param exception exception which indicates failure if not null + * @param target Description of Parameter + */ + protected void fireTargetFinished( Target target, Throwable exception ) + { + BuildEvent event = new BuildEvent( target ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.targetFinished( event ); + } + } + + + /** + * send target started event to the listeners + * + * @param target Description of Parameter + */ + protected void fireTargetStarted( Target target ) + { + BuildEvent event = new BuildEvent( target ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.targetStarted( event ); + } + } + + protected void fireTaskFinished( Task task, Throwable exception ) + { + threadTasks.remove( Thread.currentThread() ); + System.out.flush(); + System.err.flush(); + BuildEvent event = new BuildEvent( task ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.taskFinished( event ); + } + } + + protected void fireTaskStarted( Task task ) + { + // register this as the current task on the current thread. + threadTasks.put( Thread.currentThread(), task ); + BuildEvent event = new BuildEvent( task ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.taskStarted( event ); + } + } + + /** + * Allows Project and subclasses to set a property unless its already + * defined as a user property. There are a few cases internally to Project + * that need to do this currently. + * + * @param name The new PropertyInternal value + * @param value The new PropertyInternal value + */ + private void setPropertyInternal( String name, String value ) + { + if( null != userProperties.get( name ) ) + { + return; + } + properties.put( name, value ); + } + + // one step in a recursive DFS traversal of the Target dependency tree. + // - The Hashtable "state" contains the state (VISITED or VISITING or null) + // of all the target names. + // - The Stack "visiting" contains a stack of target names that are + // currently on the DFS stack. (NB: the target names in "visiting" are + // exactly the target names in "state" that are in the VISITING state.) + // 1. Set the current target to the VISITING state, and push it onto + // the "visiting" stack. + // 2. Throw a BuildException if any child of the current node is + // in the VISITING state (implies there is a cycle.) It uses the + // "visiting" Stack to construct the cycle. + // 3. If any children have not been VISITED, tsort() the child. + // 4. Add the current target to the Vector "ret" after the children + // have been visited. Move the current target to the VISITED state. + // "ret" now contains the sorted sequence of Targets upto the current + // Target. + + private final void tsort( String root, Hashtable targets, + Hashtable state, Stack visiting, + Vector ret ) + throws BuildException + { + state.put( root, VISITING ); + visiting.push( root ); + + Target target = ( Target )( targets.get( root ) ); + + // Make sure we exist + if( target == null ) + { + StringBuffer sb = new StringBuffer( "Target `" ); + sb.append( root ); + sb.append( "' does not exist in this project. " ); + visiting.pop(); + if( !visiting.empty() ) + { + String parent = ( String )visiting.peek(); + sb.append( "It is used from target `" ); + sb.append( parent ); + sb.append( "'." ); + } + + throw new BuildException( new String( sb ) ); + } + + for( Enumeration en = target.getDependencies(); en.hasMoreElements(); ) + { + String cur = ( String )en.nextElement(); + String m = ( String )state.get( cur ); + if( m == null ) + { + // Not been visited + tsort( cur, targets, state, visiting, ret ); + } + else if( m == VISITING ) + { + // Currently visiting this node, so have a cycle + throw makeCircularException( cur, visiting ); + } + } + + String p = ( String )visiting.pop(); + if( root != p ) + { + throw new RuntimeException( "Unexpected internal error: expected to pop " + root + " but got " + p ); + } + state.put( root, VISITED ); + ret.addElement( target ); + } + + /** + * Keep a record of all tasks that have been created so that they can be + * invalidated if a taskdef overrides the definition. + * + * @param type The feature to be added to the CreatedTask attribute + * @param task The feature to be added to the CreatedTask attribute + */ + private void addCreatedTask( String type, Task task ) + { + synchronized( createdTasks ) + { + Vector v = ( Vector )createdTasks.get( type ); + if( v == null ) + { + v = new Vector(); + createdTasks.put( type, v ); + } + v.addElement( task ); + } + } + + private void fireMessageLoggedEvent( BuildEvent event, String message, int priority ) + { + event.setMessage( message, priority ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.messageLogged( event ); + } + } + + /** + * Mark tasks as invalid which no longer are of the correct type for a given + * taskname. + * + * @param type Description of Parameter + */ + private void invalidateCreatedTasks( String type ) + { + synchronized( createdTasks ) + { + Vector v = ( Vector )createdTasks.get( type ); + if( v != null ) + { + Enumeration enum = v.elements(); + while( enum.hasMoreElements() ) + { + Task t = ( Task )enum.nextElement(); + t.markInvalid(); + } + v.removeAllElements(); + createdTasks.remove( type ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectComponent.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectComponent.java new file mode 100644 index 000000000..c7ddf7f96 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectComponent.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.AbstractTask; + +/** + * Base class for components of a project, including tasks and data types. + * Provides common facilities. + * + * @author Conor MacNeill + */ + +public abstract class ProjectComponent + extends AbstractTask +{ + protected Project project = null; + + /** + * Sets the project object of this component. This method is used by project + * when a component is added to it so that the component has access to the + * functions of the project. It should not be used for any other purpose. + * + * @param project Project in whose scope this component belongs. + */ + public void setProject( Project project ) + { + this.project = project; + } + + /** + * Get the Project to which this component belongs + * + * @return the components's project. + */ + public Project getProject() + { + return project; + } + + /** + * Log a message with the default (INFO) priority. + * + * @param msg Description of Parameter + */ + public void log( String msg ) + { + log( msg, Project.MSG_INFO ); + } + + /** + * Log a mesage with the give priority. + * + * @param msgLevel the message priority at which this message is to be + * logged. + * @param msg Description of Parameter + */ + public void log( String msg, int msgLevel ) + { + if( project != null ) + { + project.log( msg, msgLevel ); + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectHelper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectHelper.java new file mode 100644 index 000000000..896a0d8f8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectHelper.java @@ -0,0 +1,1061 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Vector; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.AttributeList; +import org.xml.sax.DocumentHandler; +import org.xml.sax.HandlerBase; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * Configures a Project (complete with Targets and Tasks) based on a XML build + * file. + * + * @author duncan@x180.com + */ + +public class ProjectHelper +{ + + private static SAXParserFactory parserFactory = null; + private File buildFile; + private File buildFileParent; + private Locator locator; + + private org.xml.sax.Parser parser; + private Project project; + + /** + * Constructs a new Ant parser for the specified XML file. + * + * @param project Description of Parameter + * @param buildFile Description of Parameter + */ + private ProjectHelper( Project project, File buildFile ) + { + this.project = project; + this.buildFile = new File( buildFile.getAbsolutePath() ); + buildFileParent = new File( this.buildFile.getParent() ); + } + + /** + * Adds the content of #PCDATA sections to an element. + * + * @param project The feature to be added to the Text attribute + * @param target The feature to be added to the Text attribute + * @param buf The feature to be added to the Text attribute + * @param start The feature to be added to the Text attribute + * @param end The feature to be added to the Text attribute + * @exception BuildException Description of Exception + */ + public static void addText( Project project, Object target, char[] buf, int start, int end ) + throws BuildException + { + addText( project, target, new String( buf, start, end ) ); + } + + /** + * Adds the content of #PCDATA sections to an element. + * + * @param project The feature to be added to the Text attribute + * @param target The feature to be added to the Text attribute + * @param text The feature to be added to the Text attribute + * @exception BuildException Description of Exception + */ + public static void addText( Project project, Object target, String text ) + throws BuildException + { + + if( text == null || text.trim().length() == 0 ) + { + return; + } + + if( target instanceof TaskAdapter ) + target = ( ( TaskAdapter )target ).getProxy(); + + IntrospectionHelper.getHelper( target.getClass() ).addText( project, target, text ); + } + + public static void configure( Object target, AttributeList attrs, + Project project ) + throws BuildException + { + if( target instanceof TaskAdapter ) + target = ( ( TaskAdapter )target ).getProxy(); + + IntrospectionHelper ih = + IntrospectionHelper.getHelper( target.getClass() ); + + project.addBuildListener( ih ); + + for( int i = 0; i < attrs.getLength(); i++ ) + { + // reflect these into the target + String value = replaceProperties( project, attrs.getValue( i ), + project.getProperties() ); + try + { + ih.setAttribute( project, target, + attrs.getName( i ).toLowerCase( Locale.US ), value ); + + } + catch( BuildException be ) + { + // id attribute must be set externally + if( !attrs.getName( i ).equals( "id" ) ) + { + throw be; + } + } + } + } + + /** + * Configures the Project with the contents of the specified XML file. + * + * @param project Description of Parameter + * @param buildFile Description of Parameter + * @exception BuildException Description of Exception + */ + public static void configureProject( Project project, File buildFile ) + throws BuildException + { + new ProjectHelper( project, buildFile ).parse(); + } + + /** + * This method will parse a string containing ${value} style property values + * into two lists. The first list is a collection of text fragments, while + * the other is a set of string property names null entries in the first + * list indicate a property reference from the second list. + * + * @param value Description of Parameter + * @param fragments Description of Parameter + * @param propertyRefs Description of Parameter + * @exception BuildException Description of Exception + */ + public static void parsePropertyString( String value, Vector fragments, Vector propertyRefs ) + throws BuildException + { + int prev = 0; + int pos; + while( ( pos = value.indexOf( "$", prev ) ) >= 0 ) + { + if( pos > 0 ) + { + fragments.addElement( value.substring( prev, pos ) ); + } + + if( pos == ( value.length() - 1 ) ) + { + fragments.addElement( "$" ); + prev = pos + 1; + } + else if( value.charAt( pos + 1 ) != '{' ) + { + fragments.addElement( value.substring( pos + 1, pos + 2 ) ); + prev = pos + 2; + } + else + { + int endName = value.indexOf( '}', pos ); + if( endName < 0 ) + { + throw new BuildException( "Syntax error in property: " + + value ); + } + String propertyName = value.substring( pos + 2, endName ); + fragments.addElement( null ); + propertyRefs.addElement( propertyName ); + prev = endName + 1; + } + } + + if( prev < value.length() ) + { + fragments.addElement( value.substring( prev ) ); + } + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @param project Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @since 1.5 + */ + public static String replaceProperties( Project project, String value ) + throws BuildException + { + return replaceProperties( project, value, project.getProperties() ); + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @param project Description of Parameter + * @param keys Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public static String replaceProperties( Project project, String value, Hashtable keys ) + throws BuildException + { + if( value == null ) + { + return null; + } + + Vector fragments = new Vector(); + Vector propertyRefs = new Vector(); + parsePropertyString( value, fragments, propertyRefs ); + + StringBuffer sb = new StringBuffer(); + Enumeration i = fragments.elements(); + Enumeration j = propertyRefs.elements(); + while( i.hasMoreElements() ) + { + String fragment = ( String )i.nextElement(); + if( fragment == null ) + { + String propertyName = ( String )j.nextElement(); + if( !keys.containsKey( propertyName ) ) + { + project.log( "Property ${" + propertyName + "} has not been set", Project.MSG_VERBOSE ); + } + fragment = ( keys.containsKey( propertyName ) ) ? ( String )keys.get( propertyName ) + : "${" + propertyName + "}"; + } + sb.append( fragment ); + } + + return sb.toString(); + } + + /** + * Stores a configured child element into its parent object + * + * @param project Description of Parameter + * @param parent Description of Parameter + * @param child Description of Parameter + * @param tag Description of Parameter + */ + public static void storeChild( Project project, Object parent, Object child, String tag ) + { + IntrospectionHelper ih = IntrospectionHelper.getHelper( parent.getClass() ); + ih.storeElement( project, parent, child, tag ); + } + + private static SAXParserFactory getParserFactory() + { + if( parserFactory == null ) + { + parserFactory = SAXParserFactory.newInstance(); + } + + return parserFactory; + } + + /** + * Scan AttributeList for the id attribute and maybe add a reference to + * project.

          + * + * Moved out of {@link #configure configure} to make it happen at parser + * time.

          + * + * @param target Description of Parameter + * @param attr Description of Parameter + */ + private void configureId( Object target, AttributeList attr ) + { + String id = attr.getValue( "id" ); + if( id != null ) + { + project.addReference( id, target ); + } + } + + /** + * Parses the project file. + * + * @exception BuildException Description of Exception + */ + private void parse() + throws BuildException + { + FileInputStream inputStream = null; + InputSource inputSource = null; + + try + { + SAXParser saxParser = getParserFactory().newSAXParser(); + parser = saxParser.getParser(); + + String uri = "file:" + buildFile.getAbsolutePath().replace( '\\', '/' ); + for( int index = uri.indexOf( '#' ); index != -1; index = uri.indexOf( '#' ) ) + { + uri = uri.substring( 0, index ) + "%23" + uri.substring( index + 1 ); + } + + inputStream = new FileInputStream( buildFile ); + inputSource = new InputSource( inputStream ); + inputSource.setSystemId( uri ); + project.log( "parsing buildfile " + buildFile + " with URI = " + uri, Project.MSG_VERBOSE ); + saxParser.parse( inputSource, new RootHandler() ); + } + catch( ParserConfigurationException exc ) + { + throw new BuildException( "Parser has not been configured correctly", exc ); + } + catch( SAXParseException exc ) + { + Location location = + new Location( buildFile.toString(), exc.getLineNumber(), exc.getColumnNumber() ); + + Throwable t = exc.getException(); + if( t instanceof BuildException ) + { + BuildException be = ( BuildException )t; + if( be.getLocation() == Location.UNKNOWN_LOCATION ) + { + be.setLocation( location ); + } + throw be; + } + + throw new BuildException( exc.getMessage(), t, location ); + } + catch( SAXException exc ) + { + Throwable t = exc.getException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( exc.getMessage(), t ); + } + catch( FileNotFoundException exc ) + { + throw new BuildException( exc ); + } + catch( IOException exc ) + { + throw new BuildException( "Error reading project file", exc ); + } + finally + { + if( inputStream != null ) + { + try + { + inputStream.close(); + } + catch( IOException ioe ) + { + // ignore this + } + } + } + } + + /** + * The common superclass for all sax event handlers in Ant. Basically throws + * an exception in each method, so subclasses should override what they can + * handle. Each type of xml element (task, target, etc) in ant will have its + * own subclass of AbstractHandler. In the constructor, this class takes + * over the handling of sax events from the parent handler, and returns + * control back to the parent in the endElement method. + * + * @author RT + */ + private class AbstractHandler extends HandlerBase + { + protected DocumentHandler parentHandler; + + public AbstractHandler( DocumentHandler parentHandler ) + { + this.parentHandler = parentHandler; + + // Start handling SAX events + parser.setDocumentHandler( this ); + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + String s = new String( buf, start, end ).trim(); + + if( s.length() > 0 ) + { + throw new SAXParseException( "Unexpected text \"" + s + "\"", locator ); + } + } + + public void endElement( String name ) + throws SAXException + { + + finished(); + // Let parent resume handling SAX events + parser.setDocumentHandler( parentHandler ); + } + + public void startElement( String tag, AttributeList attrs ) + throws SAXParseException + { + throw new SAXParseException( "Unexpected element \"" + tag + "\"", locator ); + } + + /** + * Called when this element and all elements nested into it have been + * handled. + */ + protected void finished() { } + } + + /** + * Handler for all data types at global level. + * + * @author RT + */ + private class DataTypeHandler extends AbstractHandler + { + private RuntimeConfigurable wrapper = null; + private Object element; + private Target target; + + public DataTypeHandler( DocumentHandler parentHandler ) + { + this( parentHandler, null ); + } + + public DataTypeHandler( DocumentHandler parentHandler, Target target ) + { + super( parentHandler ); + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + try + { + addText( project, element, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void init( String propType, AttributeList attrs ) + throws SAXParseException + { + try + { + element = project.createDataType( propType ); + if( element == null ) + { + throw new BuildException( "Unknown data type " + propType ); + } + + if( target != null ) + { + wrapper = new RuntimeConfigurable( element, propType ); + wrapper.setAttributes( attrs ); + target.addDataType( wrapper ); + } + else + { + configure( element, attrs, project ); + configureId( element, attrs ); + } + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + new NestedElementHandler( this, element, wrapper, target ).init( name, attrs ); + } + } + + /** + * Handler for all nested properties. + * + * @author RT + */ + private class NestedElementHandler extends AbstractHandler + { + private RuntimeConfigurable childWrapper = null; + private Object child; + private Object parent; + private RuntimeConfigurable parentWrapper; + private Target target; + + public NestedElementHandler( DocumentHandler parentHandler, + Object parent, + RuntimeConfigurable parentWrapper, + Target target ) + { + super( parentHandler ); + + if( parent instanceof TaskAdapter ) + { + this.parent = ( ( TaskAdapter )parent ).getProxy(); + } + else + { + this.parent = parent; + } + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + if( parentWrapper == null ) + { + try + { + addText( project, child, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + else + { + childWrapper.addText( buf, start, end ); + } + } + + public void init( String propType, AttributeList attrs ) + throws SAXParseException + { + Class parentClass = parent.getClass(); + IntrospectionHelper ih = + IntrospectionHelper.getHelper( parentClass ); + + try + { + String elementName = propType.toLowerCase( Locale.US ); + if( parent instanceof UnknownElement ) + { + UnknownElement uc = new UnknownElement( elementName ); + uc.setProject( project ); + ( ( UnknownElement )parent ).addChild( uc ); + child = uc; + } + else + { + child = ih.createElement( project, parent, elementName ); + } + + configureId( child, attrs ); + + if( parentWrapper != null ) + { + childWrapper = new RuntimeConfigurable( child, propType ); + childWrapper.setAttributes( attrs ); + parentWrapper.addChild( childWrapper ); + } + else + { + configure( child, attrs, project ); + ih.storeElement( project, parent, child, elementName ); + } + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( child instanceof TaskContainer ) + { + // taskcontainer nested element can contain other tasks - no other + // nested elements possible + new TaskHandler( this, ( TaskContainer )child, childWrapper, target ).init( name, attrs ); + } + else + { + new NestedElementHandler( this, child, childWrapper, target ).init( name, attrs ); + } + } + } + + /** + * Handler for the top level "project" element. + * + * @author RT + */ + private class ProjectHandler extends AbstractHandler + { + public ProjectHandler( DocumentHandler parentHandler ) + { + super( parentHandler ); + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + String def = null; + String name = null; + String id = null; + String baseDir = null; + + for( int i = 0; i < attrs.getLength(); i++ ) + { + String key = attrs.getName( i ); + String value = attrs.getValue( i ); + + if( key.equals( "default" ) ) + { + def = value; + } + else if( key.equals( "name" ) ) + { + name = value; + } + else if( key.equals( "id" ) ) + { + id = value; + } + else if( key.equals( "basedir" ) ) + { + baseDir = value; + } + else + { + throw new SAXParseException( "Unexpected attribute \"" + attrs.getName( i ) + "\"", locator ); + } + } + + if( def == null ) + { + throw new SAXParseException( "The default attribute of project is required", + locator ); + } + + project.setDefaultTarget( def ); + + if( name != null ) + { + project.setName( name ); + project.addReference( name, project ); + } + + if( id != null ) + project.addReference( id, project ); + + if( project.getProperty( "basedir" ) != null ) + { + project.setBasedir( project.getProperty( "basedir" ) ); + } + else + { + if( baseDir == null ) + { + project.setBasedir( buildFileParent.getAbsolutePath() ); + } + else + { + // check whether the user has specified an absolute path + if( ( new File( baseDir ) ).isAbsolute() ) + { + project.setBasedir( baseDir ); + } + else + { + project.setBaseDir( project.resolveFile( baseDir, buildFileParent ) ); + } + } + } + + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( name.equals( "taskdef" ) ) + { + handleTaskdef( name, attrs ); + } + else if( name.equals( "typedef" ) ) + { + handleTypedef( name, attrs ); + } + else if( name.equals( "property" ) ) + { + handleProperty( name, attrs ); + } + else if( name.equals( "target" ) ) + { + handleTarget( name, attrs ); + } + else if( project.getDataTypeDefinitions().get( name ) != null ) + { + handleDataType( name, attrs ); + } + else + { + throw new SAXParseException( "Unexpected element \"" + name + "\"", locator ); + } + } + + private void handleDataType( String name, AttributeList attrs ) + throws SAXParseException + { + new DataTypeHandler( this ).init( name, attrs ); + } + + private void handleProperty( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + private void handleTarget( String tag, AttributeList attrs ) + throws SAXParseException + { + new TargetHandler( this ).init( tag, attrs ); + } + + private void handleTaskdef( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + private void handleTypedef( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + } + + /** + * Handler for the root element. It's only child must be the "project" + * element. + * + * @author RT + */ + private class RootHandler extends HandlerBase + { + + public void setDocumentLocator( Locator locator ) + { + ProjectHelper.this.locator = locator; + } + + /** + * resolve file: URIs as relative to the build file. + * + * @param publicId Description of Parameter + * @param systemId Description of Parameter + * @return Description of the Returned Value + */ + public InputSource resolveEntity( String publicId, + String systemId ) + { + + project.log( "resolving systemId: " + systemId, Project.MSG_VERBOSE ); + + if( systemId.startsWith( "file:" ) ) + { + String path = systemId.substring( 5 ); + int index = path.indexOf( "file:" ); + + // we only have to handle these for backward compatibility + // since they are in the FAQ. + while( index != -1 ) + { + path = path.substring( 0, index ) + path.substring( index + 5 ); + index = path.indexOf( "file:" ); + } + + String entitySystemId = path; + index = path.indexOf( "%23" ); + // convert these to # + while( index != -1 ) + { + path = path.substring( 0, index ) + "#" + path.substring( index + 3 ); + index = path.indexOf( "%23" ); + } + + File file = new File( path ); + if( !file.isAbsolute() ) + { + file = new File( buildFileParent, path ); + } + + try + { + InputSource inputSource = new InputSource( new FileInputStream( file ) ); + inputSource.setSystemId( "file:" + entitySystemId ); + return inputSource; + } + catch( FileNotFoundException fne ) + { + project.log( file.getAbsolutePath() + " could not be found", + Project.MSG_WARN ); + } + } + // use default if not file or file not found + return null; + } + + public void startElement( String tag, AttributeList attrs ) + throws SAXParseException + { + if( tag.equals( "project" ) ) + { + new ProjectHandler( this ).init( tag, attrs ); + } + else + { + throw new SAXParseException( "Config file is not of expected XML type", locator ); + } + } + } + + /** + * Handler for "target" elements. + * + * @author RT + */ + private class TargetHandler extends AbstractHandler + { + private Target target; + + public TargetHandler( DocumentHandler parentHandler ) + { + super( parentHandler ); + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + String name = null; + String depends = ""; + String ifCond = null; + String unlessCond = null; + String id = null; + String description = null; + + for( int i = 0; i < attrs.getLength(); i++ ) + { + String key = attrs.getName( i ); + String value = attrs.getValue( i ); + + if( key.equals( "name" ) ) + { + name = value; + } + else if( key.equals( "depends" ) ) + { + depends = value; + } + else if( key.equals( "if" ) ) + { + ifCond = value; + } + else if( key.equals( "unless" ) ) + { + unlessCond = value; + } + else if( key.equals( "id" ) ) + { + id = value; + } + else if( key.equals( "description" ) ) + { + description = value; + } + else + { + throw new SAXParseException( "Unexpected attribute \"" + key + "\"", locator ); + } + } + + if( name == null ) + { + throw new SAXParseException( "target element appears without a name attribute", locator ); + } + + target = new Target(); + target.setName( name ); + target.setIf( ifCond ); + target.setUnless( unlessCond ); + target.setDescription( description ); + project.addTarget( name, target ); + + if( id != null && !id.equals( "" ) ) + project.addReference( id, target ); + + // take care of dependencies + + if( depends.length() > 0 ) + { + target.setDepends( depends ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( project.getDataTypeDefinitions().get( name ) != null ) + { + new DataTypeHandler( this, target ).init( name, attrs ); + } + else + { + new TaskHandler( this, target, null, target ).init( name, attrs ); + } + } + } + + /** + * Handler for all task elements. + * + * @author RT + */ + private class TaskHandler extends AbstractHandler + { + private RuntimeConfigurable wrapper = null; + private TaskContainer container; + private RuntimeConfigurable parentWrapper; + private Target target; + private Task task; + + public TaskHandler( DocumentHandler parentHandler, TaskContainer container, RuntimeConfigurable parentWrapper, Target target ) + { + super( parentHandler ); + this.container = container; + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + if( wrapper == null ) + { + try + { + addText( project, task, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + else + { + wrapper.addText( buf, start, end ); + } + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + try + { + task = project.createTask( tag ); + } + catch( BuildException e ) + { + // swallow here, will be thrown again in + // UnknownElement.maybeConfigure if the problem persists. + } + + if( task == null ) + { + task = new UnknownElement( tag ); + task.setProject( project ); + task.setTaskType( tag ); + task.setTaskName( tag ); + } + + task.setLocation( new Location( buildFile.toString(), locator.getLineNumber(), locator.getColumnNumber() ) ); + configureId( task, attrs ); + + // Top level tasks don't have associated targets + if( target != null ) + { + task.setOwningTarget( target ); + container.addTask( task ); + task.init(); + wrapper = task.getRuntimeConfigurableWrapper(); + wrapper.setAttributes( attrs ); + if( parentWrapper != null ) + { + parentWrapper.addChild( wrapper ); + } + } + else + { + task.init(); + configure( task, attrs, project ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( task instanceof TaskContainer ) + { + // task can contain other tasks - no other nested elements possible + new TaskHandler( this, ( TaskContainer )task, wrapper, target ).init( name, attrs ); + } + else + { + new NestedElementHandler( this, task, wrapper, target ).init( name, attrs ); + } + } + + protected void finished() + { + if( task != null && target == null ) + { + task.execute(); + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/RuntimeConfigurable.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/RuntimeConfigurable.java new file mode 100644 index 000000000..4acd55108 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/RuntimeConfigurable.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Vector; +import org.xml.sax.AttributeList; +import org.xml.sax.helpers.AttributeListImpl; + +/** + * Wrapper class that holds the attributes of a Task (or elements nested below + * that level) and takes care of configuring that element at runtime. + * + * @author Stefan Bodewig + */ +public class RuntimeConfigurable +{ + + private String elementTag = null; + private Vector children = new Vector(); + private Object wrappedObject = null; + private StringBuffer characters = new StringBuffer(); + private AttributeList attributes; + + /** + * @param proxy The element to wrap. + * @param elementTag Description of Parameter + */ + public RuntimeConfigurable( Object proxy, String elementTag ) + { + wrappedObject = proxy; + this.elementTag = elementTag; + } + + /** + * Set's the attributes for the wrapped element. + * + * @param attributes The new Attributes value + */ + public void setAttributes( AttributeList attributes ) + { + this.attributes = new AttributeListImpl( attributes ); + } + + /** + * Returns the AttributeList of the wrapped element. + * + * @return The Attributes value + */ + public AttributeList getAttributes() + { + return attributes; + } + + public String getElementTag() + { + return elementTag; + } + + /** + * Adds child elements to the wrapped element. + * + * @param child The feature to be added to the Child attribute + */ + public void addChild( RuntimeConfigurable child ) + { + children.addElement( child ); + } + + /** + * Add characters from #PCDATA areas to the wrapped element. + * + * @param data The feature to be added to the Text attribute + */ + public void addText( String data ) + { + characters.append( data ); + } + + /** + * Add characters from #PCDATA areas to the wrapped element. + * + * @param buf The feature to be added to the Text attribute + * @param start The feature to be added to the Text attribute + * @param end The feature to be added to the Text attribute + */ + public void addText( char[] buf, int start, int end ) + { + addText( new String( buf, start, end ) ); + } + + + /** + * Configure the wrapped element and all children. + * + * @param p Description of Parameter + * @exception BuildException Description of Exception + */ + public void maybeConfigure( Project p ) + throws BuildException + { + String id = null; + + if( attributes != null ) + { + ProjectHelper.configure( wrappedObject, attributes, p ); + id = attributes.getValue( "id" ); + attributes = null; + } + if( characters.length() != 0 ) + { + ProjectHelper.addText( p, wrappedObject, characters.toString() ); + characters.setLength( 0 ); + } + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + RuntimeConfigurable child = ( RuntimeConfigurable )enum.nextElement(); + if( child.wrappedObject instanceof Task ) + { + Task childTask = ( Task )child.wrappedObject; + childTask.setRuntimeConfigurableWrapper( child ); + childTask.maybeConfigure(); + } + else + { + child.maybeConfigure( p ); + } + ProjectHelper.storeChild( p, wrappedObject, child.wrappedObject, child.getElementTag().toLowerCase( Locale.US ) ); + } + + if( id != null ) + { + p.addReference( id, wrappedObject ); + } + } + + void setProxy( Object proxy ) + { + wrappedObject = proxy; + } + + /** + * Returns the child with index index. + * + * @param index Description of Parameter + * @return The Child value + */ + RuntimeConfigurable getChild( int index ) + { + return ( RuntimeConfigurable )children.elementAt( index ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Target.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Target.java new file mode 100644 index 000000000..78cb4f980 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Target.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * This class implements a target object with required parameters. + * + * @author James Davidson duncan@x180.com + */ + +public class Target implements TaskContainer +{ + private String ifCondition = ""; + private String unlessCondition = ""; + private Vector dependencies = new Vector( 2 ); + private Vector children = new Vector( 5 ); + private String description = null; + + private String name; + private Project project; + + public void setDepends( String depS ) + { + if( depS.length() > 0 ) + { + StringTokenizer tok = + new StringTokenizer( depS, ",", true ); + while( tok.hasMoreTokens() ) + { + String token = tok.nextToken().trim(); + + //Make sure the dependency is not empty string + if( token.equals( "" ) || token.equals( "," ) ) + { + throw new BuildException( "Syntax Error: Depend attribute " + + "for target \"" + getName() + + "\" has an empty string for dependency." ); + } + + addDependency( token ); + + //Make sure that depends attribute does not + //end in a , + if( tok.hasMoreTokens() ) + { + token = tok.nextToken(); + if( !tok.hasMoreTokens() || !token.equals( "," ) ) + { + throw new BuildException( "Syntax Error: Depend attribute " + + "for target \"" + getName() + + "\" ends with a , character" ); + } + } + } + } + } + + public void setDescription( String description ) + { + this.description = description; + } + + public void setIf( String property ) + { + this.ifCondition = ( property == null ) ? "" : property; + } + + public void setName( String name ) + { + this.name = name; + } + + public void setProject( Project project ) + { + this.project = project; + } + + public void setUnless( String property ) + { + this.unlessCondition = ( property == null ) ? "" : property; + } + + public Enumeration getDependencies() + { + return dependencies.elements(); + } + + public String getDescription() + { + return description; + } + + public String getName() + { + return name; + } + + public Project getProject() + { + return project; + } + + /** + * Get the current set of tasks to be executed by this target. + * + * @return The current set of tasks. + */ + public Task[] getTasks() + { + Vector tasks = new Vector( children.size() ); + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + Object o = enum.nextElement(); + if( o instanceof Task ) + { + tasks.addElement( o ); + } + } + + Task[] retval = new Task[tasks.size()]; + tasks.copyInto( retval ); + return retval; + } + + public final void performTasks() + { + try + { + project.fireTargetStarted( this ); + execute(); + project.fireTargetFinished( this, null ); + } + catch( RuntimeException exc ) + { + project.fireTargetFinished( this, exc ); + throw exc; + } + } + + public void addDataType( RuntimeConfigurable r ) + { + children.addElement( r ); + } + + public void addDependency( String dependency ) + { + dependencies.addElement( dependency ); + } + + public void addTask( Task task ) + { + children.addElement( task ); + } + + public void execute() + throws BuildException + { + if( testIfCondition() && testUnlessCondition() ) + { + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + Object o = enum.nextElement(); + if( o instanceof Task ) + { + Task task = ( Task )o; + task.perform(); + } + else + { + RuntimeConfigurable r = ( RuntimeConfigurable )o; + r.maybeConfigure( project ); + } + } + } + else if( !testIfCondition() ) + { + project.log( this, "Skipped because property '" + this.ifCondition + "' not set.", + Project.MSG_VERBOSE ); + } + else + { + project.log( this, "Skipped because property '" + this.unlessCondition + "' set.", + Project.MSG_VERBOSE ); + } + } + + public String toString() + { + return name; + } + + void replaceChild( Task el, Object o ) + { + int index = -1; + while( ( index = children.indexOf( el ) ) >= 0 ) + { + children.setElementAt( o, index ); + } + } + + private boolean testIfCondition() + { + if( "".equals( ifCondition ) ) + { + return true; + } + + String test = project.replaceProperties( ifCondition ); + return project.getProperty( test ) != null; + } + + private boolean testUnlessCondition() + { + if( "".equals( unlessCondition ) ) + { + return true; + } + String test = project.replaceProperties( unlessCondition ); + return project.getProperty( test ) == null; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Task.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Task.java new file mode 100644 index 000000000..7a08963ea --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Task.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.TaskException; + +public abstract class Task + extends ProjectComponent + implements org.apache.myrmidon.api.Task +{ + protected Target target; + protected String description; + protected Location location = Location.UNKNOWN_LOCATION; + protected String taskName; + protected String taskType; + private boolean invalid; + protected RuntimeConfigurable wrapper; + + private UnknownElement replacement; + + /** + * Sets a description of the current action. It will be usefull in + * commenting what we are doing. + * + * @param desc The new Description value + */ + public void setDescription( String desc ) + { + description = desc; + } + + /** + * Sets the file location where this task was defined. + * + * @param location The new Location value + */ + public void setLocation( Location location ) + { + this.location = location; + } + + /** + * Sets the target object of this task. + * + * @param target Target in whose scope this task belongs. + */ + public void setOwningTarget( Target target ) + { + this.target = target; + } + + /** + * Set the name to use in logging messages. + * + * @param name the name to use in logging messages. + */ + public void setTaskName( String name ) + { + this.taskName = name; + } + + public String getDescription() + { + return description; + } + + /** + * Returns the file location where this task was defined. + * + * @return The Location value + */ + public Location getLocation() + { + return location; + } + + /** + * Get the Target to which this task belongs + * + * @return the task's target. + */ + public Target getOwningTarget() + { + return target; + } + + /** + * Returns the wrapper class for runtime configuration. + * + * @return The RuntimeConfigurableWrapper value + */ + public RuntimeConfigurable getRuntimeConfigurableWrapper() + { + if( wrapper == null ) + { + wrapper = new RuntimeConfigurable( this, getTaskName() ); + } + return wrapper; + } + + /** + * Get the name to use in logging messages. + * + * @return the name to use in logging messages. + */ + public String getTaskName() + { + return taskName; + } + + /** + * Perform this task + */ + public final void perform() + throws TaskException + { + if( !invalid ) + { + try + { + project.fireTaskStarted( this ); + maybeConfigure(); + execute(); + project.fireTaskFinished( this, null ); + } + catch( TaskException te ) + { + if( te instanceof BuildException ) + { + BuildException be = (BuildException)te; + if( be.getLocation() == Location.UNKNOWN_LOCATION ) + { + be.setLocation( getLocation() ); + } + } + project.fireTaskFinished( this, te ); + throw te; + } + catch( RuntimeException re ) + { + project.fireTaskFinished( this, re ); + throw re; + } + } + else + { + UnknownElement ue = getReplacement(); + Task task = ue.getTask(); + task.perform(); + } + } + + /** + * Called by the project to let the task do it's work. This method may be + * called more than once, if the task is invoked more than once. For + * example, if target1 and target2 both depend on target3, then running "ant + * target1 target2" will run all tasks in target3 twice. + * + * @throws BuildException if someting goes wrong with the build + */ + public void execute() + throws TaskException + { + } + + /** + * Called by the project to let the task initialize properly. + * + * @throws BuildException if someting goes wrong with the build + */ + public void init() + throws TaskException + { + } + + /** + * Log a message with the default (INFO) priority. + * + * @param msg Description of Parameter + */ + public void log( String msg ) + { + log( msg, Project.MSG_INFO ); + } + + /** + * Log a mesage with the give priority. + * + * @param msgLevel the message priority at which this message is to be + * logged. + * @param msg Description of Parameter + */ + public void log( String msg, int msgLevel ) + { + project.log( this, msg, msgLevel ); + } + + /** + * Configure this task - if it hasn't been done already. + * + * @exception BuildException Description of Exception + */ + public void maybeConfigure() + throws TaskException + { + if( !invalid ) + { + if( wrapper != null ) + { + wrapper.maybeConfigure( project ); + } + } + else + { + getReplacement(); + } + } + + protected void setRuntimeConfigurableWrapper( RuntimeConfigurable wrapper ) + { + this.wrapper = wrapper; + } + + protected void handleErrorOutput( String line ) + { + log( line, Project.MSG_ERR ); + } + + protected void handleOutput( String line ) + { + log( line, Project.MSG_INFO ); + } + + /** + * Set the name with which the task has been invoked. + * + * @param type the name the task has been invoked as. + */ + void setTaskType( String type ) + { + this.taskType = type; + } + + /** + * Mark this task as invalid. + */ + final void markInvalid() + { + invalid = true; + } + + /** + * Create an UnknownElement that can be used to replace this task. + * + * @return The Replacement value + */ + private UnknownElement getReplacement() + throws TaskException + { + if( replacement == null ) + { + replacement = new UnknownElement( taskType ); + replacement.setProject( project ); + replacement.setTaskType( taskType ); + replacement.setTaskName( taskName ); + replacement.setLocation( location ); + replacement.setOwningTarget( target ); + replacement.setRuntimeConfigurableWrapper( wrapper ); + wrapper.setProxy( replacement ); + target.replaceChild( this, replacement ); + replacement.maybeConfigure(); + } + return replacement; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskAdapter.java new file mode 100644 index 000000000..6f7ebcbf1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskAdapter.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + + +/** + * Use introspection to "adapt" an arbitrary Bean ( not extending Task, but with + * similar patterns). + * + * @author costin@dnt.ro + */ +public class TaskAdapter extends Task +{ + + Object proxy; + + /** + * Checks a class, whether it is suitable to be adapted by TaskAdapter. + * Checks conditions only, which are additionally required for a tasks + * adapted by TaskAdapter. Thus, this method should be called by {@link + * Project#checkTaskClass}. Throws a BuildException and logs as + * Project.MSG_ERR for conditions, that will cause the task execution to + * fail. Logs other suspicious conditions with Project.MSG_WARN. + * + * @param taskClass Description of Parameter + * @param project Description of Parameter + */ + public static void checkTaskClass( final Class taskClass, final Project project ) + { + // don't have to check for interface, since then + // taskClass would be abstract too. + try + { + final Method executeM = taskClass.getMethod( "execute", null ); + // don't have to check for public, since + // getMethod finds public method only. + // don't have to check for abstract, since then + // taskClass would be abstract too. + if( !Void.TYPE.equals( executeM.getReturnType() ) ) + { + final String message = "return type of execute() should be void but was \"" + executeM.getReturnType() + "\" in " + taskClass; + project.log( message, Project.MSG_WARN ); + } + } + catch( NoSuchMethodException e ) + { + final String message = "No public execute() in " + taskClass; + project.log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + } + + /** + * Set the target object class + * + * @param o The new Proxy value + */ + public void setProxy( Object o ) + { + this.proxy = o; + } + + public Object getProxy() + { + return this.proxy; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Method setProjectM = null; + try + { + Class c = proxy.getClass(); + setProjectM = + c.getMethod( "setProject", new Class[]{Project.class} ); + if( setProjectM != null ) + { + setProjectM.invoke( proxy, new Object[]{project} ); + } + } + catch( NoSuchMethodException e ) + { + // ignore this if the class being used as a task does not have + // a set project method. + } + catch( Exception ex ) + { + log( "Error setting project in " + proxy.getClass(), + Project.MSG_ERR ); + throw new BuildException( ex ); + } + + Method executeM = null; + try + { + Class c = proxy.getClass(); + executeM = c.getMethod( "execute", new Class[0] ); + if( executeM == null ) + { + log( "No public execute() in " + proxy.getClass(), Project.MSG_ERR ); + throw new BuildException( "No public execute() in " + proxy.getClass() ); + } + executeM.invoke( proxy, null ); + return; + } + catch( Exception ex ) + { + log( "Error in " + proxy.getClass(), Project.MSG_ERR ); + throw new BuildException( ex ); + } + + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskContainer.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskContainer.java new file mode 100644 index 000000000..0802b18c4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskContainer.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Interface for objects which can contain tasks

          + * + * It is recommended that implementations call {@link Task#perform perform} + * instead of {@link Task#execute execute} for the tasks they contain, as this + * method ensures that {@link BuildEvent BuildEvents} will be generated.

          + * + * @author Conor MacNeill + */ +public interface TaskContainer +{ + /** + * Add a task to this task container + * + * @param task the task to be added to this container + */ + void addTask( Task task ); +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/UnknownElement.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/UnknownElement.java new file mode 100644 index 000000000..efe3b6046 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/UnknownElement.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Vector; + +/** + * Wrapper class that holds all information necessary to create a task or data + * type that did not exist when Ant started. + * + * @author Stefan Bodewig + */ +public class UnknownElement extends Task +{ + + /** + * Childelements, holds UnknownElement instances. + */ + private Vector children = new Vector(); + + /** + * Holds the name of the task/type or nested child element of a task/type + * that hasn't been defined at parser time. + */ + private String elementName; + + /** + * The real object after it has been loaded. + */ + private Object realThing; + + public UnknownElement( String elementName ) + { + this.elementName = elementName; + } + + /** + * return the corresponding XML element name. + * + * @return The Tag value + */ + public String getTag() + { + return elementName; + } + + /** + * Return the task instance after it has been created (and if it is a task. + * + * @return The Task value + */ + public Task getTask() + { + if( realThing != null && realThing instanceof Task ) + { + return ( Task )realThing; + } + return null; + } + + /** + * Get the name to use in logging messages. + * + * @return the name to use in logging messages. + */ + public String getTaskName() + { + return realThing == null || !( realThing instanceof Task ) ? + super.getTaskName() : ( ( Task )realThing ).getTaskName(); + } + + /** + * Adds a child element to this element. + * + * @param child The feature to be added to the Child attribute + */ + public void addChild( UnknownElement child ) + { + children.addElement( child ); + } + + /** + * Called when the real task has been configured for the first time. + */ + public void execute() + { + if( realThing == null ) + { + // plain impossible to get here, maybeConfigure should + // have thrown an exception. + throw new BuildException( "Could not create task of type: " + + elementName, location ); + } + + if( realThing instanceof Task ) + { + ( ( Task )realThing ).perform(); + } + } + + /** + * creates the real object instance, creates child elements, configures the + * attributes of the real object. + * + * @exception BuildException Description of Exception + */ + public void maybeConfigure() + throws BuildException + { + realThing = makeObject( this, wrapper ); + + wrapper.setProxy( realThing ); + if( realThing instanceof Task ) + { + ( ( Task )realThing ).setRuntimeConfigurableWrapper( wrapper ); + } + + handleChildren( realThing, wrapper ); + + wrapper.maybeConfigure( project ); + if( realThing instanceof Task ) + { + target.replaceChild( this, realThing ); + } + else + { + target.replaceChild( this, wrapper ); + } + } + + protected BuildException getNotFoundException( String what, + String elementName ) + { + String lSep = System.getProperty( "line.separator" ); + String msg = "Could not create " + what + " of type: " + elementName + + "." + lSep + + "Ant could not find the task or a class this" + lSep + + "task relies upon." + lSep + + "Common solutions are to use taskdef to declare" + lSep + + "your task, or, if this is an optional task," + lSep + + "to put the optional.jar and all required libraries of" + lSep + + "this task in the lib directory of" + lSep + + "your ant installation (ANT_HOME)." + lSep + + "There is also the possibility that your build file " + lSep + + "is written to work with a more recent version of ant " + lSep + + "than the one you are using, in which case you have to " + lSep + + "upgrade."; + return new BuildException( msg, location ); + } + + /** + * Creates child elements, creates children of the children, sets attributes + * of the child elements. + * + * @param parent Description of Parameter + * @param parentWrapper Description of Parameter + * @exception BuildException Description of Exception + */ + protected void handleChildren( Object parent, + RuntimeConfigurable parentWrapper ) + throws BuildException + { + + if( parent instanceof TaskAdapter ) + { + parent = ( ( TaskAdapter )parent ).getProxy(); + } + + Class parentClass = parent.getClass(); + IntrospectionHelper ih = IntrospectionHelper.getHelper( parentClass ); + + for( int i = 0; i < children.size(); i++ ) + { + RuntimeConfigurable childWrapper = parentWrapper.getChild( i ); + UnknownElement child = ( UnknownElement )children.elementAt( i ); + Object realChild = null; + + if( parent instanceof TaskContainer ) + { + realChild = makeTask( child, childWrapper, false ); + ( ( TaskContainer )parent ).addTask( ( Task )realChild ); + } + else + { + realChild = ih.createElement( project, parent, child.getTag() ); + } + + childWrapper.setProxy( realChild ); + if( parent instanceof TaskContainer ) + { + ( ( Task )realChild ).setRuntimeConfigurableWrapper( childWrapper ); + } + + child.handleChildren( realChild, childWrapper ); + + if( parent instanceof TaskContainer ) + { + ( ( Task )realChild ).maybeConfigure(); + } + } + } + + /** + * Creates a named task or data type - if it is a task, configure it up to + * the init() stage. + * + * @param ue Description of Parameter + * @param w Description of Parameter + * @return Description of the Returned Value + */ + protected Object makeObject( UnknownElement ue, RuntimeConfigurable w ) + { + Object o = makeTask( ue, w, true ); + if( o == null ) + { + o = project.createDataType( ue.getTag() ); + } + if( o == null ) + { + throw getNotFoundException( "task or type", ue.getTag() ); + } + return o; + } + + /** + * Create a named task and configure it up to the init() stage. + * + * @param ue Description of Parameter + * @param w Description of Parameter + * @param onTopLevel Description of Parameter + * @return Description of the Returned Value + */ + protected Task makeTask( UnknownElement ue, RuntimeConfigurable w, + boolean onTopLevel ) + { + Task task = project.createTask( ue.getTag() ); + if( task == null && !onTopLevel ) + { + throw getNotFoundException( "task", ue.getTag() ); + } + + if( task != null ) + { + task.setLocation( getLocation() ); + // UnknownElement always has an associated target + task.setOwningTarget( target ); + task.init(); + } + return task; + } + +}// UnknownElement diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/defaultManifest.mf b/proposal/myrmidon/src/todo/org/apache/tools/ant/defaultManifest.mf new file mode 100644 index 000000000..1dc733da7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/defaultManifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Created-By: Apache Ant @VERSION@ + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ant.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ant.java new file mode 100644 index 000000000..937f44cd2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ant.java @@ -0,0 +1,550 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.DefaultLogger; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.util.FileUtils; + +/** + * Call Ant in a sub-project
          + *  <target name="foo" depends="init">
          + *    <ant antfile="build.xml" target="bar" >
          + *      <property name="property1" value="aaaaa" />
          + *      <property name="foo" value="baz" />
          + *    </ant> </target> <target name="bar"
          + * depends="init"> <echo message="prop is ${property1}
          + * ${foo}" /> </target> 
          + * + * @author costin@dnt.ro + */ +public class Ant extends Task +{ + + /** + * the basedir where is executed the build file + */ + private File dir = null; + + /** + * the build.xml file (can be absolute) in this case dir will be ignored + */ + private String antFile = null; + + /** + * the target to call if any + */ + private String target = null; + + /** + * the output + */ + private String output = null; + + /** + * should we inherit properties from the parent ? + */ + private boolean inheritAll = true; + + /** + * should we inherit references from the parent ? + */ + private boolean inheritRefs = false; + + /** + * the properties to pass to the new project + */ + private Vector properties = new Vector(); + + /** + * the references to pass to the new project + */ + private Vector references = new Vector(); + + /** + * the temporary project created to run the build file + */ + private Project newProject; + + /** + * set the build file, it can be either absolute or relative. If it is + * absolute, dir will be ignored, if it is relative it will be + * resolved relative to dir . + * + * @param s The new Antfile value + */ + public void setAntfile( String s ) + { + // @note: it is a string and not a file to handle relative/absolute + // otherwise a relative file will be resolved based on the current + // basedir. + this.antFile = s; + } + + /** + * ... + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * If true, inherit all properties from parent Project If false, inherit + * only userProperties and those defined inside the ant call itself + * + * @param value The new InheritAll value + */ + public void setInheritAll( boolean value ) + { + inheritAll = value; + } + + /** + * If true, inherit all references from parent Project If false, inherit + * only those defined inside the ant call itself + * + * @param value The new InheritRefs value + */ + public void setInheritRefs( boolean value ) + { + inheritRefs = value; + } + + public void setOutput( String s ) + { + this.output = s; + } + + /** + * set the target to execute. If none is defined it will execute the default + * target of the build file + * + * @param s The new Target value + */ + public void setTarget( String s ) + { + this.target = s; + } + + /** + * create a reference element that identifies a data type that should be + * carried over to the new project. + * + * @param r The feature to be added to the Reference attribute + */ + public void addReference( Reference r ) + { + references.addElement( r ); + } + + /** + * create a property to pass to the new project as a 'user property' + * + * @return Description of the Returned Value + */ + public Property createProperty() + { + if( newProject == null ) + { + reinit(); + } + Property p = new Property( true ); + p.setProject( newProject ); + p.setTaskName( "property" ); + properties.addElement( p ); + return p; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + if( newProject == null ) + { + reinit(); + } + + if( ( dir == null ) && ( inheritAll == true ) ) + { + dir = project.getBaseDir(); + } + + initializeProject(); + + if( dir != null ) + { + newProject.setBaseDir( dir ); + newProject.setUserProperty( "basedir", dir.getAbsolutePath() ); + } + else + { + dir = project.getBaseDir(); + } + + overrideProperties(); + + if( antFile == null ) + { + antFile = "build.xml"; + } + + File file = FileUtils.newFileUtils().resolveFile( dir, antFile ); + antFile = file.getAbsolutePath(); + + newProject.setUserProperty( "ant.file", antFile ); + ProjectHelper.configureProject( newProject, new File( antFile ) ); + + if( target == null ) + { + target = newProject.getDefaultTarget(); + } + + addReferences(); + + // Are we trying to call the target in which we are defined? + if( newProject.getBaseDir().equals( project.getBaseDir() ) && + newProject.getProperty( "ant.file" ).equals( project.getProperty( "ant.file" ) ) && + getOwningTarget() != null && + target.equals( this.getOwningTarget().getName() ) ) + { + + throw new BuildException( "ant task calling its own parent target" ); + } + + newProject.executeTarget( target ); + } + finally + { + // help the gc + newProject = null; + } + } + + public void init() + { + newProject = new Project(); + newProject.setJavaVersionProperty(); + newProject.addTaskDefinition( "property", + ( Class )project.getTaskDefinitions().get( "property" ) ); + } + + protected void handleErrorOutput( String line ) + { + if( newProject != null ) + { + newProject.demuxOutput( line, true ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( newProject != null ) + { + newProject.demuxOutput( line, false ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Add the references explicitly defined as nested elements to the new + * project. Also copy over all references that don't override existing + * references in the new project if inheritall has been requested. + * + * @exception BuildException Description of Exception + */ + private void addReferences() + throws BuildException + { + Hashtable thisReferences = ( Hashtable )project.getReferences().clone(); + Hashtable newReferences = newProject.getReferences(); + Enumeration e; + if( references.size() > 0 ) + { + for( e = references.elements(); e.hasMoreElements(); ) + { + Reference ref = ( Reference )e.nextElement(); + String refid = ref.getRefId(); + if( refid == null ) + { + throw new BuildException( "the refid attribute is required for reference elements" ); + } + if( !thisReferences.containsKey( refid ) ) + { + log( "Parent project doesn't contain any reference '" + + refid + "'", + Project.MSG_WARN ); + continue; + } + + Object o = thisReferences.remove( refid ); + String toRefid = ref.getToRefid(); + if( toRefid == null ) + { + toRefid = refid; + } + copyReference( refid, toRefid ); + } + } + + // Now add all references that are not defined in the + // subproject, if inheritRefs is true + if( inheritRefs ) + { + for( e = thisReferences.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + if( newReferences.containsKey( key ) ) + { + continue; + } + copyReference( key, key ); + } + } + } + + /** + * Try to clone and reconfigure the object referenced by oldkey in the + * parent project and add it to the new project with the key newkey.

          + * + * If we cannot clone it, copy the referenced object itself and keep our + * fingers crossed.

          + * + * @param oldKey Description of Parameter + * @param newKey Description of Parameter + */ + private void copyReference( String oldKey, String newKey ) + { + Object orig = project.getReference( oldKey ); + Class c = orig.getClass(); + Object copy = orig; + try + { + Method cloneM = c.getMethod( "clone", new Class[0] ); + if( cloneM != null ) + { + copy = cloneM.invoke( orig, new Object[0] ); + } + } + catch( Exception e ) + { + // not Clonable + } + + if( copy instanceof ProjectComponent ) + { + ( ( ProjectComponent )copy ).setProject( newProject ); + } + else + { + try + { + Method setProjectM = + c.getMethod( "setProject", new Class[]{Project.class} ); + if( setProjectM != null ) + { + setProjectM.invoke( copy, new Object[]{newProject} ); + } + } + catch( NoSuchMethodException e ) + { + // ignore this if the class being referenced does not have + // a set project method. + } + catch( Exception e2 ) + { + String msg = "Error setting new project instance for reference with id " + + oldKey; + throw new BuildException( msg, e2, location ); + } + } + newProject.addReference( newKey, copy ); + } + + private void initializeProject() + { + Vector listeners = project.getBuildListeners(); + for( int i = 0; i < listeners.size(); i++ ) + { + newProject.addBuildListener( ( BuildListener )listeners.elementAt( i ) ); + } + + if( output != null ) + { + try + { + PrintStream out = new PrintStream( new FileOutputStream( output ) ); + DefaultLogger logger = new DefaultLogger(); + logger.setMessageOutputLevel( Project.MSG_INFO ); + logger.setOutputPrintStream( out ); + logger.setErrorPrintStream( out ); + newProject.addBuildListener( logger ); + } + catch( IOException ex ) + { + log( "Ant: Can't set output to " + output ); + } + } + + Hashtable taskdefs = project.getTaskDefinitions(); + Enumeration et = taskdefs.keys(); + while( et.hasMoreElements() ) + { + String taskName = ( String )et.nextElement(); + if( taskName.equals( "property" ) ) + { + // we have already added this taskdef in #init + continue; + } + Class taskClass = ( Class )taskdefs.get( taskName ); + newProject.addTaskDefinition( taskName, taskClass ); + } + + Hashtable typedefs = project.getDataTypeDefinitions(); + Enumeration e = typedefs.keys(); + while( e.hasMoreElements() ) + { + String typeName = ( String )e.nextElement(); + Class typeClass = ( Class )typedefs.get( typeName ); + newProject.addDataTypeDefinition( typeName, typeClass ); + } + + // set user-defined or all properties from calling project + Hashtable prop1; + if( inheritAll == true ) + { + prop1 = project.getProperties(); + } + else + { + prop1 = project.getUserProperties(); + + // set Java built-in properties separately, + // b/c we won't inherit them. + newProject.setSystemProperties(); + } + + e = prop1.keys(); + while( e.hasMoreElements() ) + { + String arg = ( String )e.nextElement(); + if( "basedir".equals( arg ) || "ant.file".equals( arg ) ) + { + // basedir and ant.file get special treatment in execute() + continue; + } + + String value = ( String )prop1.get( arg ); + if( inheritAll == true ) + { + newProject.setProperty( arg, value ); + } + else + { + newProject.setUserProperty( arg, value ); + } + } + } + + /** + * Override the properties in the new project with the one explicitly + * defined as nested elements here. + * + * @exception BuildException Description of Exception + */ + private void overrideProperties() + throws BuildException + { + Enumeration e = properties.elements(); + while( e.hasMoreElements() ) + { + Property p = ( Property )e.nextElement(); + p.setProject( newProject ); + p.execute(); + } + } + + private void reinit() + { + init(); + for( int i = 0; i < properties.size(); i++ ) + { + Property p = ( Property )properties.elementAt( i ); + Property newP = ( Property )newProject.createTask( "property" ); + newP.setName( p.getName() ); + if( p.getValue() != null ) + { + newP.setValue( p.getValue() ); + } + if( p.getFile() != null ) + { + newP.setFile( p.getFile() ); + } + if( p.getResource() != null ) + { + newP.setResource( p.getResource() ); + } + properties.setElementAt( newP, i ); + } + } + + /** + * Helper class that implements the nested <reference> element of + * <ant> and <antcall>. + * + * @author RT + */ + public static class Reference + extends org.apache.tools.ant.types.Reference + { + + private String targetid = null; + + public Reference() + { + super(); + } + + public void setToRefid( String targetid ) + { + this.targetid = targetid; + } + + public String getToRefid() + { + return targetid; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/AntStructure.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/AntStructure.java new file mode 100644 index 000000000..1b1007267 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/AntStructure.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.IntrospectionHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Creates a partial DTD for Ant from the currently known tasks. + * + * @author Stefan Bodewig + * @version $Revision$ + */ + +public class AntStructure extends Task +{ + + private final String lSep = System.getProperty( "line.separator" ); + + private final String BOOLEAN = "%boolean;"; + private final String TASKS = "%tasks;"; + private final String TYPES = "%types;"; + + private Hashtable visited = new Hashtable(); + + private File output; + + /** + * The output file. + * + * @param output The new Output value + */ + public void setOutput( File output ) + { + this.output = output; + } + + public void execute() + throws BuildException + { + + if( output == null ) + { + throw new BuildException( "output attribute is required", location ); + } + + PrintWriter out = null; + try + { + try + { + out = new PrintWriter( new OutputStreamWriter( new FileOutputStream( output ), "UTF8" ) ); + } + catch( UnsupportedEncodingException ue ) + { + /* + * Plain impossible with UTF8, see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * + * fallback to platform specific anyway. + */ + out = new PrintWriter( new FileWriter( output ) ); + } + + printHead( out, project.getTaskDefinitions().keys(), + project.getDataTypeDefinitions().keys() ); + + printTargetDecl( out ); + + Enumeration dataTypes = project.getDataTypeDefinitions().keys(); + while( dataTypes.hasMoreElements() ) + { + String typeName = ( String )dataTypes.nextElement(); + printElementDecl( out, typeName, + ( Class )project.getDataTypeDefinitions().get( typeName ) ); + } + + Enumeration tasks = project.getTaskDefinitions().keys(); + while( tasks.hasMoreElements() ) + { + String taskName = ( String )tasks.nextElement(); + printElementDecl( out, taskName, + ( Class )project.getTaskDefinitions().get( taskName ) ); + } + + printTail( out ); + + } + catch( IOException ioe ) + { + throw new BuildException( "Error writing " + output.getAbsolutePath(), + ioe, location ); + } + finally + { + if( out != null ) + { + out.close(); + } + } + } + + /** + * Does this String match the XML-NMTOKEN production? + * + * @param s Description of Parameter + * @return The Nmtoken value + */ + protected boolean isNmtoken( String s ) + { + for( int i = 0; i < s.length(); i++ ) + { + char c = s.charAt( i ); + // XXX - we are ommitting CombiningChar and Extender here + if( !Character.isLetterOrDigit( c ) && + c != '.' && c != '-' && + c != '_' && c != ':' ) + { + return false; + } + } + return true; + } + + /** + * Do the Strings all match the XML-NMTOKEN production?

          + * + * Otherwise they are not suitable as an enumerated attribute, for example. + *

          + * + * @param s Description of Parameter + * @return Description of the Returned Value + */ + protected boolean areNmtokens( String[] s ) + { + for( int i = 0; i < s.length; i++ ) + { + if( !isNmtoken( s[i] ) ) + { + return false; + } + } + return true; + } + + private void printElementDecl( PrintWriter out, String name, Class element ) + throws BuildException + { + + if( visited.containsKey( name ) ) + { + return; + } + visited.put( name, "" ); + + IntrospectionHelper ih = null; + try + { + ih = IntrospectionHelper.getHelper( element ); + } + catch( Throwable t ) + { + /* + * XXX - failed to load the class properly. + * + * should we print a warning here? + */ + return; + } + + StringBuffer sb = new StringBuffer( "" ).append( lSep ); + sb.append( "" ).append( lSep ); + out.println( sb ); + return; + } + + Vector v = new Vector(); + if( ih.supportsCharacters() ) + { + v.addElement( "#PCDATA" ); + } + + if( TaskContainer.class.isAssignableFrom( element ) ) + { + v.addElement( TASKS ); + } + + Enumeration enum = ih.getNestedElements(); + while( enum.hasMoreElements() ) + { + v.addElement( ( String )enum.nextElement() ); + } + + if( v.isEmpty() ) + { + sb.append( "EMPTY" ); + } + else + { + sb.append( "(" ); + for( int i = 0; i < v.size(); i++ ) + { + if( i != 0 ) + { + sb.append( " | " ); + } + sb.append( v.elementAt( i ) ); + } + sb.append( ")" ); + if( v.size() > 1 || !v.elementAt( 0 ).equals( "#PCDATA" ) ) + { + sb.append( "*" ); + } + } + sb.append( ">" ); + out.println( sb ); + + sb.setLength( 0 ); + sb.append( "" ).append( lSep ); + out.println( sb ); + + for( int i = 0; i < v.size(); i++ ) + { + String nestedName = ( String )v.elementAt( i ); + if( !"#PCDATA".equals( nestedName ) && + !TASKS.equals( nestedName ) && + !TYPES.equals( nestedName ) + ) + { + printElementDecl( out, nestedName, ih.getElementType( nestedName ) ); + } + } + } + + private void printHead( PrintWriter out, Enumeration tasks, + Enumeration types ) + { + out.println( "" ); + out.println( "" ); + out.print( "" ); + out.print( "" ); + + out.println( "" ); + + out.print( "" ); + out.println( "" ); + out.println( "" ); + } + + private void printTail( PrintWriter out ) { } + + private void printTargetDecl( PrintWriter out ) + { + out.print( "" ); + out.println( "" ); + + out.println( "" ); + out.println( "" ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Available.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Available.java new file mode 100644 index 000000000..a4396a6a0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Available.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + +/** + * Will set the given property if the requested resource is available at + * runtime. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Magesh Umasankar + */ + +public class Available extends Task implements Condition +{ + private String value = "true"; + private String classname; + private Path classpath; + private String file; + private Path filepath; + private AntClassLoader loader; + + private String property; + private String resource; + private FileDir type; + + public void setClassname( String classname ) + { + if( !"".equals( classname ) ) + { + this.classname = classname; + } + } + + public void setClasspath( Path classpath ) + { + createClasspath().append( classpath ); + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setFile( String file ) + { + this.file = file; + } + + public void setFilepath( Path filepath ) + { + createFilepath().append( filepath ); + } + + public void setProperty( String property ) + { + this.property = property; + } + + public void setResource( String resource ) + { + this.resource = resource; + } + + /** + * @param type The new Type value + * @deprecated setType(String) is deprecated and is replaced with + * setType(Available.FileDir) to make Ant's Introspection mechanism do + * the work and also to encapsulate operations on the type in its own + * class. + */ + public void setType( String type ) + { + log( "DEPRECATED - The setType(String) method has been deprecated." + + " Use setType(Available.FileDir) instead." ); + this.type = new FileDir(); + this.type.setValue( type ); + } + + public void setType( FileDir type ) + { + this.type = type; + } + + public void setValue( String value ) + { + this.value = value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public Path createFilepath() + { + if( this.filepath == null ) + { + this.filepath = new Path( project ); + } + return this.filepath.createPath(); + } + + public boolean eval() + throws BuildException + { + if( classname == null && file == null && resource == null ) + { + throw new BuildException( "At least one of (classname|file|resource) is required", location ); + } + + if( type != null ) + { + if( file == null ) + { + throw new BuildException( "The type attribute is only valid when specifying the file attribute." ); + } + } + + if( classpath != null ) + { + classpath.setProject( project ); + this.loader = new AntClassLoader( project, classpath ); + } + + if( ( classname != null ) && !checkClass( classname ) ) + { + log( "Unable to load class " + classname + " to set property " + property, Project.MSG_VERBOSE ); + return false; + } + + if( ( file != null ) && !checkFile() ) + { + if( type != null ) + { + log( "Unable to find " + type + " " + file + " to set property " + property, Project.MSG_VERBOSE ); + } + else + { + log( "Unable to find " + file + " to set property " + property, Project.MSG_VERBOSE ); + } + return false; + } + + if( ( resource != null ) && !checkResource( resource ) ) + { + log( "Unable to load resource " + resource + " to set property " + property, Project.MSG_VERBOSE ); + return false; + } + + if( loader != null ) + { + loader.cleanup(); + } + + return true; + } + + public void execute() + throws BuildException + { + if( property == null ) + { + throw new BuildException( "property attribute is required", location ); + } + + if( eval() ) + { + String lSep = System.getProperty( "line.separator" ); + if( null != project.getProperty( property ) ) + { + log( "DEPRECATED - used to overide an existing property. " + + lSep + + " Build writer should not reuse the same property name for " + + lSep + "different values." ); + } + this.project.setProperty( property, value ); + } + } + + private boolean checkClass( String classname ) + { + try + { + if( loader != null ) + { + loader.loadClass( classname ); + } + else + { + ClassLoader l = this.getClass().getClassLoader(); + // Can return null to represent the bootstrap class loader. + // see API docs of Class.getClassLoader. + if( l != null ) + { + l.loadClass( classname ); + } + else + { + Class.forName( classname ); + } + } + return true; + } + catch( ClassNotFoundException e ) + { + return false; + } + catch( NoClassDefFoundError e ) + { + return false; + } + } + + private boolean checkFile() + { + if( filepath == null ) + { + return checkFile( project.resolveFile( file ), file ); + } + else + { + String[] paths = filepath.list(); + for( int i = 0; i < paths.length; ++i ) + { + log( "Searching " + paths[i], Project.MSG_DEBUG ); + /* + * filepath can be a list of directory and/or + * file names (gen'd via ) + * + * look for: + * full-pathname specified == path in list + * full-pathname specified == parent dir of path in list + * simple name specified == path in list + * simple name specified == path in list + name + * simple name specified == parent dir + name + * simple name specified == parent of parent dir + name + * + */ + File path = new File( paths[i] ); + + // ** full-pathname specified == path in list + // ** simple name specified == path in list + if( path.exists() && file.equals( paths[i] ) ) + { + if( type == null ) + { + log( "Found: " + path, Project.MSG_VERBOSE ); + return true; + } + else if( type.isDir() + && path.isDirectory() ) + { + log( "Found directory: " + path, Project.MSG_VERBOSE ); + return true; + } + else if( type.isFile() + && path.isFile() ) + { + log( "Found file: " + path, Project.MSG_VERBOSE ); + return true; + } + // not the requested type + return false; + } + + FileUtils fileUtils = FileUtils.newFileUtils(); + File parent = fileUtils.getParentFile( path ); + // ** full-pathname specified == parent dir of path in list + if( parent != null && parent.exists() + && file.equals( parent.getAbsolutePath() ) ) + { + if( type == null ) + { + log( "Found: " + parent, Project.MSG_VERBOSE ); + return true; + } + else if( type.isDir() ) + { + log( "Found directory: " + parent, Project.MSG_VERBOSE ); + return true; + } + // not the requested type + return false; + } + + // ** simple name specified == path in list + name + if( path.exists() && path.isDirectory() ) + { + if( checkFile( new File( path, file ), + file + " in " + path ) ) + { + return true; + } + } + + // ** simple name specified == parent dir + name + if( parent != null && parent.exists() ) + { + if( checkFile( new File( parent, file ), + file + " in " + parent ) ) + { + return true; + } + } + + // ** simple name specified == parent of parent dir + name + if( parent != null ) + { + File grandParent = fileUtils.getParentFile( parent ); + if( grandParent != null && grandParent.exists() ) + { + if( checkFile( new File( grandParent, file ), + file + " in " + grandParent ) ) + { + return true; + } + } + } + } + } + return false; + } + + private boolean checkFile( File f, String text ) + { + if( type != null ) + { + if( type.isDir() ) + { + if( f.isDirectory() ) + { + log( "Found directory: " + text, Project.MSG_VERBOSE ); + } + return f.isDirectory(); + } + else if( type.isFile() ) + { + if( f.isFile() ) + { + log( "Found file: " + text, Project.MSG_VERBOSE ); + } + return f.isFile(); + } + } + if( f.exists() ) + { + log( "Found: " + text, Project.MSG_VERBOSE ); + } + return f.exists(); + } + + private boolean checkResource( String resource ) + { + if( loader != null ) + { + return ( loader.getResourceAsStream( resource ) != null ); + } + else + { + ClassLoader cL = this.getClass().getClassLoader(); + if( cL != null ) + { + return ( cL.getResourceAsStream( resource ) != null ); + } + else + { + return + ( ClassLoader.getSystemResourceAsStream( resource ) != null ); + } + } + } + + public static class FileDir extends EnumeratedAttribute + { + + private final static String[] values = {"file", "dir"}; + + public String[] getValues() + { + return values; + } + + public boolean isDir() + { + return "dir".equalsIgnoreCase( getValue() ); + } + + public boolean isFile() + { + return "file".equalsIgnoreCase( getValue() ); + } + + public String toString() + { + return getValue(); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BUnzip2.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BUnzip2.java new file mode 100644 index 000000000..882af9655 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BUnzip2.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.bzip2.CBZip2InputStream; + +/** + * Expands a file that has been compressed with the BZIP2 algorithm. Normally + * used to compress non-compressed archives such as TAR files. + * + * @author Magesh Umasankar + */ + +public class BUnzip2 extends Unpack +{ + + private final static String DEFAULT_EXTENSION = ".bz2"; + + protected String getDefaultExtension() + { + return DEFAULT_EXTENSION; + } + + protected void extract() + { + if( source.lastModified() > dest.lastModified() ) + { + log( "Expanding " + source.getAbsolutePath() + " to " + + dest.getAbsolutePath() ); + + FileOutputStream out = null; + CBZip2InputStream zIn = null; + FileInputStream fis = null; + BufferedInputStream bis = null; + try + { + out = new FileOutputStream( dest ); + fis = new FileInputStream( source ); + bis = new BufferedInputStream( fis ); + int b = bis.read(); + if( b != 'B' ) + { + throw new BuildException( "Invalid bz2 file.", location ); + } + b = bis.read(); + if( b != 'Z' ) + { + throw new BuildException( "Invalid bz2 file.", location ); + } + zIn = new CBZip2InputStream( bis ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = zIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + catch( IOException ioe ) + { + String msg = "Problem expanding bzip2 " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( bis != null ) + { + try + { + bis.close(); + } + catch( IOException ioex ) + {} + } + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException ioex ) + {} + } + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + if( zIn != null ) + { + try + { + zIn.close(); + } + catch( IOException ioex ) + {} + } + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BZip2.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BZip2.java new file mode 100644 index 000000000..9c3cdc401 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BZip2.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Pack; +import org.apache.tools.bzip2.CBZip2OutputStream; + +/** + * Compresses a file with the BZip2 algorithm. Normally used to compress + * non-compressed archives such as TAR files. + * + * @author Magesh Umasankar + */ + +public class BZip2 extends Pack +{ + protected void pack() + { + CBZip2OutputStream zOut = null; + try + { + BufferedOutputStream bos = + new BufferedOutputStream( new FileOutputStream( zipFile ) ); + bos.write( 'B' ); + bos.write( 'Z' ); + zOut = new CBZip2OutputStream( bos ); + zipFile( source, zOut ); + } + catch( IOException ioe ) + { + String msg = "Problem creating bzip2 " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( zOut != null ) + { + try + { + // close up + zOut.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CVSPass.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CVSPass.java new file mode 100644 index 000000000..356579a4a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CVSPass.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * CVSLogin Adds an new entry to a CVS password file + * + * @author Jeff Martin + * @version $Revision$ + */ +public class CVSPass extends Task +{ + /** + * CVS Root + */ + private String cvsRoot = null; + /** + * Password file to add password to + */ + private File passFile = null; + /** + * Password to add to file + */ + private String password = null; + /** + * End of line character + */ + private final String EOL = System.getProperty( "line.separator" ); + + /** + * Array contain char conversion data + */ + private final char shifts[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 114, 120, 53, 79, 96, 109, 72, 108, 70, 64, 76, 67, 116, 74, 68, 87, + 111, 52, 75, 119, 49, 34, 82, 81, 95, 65, 112, 86, 118, 110, 122, 105, + 41, 57, 83, 43, 46, 102, 40, 89, 38, 103, 45, 50, 42, 123, 91, 35, + 125, 55, 54, 66, 124, 126, 59, 47, 92, 71, 115, 78, 88, 107, 106, 56, + 36, 121, 117, 104, 101, 100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, + 58, 113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85, 223, + 225, 216, 187, 166, 229, 189, 222, 188, 141, 249, 148, 200, 184, 136, 248, 190, + 199, 170, 181, 204, 138, 232, 218, 183, 255, 234, 220, 247, 213, 203, 226, 193, + 174, 172, 228, 252, 217, 201, 131, 230, 197, 211, 145, 238, 161, 179, 160, 212, + 207, 221, 254, 173, 202, 146, 224, 151, 140, 196, 205, 130, 135, 133, 143, 246, + 192, 159, 244, 239, 185, 168, 215, 144, 139, 165, 180, 157, 147, 186, 214, 176, + 227, 231, 219, 169, 175, 156, 206, 198, 129, 164, 150, 210, 154, 177, 134, 127, + 182, 128, 158, 208, 162, 132, 167, 209, 149, 241, 153, 251, 237, 236, 171, 195, + 243, 233, 253, 240, 194, 250, 191, 155, 142, 137, 245, 235, 163, 242, 178, 152}; + + public CVSPass() + { + passFile = new File( System.getProperty( "user.home" ) + "/.cvspass" ); + } + + /** + * Sets cvs root to be added to the password file + * + * @param cvsRoot The new Cvsroot value + */ + public void setCvsroot( String cvsRoot ) + { + this.cvsRoot = cvsRoot; + } + + /** + * Sets the password file attribute. + * + * @param passFile The new Passfile value + */ + public void setPassfile( File passFile ) + { + this.passFile = passFile; + } + + /** + * Sets the password attribute. + * + * @param password The new Password value + */ + public void setPassword( String password ) + { + this.password = password; + } + + /** + * Does the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public final void execute() + throws BuildException + { + if( cvsRoot == null ) + throw new BuildException( "cvsroot is required" ); + if( password == null ) + throw new BuildException( "password is required" ); + + log( "cvsRoot: " + cvsRoot, project.MSG_DEBUG ); + log( "password: " + password, project.MSG_DEBUG ); + log( "passFile: " + passFile, project.MSG_DEBUG ); + + try + { + StringBuffer buf = new StringBuffer(); + + if( passFile.exists() ) + { + BufferedReader reader = + new BufferedReader( new FileReader( passFile ) ); + + String line = null; + + while( ( line = reader.readLine() ) != null ) + { + if( !line.startsWith( cvsRoot ) ) + { + buf.append( line + EOL ); + } + } + + reader.close(); + } + + String pwdfile = buf.toString() + cvsRoot + " A" + mangle( password ); + + log( "Writing -> " + pwdfile, project.MSG_DEBUG ); + + PrintWriter writer = new PrintWriter( new FileWriter( passFile ) ); + + writer.println( pwdfile ); + + writer.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + } + + private final String mangle( String password ) + { + StringBuffer buf = new StringBuffer(); + for( int i = 0; i < password.length(); i++ ) + { + buf.append( shifts[password.charAt( i )] ); + } + return buf.toString(); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CallTarget.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CallTarget.java new file mode 100644 index 000000000..80f570e08 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CallTarget.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Call another target in the same project.
          + *    <target name="foo">
          + *      <antcall target="bar">
          + *        <param name="property1" value="aaaaa" />
          + *        <param name="foo" value="baz" />
          + *       </antcall>
          + *    </target>
          + *
          + *    <target name="bar" depends="init">
          + *      <echo message="prop is ${property1} ${foo}" />
          + *    </target>
          + * 

          + * + * This only works as expected if neither property1 nor foo are defined in the + * project itself. + * + * @author Stefan Bodewig + */ +public class CallTarget extends Task +{ + private boolean initialized = false; + private boolean inheritAll = true; + + private Ant callee; + private String subTarget; + + /** + * If true, inherit all properties from parent Project If false, inherit + * only userProperties and those defined inside the antcall call itself + * + * @param inherit The new InheritAll value + */ + public void setInheritAll( boolean inherit ) + { + inheritAll = inherit; + } + + public void setTarget( String target ) + { + subTarget = target; + } + + /** + * create a reference element that identifies a data type that should be + * carried over to the new project. + * + * @param r The feature to be added to the Reference attribute + */ + public void addReference( Ant.Reference r ) + { + callee.addReference( r ); + } + + public Property createParam() + { + return callee.createProperty(); + } + + public void execute() + { + if( !initialized ) + { + init(); + } + + if( subTarget == null ) + { + throw new BuildException( "Attribute target is required.", + location ); + } + + callee.setDir( project.getBaseDir() ); + callee.setAntfile( project.getProperty( "ant.file" ) ); + callee.setTarget( subTarget ); + callee.setInheritAll( inheritAll ); + callee.execute(); + }//-- setInheritAll + + public void init() + { + callee = ( Ant )project.createTask( "ant" ); + callee.setOwningTarget( target ); + callee.setTaskName( getTaskName() ); + callee.setLocation( location ); + callee.init(); + initialized = true; + } + + protected void handleErrorOutput( String line ) + { + if( callee != null ) + { + callee.handleErrorOutput( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( callee != null ) + { + callee.handleOutput( line ); + } + else + { + super.handleOutput( line ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Checksum.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Checksum.java new file mode 100644 index 000000000..a778428c5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Checksum.java @@ -0,0 +1,489 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.FileSet; + +/** + * This task can be used to create checksums for files. It can also be used to + * verify checksums. + * + * @author Magesh Umasankar + */ +public class Checksum extends MatchingTask implements Condition +{ + /** + * File for which checksum is to be calculated. + */ + private File file = null; + /** + * MessageDigest algorithm to be used. + */ + private String algorithm = "MD5"; + /** + * MessageDigest Algorithm provider + */ + private String provider = null; + /** + * Vector to hold source file sets. + */ + private Vector filesets = new Vector(); + /** + * Stores SourceFile, DestFile pairs and SourceFile, Property String pairs. + */ + private Hashtable includeFileMap = new Hashtable(); + /** + * File Extension that is be to used to create or identify destination file + */ + private String fileext; + /** + * Create new destination file? Defaults to false. + */ + private boolean forceOverwrite; + /** + * is this task being used as a nested condition element? + */ + private boolean isCondition; + /** + * Message Digest instance + */ + private MessageDigest messageDigest; + /** + * Holds generated checksum and gets set as a Project Property. + */ + private String property; + /** + * Contains the result of a checksum verification. ("true" or "false") + */ + private String verifyProperty; + + /** + * Sets the MessageDigest algorithm to be used to calculate the checksum. + * + * @param algorithm The new Algorithm value + */ + public void setAlgorithm( String algorithm ) + { + this.algorithm = algorithm; + } + + /** + * Sets the file for which the checksum is to be calculated. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Sets the File Extension that is be to used to create or identify + * destination file + * + * @param fileext The new Fileext value + */ + public void setFileext( String fileext ) + { + this.fileext = fileext; + } + + /** + * Overwrite existing file irrespective of whether it is newer than the + * source file? Defaults to false. + * + * @param forceOverwrite The new ForceOverwrite value + */ + public void setForceOverwrite( boolean forceOverwrite ) + { + this.forceOverwrite = forceOverwrite; + } + + /** + * Sets the property to hold the generated checksum + * + * @param property The new Property value + */ + public void setProperty( String property ) + { + this.property = property; + } + + /** + * Sets the MessageDigest algorithm provider to be used to calculate the + * checksum. + * + * @param provider The new Provider value + */ + public void setProvider( String provider ) + { + this.provider = provider; + } + + /** + * Sets verify property. This project property holds the result of a + * checksum verification - "true" or "false" + * + * @param verifyProperty The new Verifyproperty value + */ + public void setVerifyproperty( String verifyProperty ) + { + this.verifyProperty = verifyProperty; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Calculate the checksum(s) + * + * @return Returns true if the checksum verification test passed, false + * otherwise. + * @exception BuildException Description of Exception + */ + public boolean eval() + throws BuildException + { + isCondition = true; + return validateAndExecute(); + } + + /** + * Calculate the checksum(s). + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + boolean value = validateAndExecute(); + if( verifyProperty != null ) + { + project.setNewProperty( verifyProperty, + new Boolean( value ).toString() ); + } + } + + /** + * Add key-value pair to the hashtable upon which to later operate upon. + * + * @param file The feature to be added to the ToIncludeFileMap attribute + * @exception BuildException Description of Exception + */ + private void addToIncludeFileMap( File file ) + throws BuildException + { + if( file != null ) + { + if( file.exists() ) + { + if( property == null ) + { + File dest = new File( file.getParent(), file.getName() + fileext ); + if( forceOverwrite || isCondition || + ( file.lastModified() > dest.lastModified() ) ) + { + includeFileMap.put( file, dest ); + } + else + { + log( file + " omitted as " + dest + " is up to date.", + Project.MSG_VERBOSE ); + } + } + else + { + includeFileMap.put( file, property ); + } + } + else + { + String message = "Could not find file " + + file.getAbsolutePath() + + " to generate checksum for."; + log( message ); + throw new BuildException( message, location ); + } + } + } + + /** + * Generate checksum(s) using the message digest created earlier. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private boolean generateChecksums() + throws BuildException + { + boolean checksumMatches = true; + FileInputStream fis = null; + FileOutputStream fos = null; + try + { + for( Enumeration e = includeFileMap.keys(); e.hasMoreElements(); ) + { + messageDigest.reset(); + File src = ( File )e.nextElement(); + if( !isCondition ) + { + log( "Calculating " + algorithm + " checksum for " + src ); + } + fis = new FileInputStream( src ); + DigestInputStream dis = new DigestInputStream( fis, + messageDigest ); + while( dis.read() != -1 ) + ; + dis.close(); + fis.close(); + fis = null; + byte[] fileDigest = messageDigest.digest(); + String checksum = ""; + for( int i = 0; i < fileDigest.length; i++ ) + { + String hexStr = Integer.toHexString( 0x00ff & fileDigest[i] ); + if( hexStr.length() < 2 ) + { + checksum += "0"; + } + checksum += hexStr; + } + //can either be a property name string or a file + Object destination = includeFileMap.get( src ); + if( destination instanceof java.lang.String ) + { + String prop = ( String )destination; + if( isCondition ) + { + checksumMatches = checksum.equals( property ); + } + else + { + project.setProperty( prop, checksum ); + } + } + else if( destination instanceof java.io.File ) + { + if( isCondition ) + { + File existingFile = ( File )destination; + if( existingFile.exists() && + existingFile.length() == checksum.length() ) + { + fis = new FileInputStream( existingFile ); + InputStreamReader isr = new InputStreamReader( fis ); + BufferedReader br = new BufferedReader( isr ); + String suppliedChecksum = br.readLine(); + fis.close(); + fis = null; + br.close(); + isr.close(); + checksumMatches = + checksum.equals( suppliedChecksum ); + } + else + { + checksumMatches = false; + } + } + else + { + File dest = ( File )destination; + fos = new FileOutputStream( dest ); + fos.write( checksum.getBytes() ); + fos.close(); + fos = null; + } + } + } + } + catch( Exception e ) + { + throw new BuildException( e ); + } + finally + { + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException e ) + {} + } + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException e ) + {} + } + } + return checksumMatches; + } + + /** + * Validate attributes and get down to business. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private boolean validateAndExecute() + throws BuildException + { + + if( file == null && filesets.size() == 0 ) + { + throw new BuildException( + "Specify at least one source - a file or a fileset." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( + "Checksum cannot be generated for directories" ); + } + + if( property != null && fileext != null ) + { + throw new BuildException( + "Property and FileExt cannot co-exist." ); + } + + if( property != null ) + { + if( forceOverwrite ) + { + throw new BuildException( + "ForceOverwrite cannot be used when Property is specified" ); + } + + if( file != null ) + { + if( filesets.size() > 0 ) + { + throw new BuildException( + "Multiple files cannot be used when Property is specified" ); + } + } + else + { + if( filesets.size() > 1 ) + { + throw new BuildException( + "Multiple files cannot be used when Property is specified" ); + } + } + } + + if( verifyProperty != null ) + { + isCondition = true; + } + + if( verifyProperty != null && forceOverwrite ) + { + throw new BuildException( + "VerifyProperty and ForceOverwrite cannot co-exist." ); + } + + if( isCondition && forceOverwrite ) + { + throw new BuildException( + "ForceOverwrite cannot be used when conditions are being used." ); + } + + if( fileext == null ) + { + fileext = "." + algorithm; + } + else if( fileext.trim().length() == 0 ) + { + throw new BuildException( + "File extension when specified must not be an empty string" ); + } + + messageDigest = null; + if( provider != null ) + { + try + { + messageDigest = MessageDigest.getInstance( algorithm, provider ); + } + catch( NoSuchAlgorithmException noalgo ) + { + throw new BuildException( noalgo ); + } + catch( NoSuchProviderException noprovider ) + { + throw new BuildException( noprovider ); + } + } + else + { + try + { + messageDigest = MessageDigest.getInstance( algorithm ); + } + catch( NoSuchAlgorithmException noalgo ) + { + throw new BuildException( noalgo ); + } + } + + if( messageDigest == null ) + { + throw new BuildException( "Unable to create Message Digest", + location ); + } + + addToIncludeFileMap( file ); + + int sizeofFileSet = filesets.size(); + for( int i = 0; i < sizeofFileSet; i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + for( int j = 0; j < srcFiles.length; j++ ) + { + File src = new File( fs.getDir( project ), srcFiles[j] ); + addToIncludeFileMap( src ); + } + } + + return generateChecksums(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Chmod.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Chmod.java new file mode 100644 index 000000000..96f736e03 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Chmod.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + + +/** + * Chmod equivalent for unix-like environments. + * + * @author costin@eng.sun.com + * @author Mariusz Nowostawski (Marni) + * mnowostawski@infoscience.otago.ac.nz + * @author Stefan Bodewig + */ + +public class Chmod extends ExecuteOn +{ + + private FileSet defaultSet = new FileSet(); + private boolean defaultSetDefined = false; + private boolean havePerm = false; + + public Chmod() + { + super.setExecutable( "chmod" ); + super.setParallel( true ); + super.setSkipEmptyFilesets( true ); + } + + public void setCommand( String e ) + { + throw new BuildException( taskType + " doesn\'t support the command attribute", location ); + } + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + defaultSetDefined = true; + defaultSet.setDefaultexcludes( useDefaultExcludes ); + } + + public void setDir( File src ) + { + defaultSet.setDir( src ); + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + defaultSetDefined = true; + defaultSet.setExcludes( excludes ); + } + + + public void setExecutable( String e ) + { + throw new BuildException( taskType + " doesn\'t support the executable attribute", location ); + } + + public void setFile( File src ) + { + FileSet fs = new FileSet(); + fs.setDir( new File( src.getParent() ) ); + fs.createInclude().setName( src.getName() ); + addFileset( fs ); + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + defaultSetDefined = true; + defaultSet.setIncludes( includes ); + } + + public void setPerm( String perm ) + { + createArg().setValue( perm ); + havePerm = true; + } + + public void setSkipEmptyFilesets( boolean skip ) + { + throw new BuildException( taskType + " doesn\'t support the skipemptyfileset attribute", location ); + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + defaultSetDefined = true; + return defaultSet.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + defaultSetDefined = true; + return defaultSet.createInclude(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + defaultSetDefined = true; + return defaultSet.createPatternSet(); + } + + public void execute() + throws BuildException + { + if( defaultSetDefined || defaultSet.getDir( project ) == null ) + { + super.execute(); + } + else if( isValidOs() ) + { + // we are chmodding the given directory + createArg().setValue( defaultSet.getDir( project ).getPath() ); + Execute execute = prepareExec(); + try + { + execute.setCommandline( cmdl.getCommandline() ); + runExecute( execute ); + } + catch( IOException e ) + { + throw new BuildException( "Execute failed: " + e, e, location ); + } + finally + { + // close the output file if required + logFlush(); + } + } + } + + protected boolean isValidOs() + { + return Os.isFamily( "unix" ) && super.isValidOs(); + } + + protected void checkConfiguration() + { + if( !havePerm ) + { + throw new BuildException( "Required attribute perm not set in chmod", + location ); + } + + if( defaultSetDefined && defaultSet.getDir( project ) != null ) + { + addFileset( defaultSet ); + } + super.checkConfiguration(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CompileTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CompileTask.java new file mode 100644 index 000000000..787a43f73 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CompileTask.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.types.PatternSet; + +/** + * This task will compile and load a new taskdef all in one step. At times, this + * is useful for eliminating ordering dependencies which otherwise would require + * multiple executions of Ant. + * + * @author Sam Ruby rubys@us.ibm.com + * @deprecated use <taskdef> elements nested into <target>s instead + */ + +public class CompileTask extends Javac +{ + + protected Vector taskList = new Vector(); + + /** + * add a new task entry on the task list + * + * @return Description of the Returned Value + */ + public Taskdef createTaskdef() + { + Taskdef task = new Taskdef(); + taskList.addElement( task ); + return task; + } + + /** + * have execute do nothing + */ + public void execute() { } + + /** + * do all the real work in init + */ + public void init() + { + log( "!! CompileTask is deprecated. !!" ); + log( "Use elements nested into s instead" ); + + // create all the include entries from the task defs + for( Enumeration e = taskList.elements(); e.hasMoreElements(); ) + { + Taskdef task = ( Taskdef )e.nextElement(); + String source = task.getClassname().replace( '.', '/' ) + ".java"; + PatternSet.NameEntry include = super.createInclude(); + include.setName( "**/" + source ); + } + + // execute Javac + super.init(); + super.execute(); + + // now define all the new tasks + for( Enumeration e = taskList.elements(); e.hasMoreElements(); ) + { + Taskdef task = ( Taskdef )e.nextElement(); + task.init(); + } + + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ConditionTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ConditionTask.java new file mode 100644 index 000000000..da72ebc93 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ConditionTask.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.taskdefs.condition.ConditionBase; + +/** + * <condition> task as a generalization of <available> and + * <uptodate>

          + * + * This task supports boolean logic as well as pluggable conditions to decide, + * whether a property should be set.

          + * + * This task does not extend Task to take advantage of ConditionBase.

          + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ConditionTask extends ConditionBase +{ + private String value = "true"; + + private String property; + + /** + * The name of the property to set. Required. + * + * @param p The new Property value + * @since 1.1 + */ + public void setProperty( String p ) + { + property = p; + } + + /** + * The value for the property to set. Defaults to "true". + * + * @param v The new Value value + * @since 1.1 + */ + public void setValue( String v ) + { + value = v; + } + + /** + * See whether our nested condition holds and set the property. + * + * @exception BuildException Description of Exception + * @since 1.1 + */ + public void execute() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + Condition c = ( Condition )getConditions().nextElement(); + if( c.eval() ) + { + getProject().setNewProperty( property, value ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copy.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copy.java new file mode 100644 index 000000000..055c978fd --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copy.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.FlatFileNameMapper; +import org.apache.tools.ant.util.IdentityMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * A consolidated copy task. Copies a file or directory to a new file or + * directory. Files are only copied if the source file is newer than the + * destination file, or when the destination file does not exist. It is possible + * to explicitly overwrite existing files.

          + * + * This implementation is based on Arnout Kuiper's initial design document, the + * following mailing list discussions, and the copyfile/copydir tasks.

          + * + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Stefan Bodewig + * @author Michael McCallum + * @author Magesh Umasankar + */ +public class Copy extends Task +{ + protected File file = null;// the source file + protected File destFile = null;// the destination file + protected File destDir = null;// the destination directory + protected Vector filesets = new Vector(); + + protected boolean filtering = false; + protected boolean preserveLastModified = false; + protected boolean forceOverwrite = false; + protected boolean flatten = false; + protected int verbosity = Project.MSG_VERBOSE; + protected boolean includeEmpty = true; + + protected Hashtable fileCopyMap = new Hashtable(); + protected Hashtable dirCopyMap = new Hashtable(); + protected Hashtable completeDirMap = new Hashtable(); + + protected Mapper mapperElement = null; + private Vector filterSets = new Vector(); + private FileUtils fileUtils; + + public Copy() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Sets a single source file to copy. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Sets filtering. + * + * @param filtering The new Filtering value + */ + public void setFiltering( boolean filtering ) + { + this.filtering = filtering; + } + + /** + * When copying directory trees, the files can be "flattened" into a single + * directory. If there are multiple files with the same name in the source + * directory tree, only the first file will be copied into the "flattened" + * directory, unless the forceoverwrite attribute is true. + * + * @param flatten The new Flatten value + */ + public void setFlatten( boolean flatten ) + { + this.flatten = flatten; + } + + /** + * Used to copy empty directories. + * + * @param includeEmpty The new IncludeEmptyDirs value + */ + public void setIncludeEmptyDirs( boolean includeEmpty ) + { + this.includeEmpty = includeEmpty; + } + + /** + * Overwrite any existing destination file(s). + * + * @param overwrite The new Overwrite value + */ + public void setOverwrite( boolean overwrite ) + { + this.forceOverwrite = overwrite; + } + + /** + * Give the copied files the same last modified time as the original files. + * + * @param preserve The new PreserveLastModified value + * @deprecated setPreserveLastModified(String) has been deprecated and + * replaced with setPreserveLastModified(boolean) to consistently let + * the Introspection mechanism work. + */ + public void setPreserveLastModified( String preserve ) + { + setPreserveLastModified( Project.toBoolean( preserve ) ); + } + + /** + * Give the copied files the same last modified time as the original files. + * + * @param preserve The new PreserveLastModified value + */ + public void setPreserveLastModified( boolean preserve ) + { + preserveLastModified = preserve; + } + + /** + * Sets the destination directory. + * + * @param destDir The new Todir value + */ + public void setTodir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Sets the destination file. + * + * @param destFile The new Tofile value + */ + public void setTofile( File destFile ) + { + this.destFile = destFile; + } + + /** + * Used to force listing of all names of copied files. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + if( verbose ) + { + this.verbosity = Project.MSG_INFO; + } + else + { + this.verbosity = Project.MSG_VERBOSE; + } + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Create a nested filterset + * + * @return Description of the Returned Value + */ + public FilterSet createFilterSet() + { + FilterSet filterSet = new FilterSet(); + filterSets.addElement( filterSet ); + return filterSet; + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Performs the copy operation. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // make sure we don't have an illegal set of options + validateAttributes(); + + // deal with the single file + if( file != null ) + { + if( file.exists() ) + { + if( destFile == null ) + { + destFile = new File( destDir, file.getName() ); + } + + if( forceOverwrite || + ( file.lastModified() > destFile.lastModified() ) ) + { + fileCopyMap.put( file.getAbsolutePath(), destFile.getAbsolutePath() ); + } + else + { + log( file + " omitted as " + destFile + " is up to date.", + Project.MSG_VERBOSE ); + } + } + else + { + String message = "Could not find file " + + file.getAbsolutePath() + " to copy."; + log( message ); + throw new BuildException( message ); + } + } + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + String[] srcDirs = ds.getIncludedDirectories(); + boolean isEverythingIncluded = ds.isEverythingIncluded(); + if( isEverythingIncluded + && !flatten && mapperElement == null ) + { + completeDirMap.put( fromDir, destDir ); + } + scan( fromDir, destDir, srcFiles, srcDirs ); + } + + // do all the copy operations now... + doFileOperations(); + + // clean up destDir again - so this instance can be used a second + // time without throwing an exception + if( destFile != null ) + { + destDir = null; + } + } + + protected FileUtils getFileUtils() + { + return fileUtils; + } + + /** + * Get the filtersets being applied to this operation. + * + * @return a vector of FilterSet objects + */ + protected Vector getFilterSets() + { + return filterSets; + } + + protected void buildMap( File fromDir, File toDir, String[] names, + FileNameMapper mapper, Hashtable map ) + { + + String[] toCopy = null; + if( forceOverwrite ) + { + Vector v = new Vector(); + for( int i = 0; i < names.length; i++ ) + { + if( mapper.mapFileName( names[i] ) != null ) + { + v.addElement( names[i] ); + } + } + toCopy = new String[v.size()]; + v.copyInto( toCopy ); + } + else + { + SourceFileScanner ds = new SourceFileScanner( this ); + toCopy = ds.restrict( names, fromDir, toDir, mapper ); + } + + for( int i = 0; i < toCopy.length; i++ ) + { + File src = new File( fromDir, toCopy[i] ); + File dest = new File( toDir, mapper.mapFileName( toCopy[i] )[0] ); + map.put( src.getAbsolutePath(), dest.getAbsolutePath() ); + } + } + + /** + * Actually does the file (and possibly empty directory) copies. This is a + * good method for subclasses to override. + */ + protected void doFileOperations() + { + if( fileCopyMap.size() > 0 ) + { + log( "Copying " + fileCopyMap.size() + + " file" + ( fileCopyMap.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + + Enumeration e = fileCopyMap.keys(); + while( e.hasMoreElements() ) + { + String fromFile = ( String )e.nextElement(); + String toFile = ( String )fileCopyMap.get( fromFile ); + + if( fromFile.equals( toFile ) ) + { + log( "Skipping self-copy of " + fromFile, verbosity ); + continue; + } + + try + { + log( "Copying " + fromFile + " to " + toFile, verbosity ); + + FilterSetCollection executionFilters = new FilterSetCollection(); + if( filtering ) + { + executionFilters.addFilterSet( project.getGlobalFilterSet() ); + } + for( Enumeration filterEnum = filterSets.elements(); filterEnum.hasMoreElements(); ) + { + executionFilters.addFilterSet( ( FilterSet )filterEnum.nextElement() ); + } + fileUtils.copyFile( fromFile, toFile, executionFilters, + forceOverwrite, preserveLastModified ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + + if( includeEmpty ) + { + Enumeration e = dirCopyMap.elements(); + int count = 0; + while( e.hasMoreElements() ) + { + File d = new File( ( String )e.nextElement() ); + if( !d.exists() ) + { + if( !d.mkdirs() ) + { + log( "Unable to create directory " + d.getAbsolutePath(), Project.MSG_ERR ); + } + else + { + count++; + } + } + } + + if( count > 0 ) + { + log( "Copied " + count + + " empty director" + + ( count == 1 ? "y" : "ies" ) + + " to " + destDir.getAbsolutePath() ); + } + } + } + + /** + * Compares source files to destination files to see if they should be + * copied. + * + * @param fromDir Description of Parameter + * @param toDir Description of Parameter + * @param files Description of Parameter + * @param dirs Description of Parameter + */ + protected void scan( File fromDir, File toDir, String[] files, String[] dirs ) + { + FileNameMapper mapper = null; + if( mapperElement != null ) + { + mapper = mapperElement.getImplementation(); + } + else if( flatten ) + { + mapper = new FlatFileNameMapper(); + } + else + { + mapper = new IdentityMapper(); + } + + buildMap( fromDir, toDir, files, mapper, fileCopyMap ); + + if( includeEmpty ) + { + buildMap( fromDir, toDir, dirs, mapper, dirCopyMap ); + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + /** + * Ensure we have a consistent and legal set of attributes, and set any + * internal flags necessary based on different combinations of attributes. + * + * @exception BuildException Description of Exception + */ + protected void validateAttributes() + throws BuildException + { + if( file == null && filesets.size() == 0 ) + { + throw new BuildException( "Specify at least one source - a file or a fileset." ); + } + + if( destFile != null && destDir != null ) + { + throw new BuildException( "Only one of tofile and todir may be set." ); + } + + if( destFile == null && destDir == null ) + { + throw new BuildException( "One of tofile or todir must be set." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( "Use a fileset to copy directories." ); + } + + if( destFile != null && filesets.size() > 0 ) + { + if( filesets.size() > 1 ) + { + throw new BuildException( + "Cannot concatenate multiple files into a single file." ); + } + else + { + FileSet fs = ( FileSet )filesets.elementAt( 0 ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + + if( srcFiles.length > 0 ) + { + if( file == null ) + { + file = new File( srcFiles[0] ); + filesets.removeElementAt( 0 ); + } + else + { + throw new BuildException( + "Cannot concatenate multiple files into a single file." ); + } + } + else + { + throw new BuildException( + "Cannot perform operation from directory to file." ); + } + } + } + + if( destFile != null ) + { + destDir = new File( destFile.getParent() );// be 1.1 friendly + } + + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copydir.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copydir.java new file mode 100644 index 000000000..f539fffe2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copydir.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; + +/** + * Copies a directory. + * + * @author James Davidson duncan@x180.com + * @deprecated The copydir task is deprecated. Use copy instead. + */ + +public class Copydir extends MatchingTask +{ + private boolean filtering = false; + private boolean flatten = false; + private boolean forceOverwrite = false; + private Hashtable filecopyList = new Hashtable(); + private File destDir; + + private File srcDir; + + public void setDest( File dest ) + { + destDir = dest; + } + + public void setFiltering( boolean filter ) + { + filtering = filter; + } + + public void setFlatten( boolean flatten ) + { + this.flatten = flatten; + } + + public void setForceoverwrite( boolean force ) + { + forceOverwrite = force; + } + + public void setSrc( File src ) + { + srcDir = src; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The copydir task is deprecated. Use copy instead." ); + + if( srcDir == null ) + { + throw new BuildException( "src attribute must be set!", + location ); + } + + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir " + srcDir.toString() + + " does not exist!", location ); + } + + if( destDir == null ) + { + throw new BuildException( "The dest attribute must be set.", location ); + } + + if( srcDir.equals( destDir ) ) + { + log( "Warning: src == dest" ); + } + + DirectoryScanner ds = super.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + scanDir( srcDir, destDir, files ); + if( filecopyList.size() > 0 ) + { + log( "Copying " + filecopyList.size() + " file" + + ( filecopyList.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + Enumeration enum = filecopyList.keys(); + while( enum.hasMoreElements() ) + { + String fromFile = ( String )enum.nextElement(); + String toFile = ( String )filecopyList.get( fromFile ); + try + { + project.copyFile( fromFile, toFile, filtering, + forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + } + + private void scanDir( File from, File to, String[] files ) + { + for( int i = 0; i < files.length; i++ ) + { + String filename = files[i]; + File srcFile = new File( from, filename ); + File destFile; + if( flatten ) + { + destFile = new File( to, new File( filename ).getName() ); + } + else + { + destFile = new File( to, filename ); + } + if( forceOverwrite || + ( srcFile.lastModified() > destFile.lastModified() ) ) + { + filecopyList.put( srcFile.getAbsolutePath(), + destFile.getAbsolutePath() ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copyfile.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copyfile.java new file mode 100644 index 000000000..8d3398975 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copyfile.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Copies a file. + * + * @author duncan@x180.com + * @deprecated The copyfile task is deprecated. Use copy instead. + */ + +public class Copyfile extends Task +{ + private boolean filtering = false; + private boolean forceOverwrite = false; + private File destFile; + + private File srcFile; + + public void setDest( File dest ) + { + destFile = dest; + } + + public void setFiltering( String filter ) + { + filtering = Project.toBoolean( filter ); + } + + public void setForceoverwrite( boolean force ) + { + forceOverwrite = force; + } + + public void setSrc( File src ) + { + srcFile = src; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The copyfile task is deprecated. Use copy instead." ); + + if( srcFile == null ) + { + throw new BuildException( "The src attribute must be present.", location ); + } + + if( !srcFile.exists() ) + { + throw new BuildException( "src " + srcFile.toString() + + " does not exist.", location ); + } + + if( destFile == null ) + { + throw new BuildException( "The dest attribute must be present.", location ); + } + + if( srcFile.equals( destFile ) ) + { + log( "Warning: src == dest" ); + } + + if( forceOverwrite || srcFile.lastModified() > destFile.lastModified() ) + { + try + { + project.copyFile( srcFile, destFile, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Error copying file: " + srcFile.getAbsolutePath() + + " due to " + ioe.getMessage(); + throw new BuildException( msg ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Cvs.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Cvs.java new file mode 100644 index 000000000..63bda456c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Cvs.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Environment; + +/** + * @author costin@dnt.ro + * @author stefano@apache.org + * @author Wolfgang Werner + * wwerner@picturesafe.de + */ + +public class Cvs extends Task +{ + + private Commandline cmd = new Commandline(); + + /** + * the CVS command to execute. + */ + private String command = "checkout"; + + /** + * suppress information messages. + */ + private boolean quiet = false; + + /** + * report only, don't change any files. + */ + private boolean noexec = false; + + /** + * CVS port + */ + private int port = 0; + + /** + * CVS password file + */ + private File passFile = null; + + /** + * If true it will stop the build if cvs exits with error. Default is false. + * (Iulian) + */ + private boolean failOnError = false; + + /** + * the CVSROOT variable. + */ + private String cvsRoot; + + /** + * the CVS_RSH variable. + */ + private String cvsRsh; + + /** + * the directory where the checked out files should be placed. + */ + private File dest; + + /** + * the file to direct standard error from the command. + */ + private File error; + + /** + * the file to direct standard output from the command. + */ + private File output; + + /** + * the package/module to check out. + */ + private String pack; + + public void setCommand( String c ) + { + this.command = c; + } + + public void setCvsRoot( String root ) + { + // Check if not real cvsroot => set it to null + if( root != null ) + { + if( root.trim().equals( "" ) ) + root = null; + } + + this.cvsRoot = root; + } + + public void setCvsRsh( String rsh ) + { + // Check if not real cvsrsh => set it to null + if( rsh != null ) + { + if( rsh.trim().equals( "" ) ) + rsh = null; + } + + this.cvsRsh = rsh; + } + + + public void setDate( String p ) + { + if( p != null && p.trim().length() > 0 ) + { + cmd.createArgument().setValue( "-D" ); + cmd.createArgument().setValue( p ); + } + } + + public void setDest( File dest ) + { + this.dest = dest; + } + + public void setError( File error ) + { + this.error = error; + } + + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + public void setNoexec( boolean ne ) + { + noexec = ne; + } + + public void setOutput( File output ) + { + this.output = output; + } + + public void setPackage( String p ) + { + this.pack = p; + } + + public void setPassfile( File passFile ) + { + this.passFile = passFile; + } + + public void setPort( int port ) + { + this.port = port; + } + + public void setQuiet( boolean q ) + { + quiet = q; + } + + public void setTag( String p ) + { + // Check if not real tag => set it to null + if( p != null && p.trim().length() > 0 ) + { + cmd.createArgument().setValue( "-r" ); + cmd.createArgument().setValue( p ); + } + } + + + public void execute() + throws BuildException + { + + // XXX: we should use JCVS (www.ice.com/JCVS) instead of command line + // execution so that we don't rely on having native CVS stuff around (SM) + + // We can't do it ourselves as jCVS is GPLed, a third party task + // outside of jakarta repositories would be possible though (SB). + + Commandline toExecute = new Commandline(); + + toExecute.setExecutable( "cvs" ); + if( cvsRoot != null ) + { + toExecute.createArgument().setValue( "-d" ); + toExecute.createArgument().setValue( cvsRoot ); + } + if( noexec ) + { + toExecute.createArgument().setValue( "-n" ); + } + if( quiet ) + { + toExecute.createArgument().setValue( "-q" ); + } + toExecute.createArgument().setLine( command ); + toExecute.addArguments( cmd.getCommandline() ); + + if( pack != null ) + { + toExecute.createArgument().setLine( pack ); + } + + Environment env = new Environment(); + + if( port > 0 ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_CLIENT_PORT" ); + var.setValue( String.valueOf( port ) ); + env.addVariable( var ); + } + + if( passFile != null ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_PASSFILE" ); + var.setValue( String.valueOf( passFile ) ); + env.addVariable( var ); + } + + if( cvsRsh != null ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_RSH" ); + var.setValue( String.valueOf( cvsRsh ) ); + env.addVariable( var ); + } + + ExecuteStreamHandler streamhandler = null; + OutputStream outputstream = null; + OutputStream errorstream = null; + if( error == null && output == null ) + { + streamhandler = new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ); + } + else + { + if( output != null ) + { + try + { + outputstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + outputstream = new LogOutputStream( this, Project.MSG_INFO ); + } + if( error != null ) + { + try + { + errorstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( error ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + errorstream = new LogOutputStream( this, Project.MSG_WARN ); + } + streamhandler = new PumpStreamHandler( outputstream, errorstream ); + } + + Execute exe = new Execute( streamhandler, + null ); + + exe.setAntRun( project ); + if( dest == null ) + dest = project.getBaseDir(); + exe.setWorkingDirectory( dest ); + + exe.setCommandline( toExecute.getCommandline() ); + exe.setEnvironment( env.getVariables() ); + try + { + int retCode = exe.execute(); + /* + * Throw an exception if cvs exited with error. (Iulian) + */ + if( failOnError && retCode != 0 ) + throw new BuildException( "cvs exited with error code " + retCode ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + if( output != null ) + { + try + { + outputstream.close(); + } + catch( IOException e ) + {} + } + if( error != null ) + { + try + { + errorstream.close(); + } + catch( IOException e ) + {} + } + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Definer.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Definer.java new file mode 100644 index 000000000..0186ee465 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Definer.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Properties; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Base class for Taskdef and Typedef - does all the classpath handling and and + * class loading. + * + * @author Costin Manolache + * @author Stefan Bodewig + */ +public abstract class Definer extends Task +{ + private boolean reverseLoader = false; + private Path classpath; + private File file; + private String name; + private String resource; + private String value; + + public void setClassname( String v ) + { + value = v; + } + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setName( String name ) + { + this.name = name; + } + + public void setResource( String res ) + { + this.resource = res; + } + + public void setReverseLoader( boolean reverseLoader ) + { + this.reverseLoader = reverseLoader; + log( "The reverseloader attribute is DEPRECATED. It will be removed", Project.MSG_WARN ); + } + + public String getClassname() + { + return value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public void execute() + throws BuildException + { + AntClassLoader al = createLoader(); + + if( file == null && resource == null ) + { + + // simple case - one definition + if( name == null || value == null ) + { + String msg = "name or classname attributes of " + + getTaskName() + " element " + + "are undefined"; + throw new BuildException( msg ); + } + addDefinition( al, name, value ); + + } + else + { + + try + { + if( name != null || value != null ) + { + String msg = "You must not specify name or value " + + "together with file or resource."; + throw new BuildException( msg, location ); + } + + if( file != null && resource != null ) + { + String msg = "You must not specify both, file and resource."; + throw new BuildException( msg, location ); + } + + Properties props = new Properties(); + InputStream is = null; + if( file != null ) + { + log( "Loading definitions from file " + file, + Project.MSG_VERBOSE ); + is = new FileInputStream( file ); + if( is == null ) + { + log( "Could not load definitions from file " + file + + ". It doesn\'t exist.", Project.MSG_WARN ); + } + } + if( resource != null ) + { + log( "Loading definitions from resource " + resource, + Project.MSG_VERBOSE ); + is = al.getResourceAsStream( resource ); + if( is == null ) + { + log( "Could not load definitions from resource " + + resource + ". It could not be found.", + Project.MSG_WARN ); + } + } + + if( is != null ) + { + props.load( is ); + Enumeration keys = props.keys(); + while( keys.hasMoreElements() ) + { + String n = ( String )keys.nextElement(); + String v = props.getProperty( n ); + addDefinition( al, n, v ); + } + } + } + catch( IOException ex ) + { + throw new BuildException( ex); + } + } + } + + protected abstract void addDefinition( String name, Class c ); + + private void addDefinition( ClassLoader al, String name, String value ) + throws BuildException + { + try + { + Class c = al.loadClass( value ); + AntClassLoader.initializeClass( c ); + addDefinition( name, c ); + } + catch( ClassNotFoundException cnfe ) + { + String msg = getTaskName() + " class " + value + + " cannot be found"; + throw new BuildException( msg, cnfe, location ); + } + catch( NoClassDefFoundError ncdfe ) + { + String msg = getTaskName() + " class " + value + + " cannot be found"; + throw new BuildException( msg, ncdfe, location ); + } + } + + + private AntClassLoader createLoader() + { + AntClassLoader al = null; + if( classpath != null ) + { + al = new AntClassLoader( project, classpath, !reverseLoader ); + } + else + { + al = new AntClassLoader( project, Path.systemClasspath, !reverseLoader ); + } + // need to load Task via system classloader or the new + // task we want to define will never be a Task but always + // be wrapped into a TaskAdapter. + al.addSystemPackageRoot( "org.apache.tools.ant" ); + return al; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Delete.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Delete.java new file mode 100644 index 000000000..e7df0b543 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Delete.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + +/** + * Deletes a file or directory, or set of files defined by a fileset. The + * original delete task would delete a file, or a set of files using the + * include/exclude syntax. The deltree task would delete a directory tree. This + * task combines the functionality of these two originally distinct tasks.

          + * + * Currently Delete extends MatchingTask. This is intend only to provide + * backwards compatibility for a release. The future position is to use nested + * filesets exclusively.

          + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Tom Dimock tad1@cornell.edu + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Jon S. Stevens jon@latchkey.com + */ +public class Delete extends MatchingTask +{ + protected File file = null; + protected File dir = null; + protected Vector filesets = new Vector(); + protected boolean usedMatchingTask = false; + protected boolean includeEmpty = false;// by default, remove matching empty dirs + + private int verbosity = Project.MSG_VERBOSE; + private boolean quiet = false; + private boolean failonerror = true; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + usedMatchingTask = true; + super.setDefaultexcludes( useDefaultExcludes ); + } + + /** + * Set the directory from which files are to be deleted + * + * @param dir the directory path. + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + usedMatchingTask = true; + super.setExcludes( excludes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param excludesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setExcludesfile( File excludesfile ) + { + usedMatchingTask = true; + super.setExcludesfile( excludesfile ); + } + + /** + * this flag means 'note errors to the output, but keep going' + * + * @param failonerror true or false + */ + public void setFailOnError( boolean failonerror ) + { + this.failonerror = failonerror; + } + + /** + * Set the name of a single file to be removed. + * + * @param file the file to be deleted + */ + public void setFile( File file ) + { + this.file = file; + } + + + /** + * Used to delete empty directories. + * + * @param includeEmpty The new IncludeEmptyDirs value + */ + public void setIncludeEmptyDirs( boolean includeEmpty ) + { + this.includeEmpty = includeEmpty; + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + usedMatchingTask = true; + super.setIncludes( includes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param includesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setIncludesfile( File includesfile ) + { + usedMatchingTask = true; + super.setIncludesfile( includesfile ); + } + + /** + * If the file does not exist, do not display a diagnostic message or modify + * the exit status to reflect an error. This means that if a file or + * directory cannot be deleted, then no error is reported. This setting + * emulates the -f option to the Unix "rm" command. Default is + * false meaning things are "noisy" + * + * @param quiet "true" or "on" + */ + public void setQuiet( boolean quiet ) + { + this.quiet = quiet; + if( quiet ) + { + this.failonerror = false; + } + } + + /** + * Used to force listing of all names of deleted files. + * + * @param verbose "true" or "on" + */ + public void setVerbose( boolean verbose ) + { + if( verbose ) + { + this.verbosity = Project.MSG_INFO; + } + else + { + this.verbosity = Project.MSG_VERBOSE; + } + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + usedMatchingTask = true; + return super.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + usedMatchingTask = true; + return super.createInclude(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + usedMatchingTask = true; + return super.createPatternSet(); + } + + /** + * Delete the file(s). + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( usedMatchingTask ) + { + log( "DEPRECATED - Use of the implicit FileSet is deprecated. Use a nested fileset element instead." ); + } + + if( file == null && dir == null && filesets.size() == 0 ) + { + throw new BuildException( "At least one of the file or dir attributes, or a fileset element, must be set." ); + } + + if( quiet && failonerror ) + { + throw new BuildException( "quiet and failonerror cannot both be set to true", + location ); + } + + // delete the single file + if( file != null ) + { + if( file.exists() ) + { + if( file.isDirectory() ) + { + log( "Directory " + file.getAbsolutePath() + " cannot be removed using the file attribute. Use dir instead." ); + } + else + { + log( "Deleting: " + file.getAbsolutePath() ); + + if( !file.delete() ) + { + String message = "Unable to delete file " + file.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + else + { + log( "Could not find file " + file.getAbsolutePath() + " to delete.", + Project.MSG_VERBOSE ); + } + } + + // delete the directory + if( dir != null && dir.exists() && dir.isDirectory() && !usedMatchingTask ) + { + /* + * If verbosity is MSG_VERBOSE, that mean we are doing regular logging + * (backwards as that sounds). In that case, we want to print one + * message about deleting the top of the directory tree. Otherwise, + * the removeDir method will handle messages for _all_ directories. + */ + if( verbosity == Project.MSG_VERBOSE ) + { + log( "Deleting directory " + dir.getAbsolutePath() ); + } + removeDir( dir ); + } + + // delete the files in the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + try + { + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] files = ds.getIncludedFiles(); + String[] dirs = ds.getIncludedDirectories(); + removeFiles( fs.getDir( project ), files, dirs ); + } + catch( BuildException be ) + { + // directory doesn't exist or is not readable + if( failonerror ) + { + throw be; + } + else + { + log( be.getMessage(), + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + + // delete the files from the default fileset + if( usedMatchingTask && dir != null ) + { + try + { + DirectoryScanner ds = super.getDirectoryScanner( dir ); + String[] files = ds.getIncludedFiles(); + String[] dirs = ds.getIncludedDirectories(); + removeFiles( dir, files, dirs ); + } + catch( BuildException be ) + { + // directory doesn't exist or is not readable + if( failonerror ) + { + throw be; + } + else + { + log( be.getMessage(), + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + protected void removeDir( File d ) + { + String[] list = d.list(); + if( list == null ) + list = new String[0]; + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + removeDir( f ); + } + else + { + log( "Deleting " + f.getAbsolutePath(), verbosity ); + if( !f.delete() ) + { + String message = "Unable to delete file " + f.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + log( "Deleting directory " + d.getAbsolutePath(), verbosity ); + if( !d.delete() ) + { + String message = "Unable to delete directory " + dir.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + + /** + * remove an array of files in a directory, and a list of subdirectories + * which will only be deleted if 'includeEmpty' is true + * + * @param d directory to work from + * @param files array of files to delete; can be of zero length + * @param dirs array of directories to delete; can of zero length + */ + protected void removeFiles( File d, String[] files, String[] dirs ) + { + if( files.length > 0 ) + { + log( "Deleting " + files.length + " files from " + d.getAbsolutePath() ); + for( int j = 0; j < files.length; j++ ) + { + File f = new File( d, files[j] ); + log( "Deleting " + f.getAbsolutePath(), verbosity ); + if( !f.delete() ) + { + String message = "Unable to delete file " + f.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + + if( dirs.length > 0 && includeEmpty ) + { + int dirCount = 0; + for( int j = dirs.length - 1; j >= 0; j-- ) + { + File dir = new File( d, dirs[j] ); + String[] dirFiles = dir.list(); + if( dirFiles == null || dirFiles.length == 0 ) + { + log( "Deleting " + dir.getAbsolutePath(), verbosity ); + if( !dir.delete() ) + { + String message = "Unable to delete directory " + + dir.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + else + { + dirCount++; + } + } + } + + if( dirCount > 0 ) + { + log( "Deleted " + dirCount + " director" + + ( dirCount == 1 ? "y" : "ies" ) + + " from " + d.getAbsolutePath() ); + } + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Deltree.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Deltree.java new file mode 100644 index 000000000..f6e746e7b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Deltree.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * @author duncan@x180.com + * @deprecated The deltree task is deprecated. Use delete instead. + */ + +public class Deltree extends Task +{ + + private File dir; + + public void setDir( File dir ) + { + this.dir = dir; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The deltree task is deprecated. Use delete instead." ); + + if( dir == null ) + { + throw new BuildException( "dir attribute must be set!", location ); + } + + if( dir.exists() ) + { + if( !dir.isDirectory() ) + { + if( !dir.delete() ) + { + throw new BuildException( "Unable to delete directory " + + dir.getAbsolutePath(), + location ); + } + return; + // String msg = "Given dir: " + dir.getAbsolutePath() + + // " is not a dir"; + // throw new BuildException(msg); + } + + log( "Deleting: " + dir.getAbsolutePath() ); + + try + { + removeDir( dir ); + } + catch( IOException ioe ) + { + String msg = "Unable to delete " + dir.getAbsolutePath(); + throw new BuildException( msg, location ); + } + } + } + + private void removeDir( File dir ) + throws IOException + { + + // check to make sure that the given dir isn't a symlink + // the comparison of absolute path and canonical path + // catches this + + // if (dir.getCanonicalPath().equals(dir.getAbsolutePath())) { + // (costin) It will not work if /home/costin is symlink to /da0/home/costin ( taz + // for example ) + String[] list = dir.list(); + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( dir, s ); + if( f.isDirectory() ) + { + removeDir( f ); + } + else + { + if( !f.delete() ) + { + throw new BuildException( "Unable to delete file " + f.getAbsolutePath() ); + } + } + } + if( !dir.delete() ) + { + throw new BuildException( "Unable to delete directory " + dir.getAbsolutePath() ); + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/DependSet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/DependSet.java new file mode 100644 index 000000000..0768f88e9 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/DependSet.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Date; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.FileList; +import org.apache.tools.ant.types.FileSet; + +/** + * A Task to record explicit dependencies. If any of the target files are out of + * date with respect to any of the source files, all target files are removed. + * This is useful where dependencies cannot be computed (for example, + * dynamically interpreted parameters or files that need to stay in synch but + * are not directly linked) or where the ant task in question could compute them + * but does not (for example, the linked DTD for an XML file using the style + * task). nested arguments: + *
            + *
          • srcfileset (fileset describing the source files to examine) + *
          • srcfilelist (filelist describing the source files to examine) + *
          • targetfileset (fileset describing the target files to examine) + *
          • targetfilelist (filelist describing the target files to examine) + *
          + * At least one instance of either a fileset or filelist for both source and + * target are required.

          + * + * This task will examine each of the source files against each of the target + * files. If any target files are out of date with respect to any of the source + * files, all targets are removed. If any files named in a (src or target) + * filelist do not exist, all targets are removed. Hint: If missing files should + * be ignored, specify them as include patterns in filesets, rather than using + * filelists.

          + * + * This task attempts to optimize speed of dependency checking. It will stop + * after the first out of date file is found and remove all targets, rather than + * exhaustively checking every source vs target combination unnecessarily.

          + *

          + * + * Example uses: + *

            + *
          • Record the fact that an XML file must be up to date with respect to + * its XSD (Schema file), even though the XML file itself includes no + * reference to its XSD.
          • + *
          • Record the fact that an XSL stylesheet includes other sub-stylesheets + *
          • + *
          • Record the fact that java files must be recompiled if the ant build + * file changes
          • + *
          + * + * + * @author Craeg Strong + * @version $Revision$ $Date$ + */ +public class DependSet extends MatchingTask +{ + + private Vector sourceFileSets = new Vector(); + private Vector sourceFileLists = new Vector(); + private Vector targetFileSets = new Vector(); + private Vector targetFileLists = new Vector(); + + /** + * Creates a new DependSet Task. + */ + public DependSet() { } + + /** + * Nested <srcfilelist> element. + * + * @param fl The feature to be added to the Srcfilelist attribute + */ + public void addSrcfilelist( FileList fl ) + { + sourceFileLists.addElement( fl ); + }//-- DependSet + + /** + * Nested <srcfileset> element. + * + * @param fs The feature to be added to the Srcfileset attribute + */ + public void addSrcfileset( FileSet fs ) + { + sourceFileSets.addElement( fs ); + } + + /** + * Nested <targetfilelist> element. + * + * @param fl The feature to be added to the Targetfilelist attribute + */ + public void addTargetfilelist( FileList fl ) + { + targetFileLists.addElement( fl ); + } + + /** + * Nested <targetfileset> element. + * + * @param fs The feature to be added to the Targetfileset attribute + */ + public void addTargetfileset( FileSet fs ) + { + targetFileSets.addElement( fs ); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + + public void execute() + throws BuildException + { + + if( ( sourceFileSets.size() == 0 ) && ( sourceFileLists.size() == 0 ) ) + { + throw new BuildException( "At least one or element must be set" ); + } + + if( ( targetFileSets.size() == 0 ) && ( targetFileLists.size() == 0 ) ) + { + throw new BuildException( "At least one or element must be set" ); + } + + long now = ( new Date() ).getTime(); + /* + * If we're on Windows, we have to munge the time up to 2 secs to + * be able to check file modification times. + * (Windows has a max resolution of two secs for modification times) + */ + if( Os.isFamily( "windows" ) ) + { + now += 2000; + } + + // + // Grab all the target files specified via filesets + // + Vector allTargets = new Vector(); + Enumeration enumTargetSets = targetFileSets.elements(); + while( enumTargetSets.hasMoreElements() ) + { + + FileSet targetFS = ( FileSet )enumTargetSets.nextElement(); + DirectoryScanner targetDS = targetFS.getDirectoryScanner( project ); + String[] targetFiles = targetDS.getIncludedFiles(); + + for( int i = 0; i < targetFiles.length; i++ ) + { + + File dest = new File( targetFS.getDir( project ), targetFiles[i] ); + allTargets.addElement( dest ); + + if( dest.lastModified() > now ) + { + log( "Warning: " + targetFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + } + } + + // + // Grab all the target files specified via filelists + // + boolean upToDate = true; + Enumeration enumTargetLists = targetFileLists.elements(); + while( enumTargetLists.hasMoreElements() ) + { + + FileList targetFL = ( FileList )enumTargetLists.nextElement(); + String[] targetFiles = targetFL.getFiles( project ); + + for( int i = 0; i < targetFiles.length; i++ ) + { + + File dest = new File( targetFL.getDir( project ), targetFiles[i] ); + if( !dest.exists() ) + { + log( targetFiles[i] + " does not exist.", Project.MSG_VERBOSE ); + upToDate = false; + continue; + } + else + { + allTargets.addElement( dest ); + } + if( dest.lastModified() > now ) + { + log( "Warning: " + targetFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + } + } + + // + // Check targets vs source files specified via filesets + // + if( upToDate ) + { + Enumeration enumSourceSets = sourceFileSets.elements(); + while( upToDate && enumSourceSets.hasMoreElements() ) + { + + FileSet sourceFS = ( FileSet )enumSourceSets.nextElement(); + DirectoryScanner sourceDS = sourceFS.getDirectoryScanner( project ); + String[] sourceFiles = sourceDS.getIncludedFiles(); + + for( int i = 0; upToDate && i < sourceFiles.length; i++ ) + { + File src = new File( sourceFS.getDir( project ), sourceFiles[i] ); + + if( src.lastModified() > now ) + { + log( "Warning: " + sourceFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + + Enumeration enumTargets = allTargets.elements(); + while( upToDate && enumTargets.hasMoreElements() ) + { + + File dest = ( File )enumTargets.nextElement(); + if( src.lastModified() > dest.lastModified() ) + { + log( dest.getPath() + " is out of date with respect to " + + sourceFiles[i], Project.MSG_VERBOSE ); + upToDate = false; + + } + } + } + } + } + + // + // Check targets vs source files specified via filelists + // + if( upToDate ) + { + Enumeration enumSourceLists = sourceFileLists.elements(); + while( upToDate && enumSourceLists.hasMoreElements() ) + { + + FileList sourceFL = ( FileList )enumSourceLists.nextElement(); + String[] sourceFiles = sourceFL.getFiles( project ); + + int i = 0; + do + { + File src = new File( sourceFL.getDir( project ), sourceFiles[i] ); + + if( src.lastModified() > now ) + { + log( "Warning: " + sourceFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + + if( !src.exists() ) + { + log( sourceFiles[i] + " does not exist.", Project.MSG_VERBOSE ); + upToDate = false; + break; + } + + Enumeration enumTargets = allTargets.elements(); + while( upToDate && enumTargets.hasMoreElements() ) + { + + File dest = ( File )enumTargets.nextElement(); + + if( src.lastModified() > dest.lastModified() ) + { + log( dest.getPath() + " is out of date with respect to " + + sourceFiles[i], Project.MSG_VERBOSE ); + upToDate = false; + + } + } + }while ( upToDate && ( ++i < sourceFiles.length ) ); + } + } + + if( !upToDate ) + { + log( "Deleting all target files. ", Project.MSG_VERBOSE ); + for( Enumeration e = allTargets.elements(); e.hasMoreElements(); ) + { + File fileToRemove = ( File )e.nextElement(); + log( "Deleting file " + fileToRemove.getAbsolutePath(), Project.MSG_VERBOSE ); + fileToRemove.delete(); + } + } + + }//-- execute + +}//-- DependSet.java diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ear.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ear.java new file mode 100644 index 000000000..955cbb6ca --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ear.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + + +/** + * Creates a EAR archive. Based on WAR task + * + * @author Stefan Bodewig + * @author Les Hughes + */ +public class Ear extends Jar +{ + + private File deploymentDescriptor; + private boolean descriptorAdded; + + public Ear() + { + super(); + archiveType = "ear"; + emptyBehavior = "create"; + } + + public void setAppxml( File descr ) + { + deploymentDescriptor = descr; + if( !deploymentDescriptor.exists() ) + throw new BuildException( "Deployment descriptor: " + deploymentDescriptor + " does not exist." ); + + // Create a ZipFileSet for this file, and pass it up. + ZipFileSet fs = new ZipFileSet(); + fs.setDir( new File( deploymentDescriptor.getParent() ) ); + fs.setIncludes( deploymentDescriptor.getName() ); + fs.setFullpath( "META-INF/application.xml" ); + super.addFileset( fs ); + } + + public void setEarfile( File earFile ) + { + log( "DEPRECATED - The earfile attribute is deprecated. Use file attribute instead." ); + setFile( earFile ); + } + + + public void addArchives( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + // Do we need to do this? LH + log( "addArchives called", Project.MSG_DEBUG ); + fs.setPrefix( "/" ); + super.addFileset( fs ); + } + + /** + * Make sure we don't think we already have a web.xml next time this task + * gets executed. + */ + protected void cleanUp() + { + descriptorAdded = false; + super.cleanUp(); + } + + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + // If no webxml file is specified, it's an error. + if( deploymentDescriptor == null && !isInUpdateMode() ) + { + throw new BuildException( "appxml attribute is required", location ); + } + + super.initZipOutputStream( zOut ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is WEB-INF/web.xml, we warn if it's not the + // one specified in the "webxml" attribute - or if it's being added twice, + // meaning the same file is specified by the "webxml" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "META-INF/aplication.xml" ) ) + { + if( deploymentDescriptor == null || !deploymentDescriptor.equals( file ) || descriptorAdded ) + { + log( "Warning: selected " + archiveType + " files include a META-INF/application.xml which will be ignored " + + "(please use appxml attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + descriptorAdded = true; + } + } + else + { + super.zipFile( file, zOut, vPath ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Echo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Echo.java new file mode 100644 index 000000000..1e57b259a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Echo.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Echo + * + * @author costin@dnt.ro + */ +public class Echo extends Task +{ + protected String message = "";// required + protected File file = null; + protected boolean append = false; + + // by default, messages are always displayed + protected int logLevel = Project.MSG_WARN; + + /** + * Shall we append to an existing file? + * + * @param append The new Append value + */ + public void setAppend( boolean append ) + { + this.append = append; + } + + /** + * Sets the file attribute. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Set the logging level to one of + *
            + *
          • error
          • + *
          • warning
          • + *
          • info
          • + *
          • verbose
          • + *
          • debug
          • + *

              + * + * The default is "warning" to ensure that messages are + * displayed by default when using the -quiet command line option.

              + * + * @param echoLevel The new Level value + */ + public void setLevel( EchoLevel echoLevel ) + { + String option = echoLevel.getValue(); + if( option.equals( "error" ) ) + { + logLevel = Project.MSG_ERR; + } + else if( option.equals( "warning" ) ) + { + logLevel = Project.MSG_WARN; + } + else if( option.equals( "info" ) ) + { + logLevel = Project.MSG_INFO; + } + else if( option.equals( "verbose" ) ) + { + logLevel = Project.MSG_VERBOSE; + } + else + { + // must be "debug" + logLevel = Project.MSG_DEBUG; + } + } + + /** + * Sets the message variable. + * + * @param msg Sets the value for the message variable. + */ + public void setMessage( String msg ) + { + this.message = msg; + } + + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + /** + * Does the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( file == null ) + { + log( message, logLevel ); + } + else + { + FileWriter out = null; + try + { + out = new FileWriter( file.getAbsolutePath(), append ); + out.write( message, 0, message.length() ); + } + catch( IOException ioe ) + { + throw new BuildException( ioe); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + } + } + } + + public static class EchoLevel extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"error", "warning", "info", "verbose", "debug"}; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exec.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exec.java new file mode 100644 index 000000000..076de6fde --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exec.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Executes a given command if the os platform is appropriate. + * + * @author duncan@x180.com + * @author rubys@us.ibm.com + * @deprecated Instead of using this class, please extend ExecTask or delegate + * to Execute. + */ +public class Exec extends Task +{ + + private final static int BUFFER_SIZE = 512; + protected PrintWriter fos = null; + private boolean failOnError = false; + private String command; + private File dir; + private String os; + private String out; + + public void setCommand( String command ) + { + this.command = command; + } + + public void setDir( String d ) + { + this.dir = project.resolveFile( d ); + } + + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + public void setOs( String os ) + { + this.os = os; + } + + public void setOutput( String out ) + { + this.out = out; + } + + public void execute() + throws BuildException + { + run( command ); + } + + protected void logFlush() + { + if( fos != null ) + fos.close(); + } + + protected void outputLog( String line, int messageLevel ) + { + if( fos == null ) + { + log( line, messageLevel ); + } + else + { + fos.println( line ); + } + } + + protected int run( String command ) + throws BuildException + { + + int err = -1;// assume the worst + + // test if os match + String myos = System.getProperty( "os.name" ); + log( "Myos = " + myos, Project.MSG_VERBOSE ); + if( ( os != null ) && ( os.indexOf( myos ) < 0 ) ) + { + // this command will be executed only on the specified OS + log( "Not found in " + os, Project.MSG_VERBOSE ); + return 0; + } + + // default directory to the project's base directory + if( dir == null ) + dir = project.getBaseDir(); + + if( myos.toLowerCase().indexOf( "windows" ) >= 0 ) + { + if( !dir.equals( project.resolveFile( "." ) ) ) + { + if( myos.toLowerCase().indexOf( "nt" ) >= 0 ) + { + command = "cmd /c cd " + dir + " && " + command; + } + else + { + String ant = project.getProperty( "ant.home" ); + if( ant == null ) + { + throw new BuildException( "Property 'ant.home' not found", location ); + } + + String antRun = project.resolveFile( ant + "/bin/antRun.bat" ).toString(); + command = antRun + " " + dir + " " + command; + } + } + } + else + { + String ant = project.getProperty( "ant.home" ); + if( ant == null ) + throw new BuildException( "Property 'ant.home' not found", location ); + String antRun = project.resolveFile( ant + "/bin/antRun" ).toString(); + + command = antRun + " " + dir + " " + command; + } + + try + { + // show the command + log( command, Project.MSG_VERBOSE ); + + // exec command on system runtime + Process proc = Runtime.getRuntime().exec( command ); + + if( out != null ) + { + fos = new PrintWriter( new FileWriter( out ) ); + log( "Output redirected to " + out, Project.MSG_VERBOSE ); + } + + // copy input and error to the output stream + StreamPumper inputPumper = + new StreamPumper( proc.getInputStream(), Project.MSG_INFO, this ); + StreamPumper errorPumper = + new StreamPumper( proc.getErrorStream(), Project.MSG_WARN, this ); + + // starts pumping away the generated output/error + inputPumper.start(); + errorPumper.start(); + + // Wait for everything to finish + proc.waitFor(); + inputPumper.join(); + errorPumper.join(); + proc.destroy(); + + // close the output file if required + logFlush(); + + // check its exit value + err = proc.exitValue(); + if( err != 0 ) + { + if( failOnError ) + { + throw new BuildException( "Exec returned: " + err, location ); + } + else + { + log( "Result: " + err, Project.MSG_ERR ); + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Error exec: " + command, ioe, location ); + } + catch( InterruptedException ex ) + {} + + return err; + } + + // Inner class for continually pumping the input stream during + // Process's runtime. + class StreamPumper extends Thread + { + private boolean endOfStream = false; + private int SLEEP_TIME = 5; + private BufferedReader din; + private int messageLevel; + private Exec parent; + + public StreamPumper( InputStream is, int messageLevel, Exec parent ) + { + this.din = new BufferedReader( new InputStreamReader( is ) ); + this.messageLevel = messageLevel; + this.parent = parent; + } + + public void pumpStream() + throws IOException + { + byte[] buf = new byte[BUFFER_SIZE]; + if( !endOfStream ) + { + String line = din.readLine(); + + if( line != null ) + { + outputLog( line, messageLevel ); + } + else + { + endOfStream = true; + } + } + } + + public void run() + { + try + { + try + { + while( !endOfStream ) + { + pumpStream(); + sleep( SLEEP_TIME ); + } + } + catch( InterruptedException ie ) + {} + din.close(); + } + catch( IOException ioe ) + {} + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecTask.java new file mode 100644 index 000000000..f236d8eae --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecTask.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringReader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Environment; + +/** + * Executes a given command if the os platform is appropriate. + * + * @author duncan@x180.com + * @author rubys@us.ibm.com + * @author thomas.haas@softwired-inc.com + * @author Stefan Bodewig + * @author Mariusz Nowostawski + */ +public class ExecTask extends Task +{ + + private static String lSep = System.getProperty( "line.separator" ); + protected boolean failOnError = false; + protected boolean newEnvironment = false; + private Integer timeout = null; + private Environment env = new Environment(); + protected Commandline cmdl = new Commandline(); + private FileOutputStream fos = null; + private ByteArrayOutputStream baos = null; + private boolean failIfExecFails = true; + + /** + * Controls whether the VM (1.3 and above) is used to execute the command + */ + private boolean vmLauncher = true; + private File dir; + + private String os; + private File out; + private String outputprop; + private String resultProperty; + + /** + * The full commandline to execute, executable + arguments. + * + * @param cmdl The new Command value + */ + public void setCommand( Commandline cmdl ) + { + log( "The command attribute is deprecated. " + + "Please use the executable attribute and nested arg elements.", + Project.MSG_WARN ); + this.cmdl = cmdl; + } + + /** + * The working directory of the process + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * The command to execute. + * + * @param value The new Executable value + */ + public void setExecutable( String value ) + { + cmdl.setExecutable( value ); + } + + /** + * ant attribute + * + * @param flag The new FailIfExecutionFails value + */ + public void setFailIfExecutionFails( boolean flag ) + { + failIfExecFails = flag; + } + + /** + * Throw a BuildException if process returns non 0. + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Use a completely new environment + * + * @param newenv The new Newenvironment value + */ + public void setNewenvironment( boolean newenv ) + { + newEnvironment = newenv; + } + + /** + * Only execute the process if os.name is included in this + * string. + * + * @param os The new Os value + */ + public void setOs( String os ) + { + this.os = os; + } + + /** + * File the output of the process is redirected to. + * + * @param out The new Output value + */ + public void setOutput( File out ) + { + this.out = out; + } + + /** + * Property name whose value should be set to the output of the process + * + * @param outputprop The new Outputproperty value + */ + public void setOutputproperty( String outputprop ) + { + this.outputprop = outputprop; + } + + /** + * fill a property in with a result. when no property is defined: failure to + * execute + * + * @param resultProperty The new ResultProperty value + * @since 1.5 + */ + public void setResultProperty( String resultProperty ) + { + this.resultProperty = resultProperty; + } + + /** + * Timeout in milliseconds after which the process will be killed. + * + * @param value The new Timeout value + */ + public void setTimeout( Integer value ) + { + timeout = value; + } + + /** + * Control whether the VM is used to launch the new process or whether the + * OS's shell is used. + * + * @param vmLauncher The new VMLauncher value + */ + public void setVMLauncher( boolean vmLauncher ) + { + this.vmLauncher = vmLauncher; + } + + /** + * Add a nested env element - an environment variable. + * + * @param var The feature to be added to the Env attribute + */ + public void addEnv( Environment.Variable var ) + { + env.addVariable( var ); + } + + /** + * Add a nested arg element - a command line argument. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createArg() + { + return cmdl.createArgument(); + } + + /** + * Do the work. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + checkConfiguration(); + if( isValidOs() ) + { + runExec( prepareExec() ); + } + } + + /** + * Is this the OS the user wanted? + * + * @return The ValidOs value + */ + protected boolean isValidOs() + { + // test if os match + String myos = System.getProperty( "os.name" ); + log( "Current OS is " + myos, Project.MSG_VERBOSE ); + if( ( os != null ) && ( os.indexOf( myos ) < 0 ) ) + { + // this command will be executed only on the specified OS + log( "This OS, " + myos + " was not found in the specified list of valid OSes: " + os, Project.MSG_VERBOSE ); + return false; + } + return true; + } + + /** + * A Utility method for this classes and subclasses to run an Execute + * instance (an external command). + * + * @param exe Description of Parameter + * @exception IOException Description of Exception + */ + protected final void runExecute( Execute exe ) + throws IOException + { + int err = -1;// assume the worst + + err = exe.execute(); + //test for and handle a forced process death + if( exe.killedProcess() ) + { + log( "Timeout: killed the sub-process", Project.MSG_WARN ); + } + maybeSetResultPropertyValue( err ); + if( err != 0 ) + { + if( failOnError ) + { + throw new BuildException( taskType + " returned: " + err, location ); + } + else + { + log( "Result: " + err, Project.MSG_ERR ); + } + } + if( baos != null ) + { + BufferedReader in = + new BufferedReader( new StringReader( baos.toString() ) ); + String line = null; + StringBuffer val = new StringBuffer(); + while( ( line = in.readLine() ) != null ) + { + if( val.length() != 0 ) + { + val.append( lSep ); + } + val.append( line ); + } + project.setNewProperty( outputprop, val.toString() ); + } + } + + /** + * Has the user set all necessary attributes? + * + * @exception BuildException Description of Exception + */ + protected void checkConfiguration() + throws BuildException + { + if( cmdl.getExecutable() == null ) + { + throw new BuildException( "no executable specified", location ); + } + if( dir != null && !dir.exists() ) + { + throw new BuildException( "The directory you specified does not exist" ); + } + if( dir != null && !dir.isDirectory() ) + { + throw new BuildException( "The directory you specified is not a directory" ); + } + } + + /** + * Create the StreamHandler to use with our Execute instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecuteStreamHandler createHandler() + throws BuildException + { + if( out != null ) + { + try + { + fos = new FileOutputStream( out ); + log( "Output redirected to " + out, Project.MSG_VERBOSE ); + return new PumpStreamHandler( fos ); + } + catch( FileNotFoundException fne ) + { + throw new BuildException( "Cannot write to " + out, fne, location ); + } + catch( IOException ioe ) + { + throw new BuildException( "Cannot write to " + out, ioe, location ); + } + } + else if( outputprop != null ) + { + baos = new ByteArrayOutputStream(); + log( "Output redirected to ByteArray", Project.MSG_VERBOSE ); + return new PumpStreamHandler( baos ); + } + else + { + return new LogStreamHandler( this, + Project.MSG_INFO, Project.MSG_WARN ); + } + } + + /** + * Create the Watchdog to kill a runaway process. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + if( timeout == null ) + return null; + return new ExecuteWatchdog( timeout.intValue() ); + } + + /** + * Flush the output stream - if there is one. + */ + protected void logFlush() + { + try + { + if( fos != null ) + fos.close(); + if( baos != null ) + baos.close(); + } + catch( IOException io ) + {} + } + + /** + * helper method to set result property to the passed in value if + * appropriate + * + * @param result Description of Parameter + */ + protected void maybeSetResultPropertyValue( int result ) + { + String res = Integer.toString( result ); + if( resultProperty != null ) + { + project.setNewProperty( resultProperty, res ); + } + } + + /** + * Create an Execute instance with the correct working directory set. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected Execute prepareExec() + throws BuildException + { + // default directory to the project's base directory + if( dir == null ) + dir = project.getBaseDir(); + // show the command + log( cmdl.toString(), Project.MSG_VERBOSE ); + + Execute exe = new Execute( createHandler(), createWatchdog() ); + exe.setAntRun( project ); + exe.setWorkingDirectory( dir ); + exe.setVMLauncher( vmLauncher ); + String[] environment = env.getVariables(); + if( environment != null ) + { + for( int i = 0; i < environment.length; i++ ) + { + log( "Setting environment variable: " + environment[i], + Project.MSG_VERBOSE ); + } + } + exe.setNewenvironment( newEnvironment ); + exe.setEnvironment( environment ); + return exe; + } + + /** + * Run the command using the given Execute instance. This may be overidden + * by subclasses + * + * @param exe Description of Parameter + * @exception BuildException Description of Exception + */ + protected void runExec( Execute exe ) + throws BuildException + { + exe.setCommandline( cmdl.getCommandline() ); + try + { + runExecute( exe ); + } + catch( IOException e ) + { + if( failIfExecFails ) + { + throw new BuildException( "Execute failed: " + e.toString(), e, location ); + } + else + { + log( "Execute failed: " + e.toString(), Project.MSG_ERR ); + } + } + finally + { + // close the output file if required + logFlush(); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Execute.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Execute.java new file mode 100644 index 000000000..c1fdd0c72 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Execute.java @@ -0,0 +1,947 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Locale; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; + +/** + * Runs an external program. + * + * @author thomas.haas@softwired-inc.com + */ +public class Execute +{ + /** + * Invalid exit code. + */ + public final static int INVALID = Integer.MAX_VALUE; + + private static String antWorkingDirectory = System.getProperty( "user.dir" ); + private static CommandLauncher vmLauncher; + private static CommandLauncher shellLauncher; + private static Vector procEnvironment; + + /** + * Used to destroy processes when the VM exits. + */ + private static ProcessDestroyer processDestroyer = new ProcessDestroyer(); + + private String[] cmdl = null; + private String[] env = null; + private int exitValue = INVALID; + private File workingDirectory = null; + private Project project = null; + private boolean newEnvironment = false; + + /** + * Controls whether the VM is used to launch commands, where possible + */ + private boolean useVMLauncher = true; + private ExecuteStreamHandler streamHandler; + private ExecuteWatchdog watchdog; + + /** + * Builds a command launcher for the OS and JVM we are running under + */ + static + { + // Try using a JDK 1.3 launcher + try + { + vmLauncher = new Java13CommandLauncher(); + } + catch( NoSuchMethodException exc ) + { + // Ignore and keep try + } + + if( Os.isFamily( "mac" ) ) + { + // Mac + shellLauncher = new MacCommandLauncher( new CommandLauncher() ); + } + else if( Os.isFamily( "os/2" ) ) + { + // OS/2 - use same mechanism as Windows 2000 + shellLauncher = new WinNTCommandLauncher( new CommandLauncher() ); + } + else if( Os.isFamily( "windows" ) ) + { + // Windows. Need to determine which JDK we're running in + + CommandLauncher baseLauncher; + if( System.getProperty( "java.version" ).startsWith( "1.1" ) ) + { + // JDK 1.1 + baseLauncher = new Java11CommandLauncher(); + } + else + { + // JDK 1.2 + baseLauncher = new CommandLauncher(); + } + + // Determine if we're running under 2000/NT or 98/95 + String osname = + System.getProperty( "os.name" ).toLowerCase( Locale.US ); + + if( osname.indexOf( "nt" ) >= 0 || osname.indexOf( "2000" ) >= 0 ) + { + // Windows 2000/NT + shellLauncher = new WinNTCommandLauncher( baseLauncher ); + } + else + { + // Windows 98/95 - need to use an auxiliary script + shellLauncher = new ScriptCommandLauncher( "bin/antRun.bat", baseLauncher ); + } + } + else if( ( new Os( "netware" ) ).eval() ) + { + // NetWare. Need to determine which JDK we're running in + CommandLauncher baseLauncher; + if( System.getProperty( "java.version" ).startsWith( "1.1" ) ) + { + // JDK 1.1 + baseLauncher = new Java11CommandLauncher(); + } + else + { + // JDK 1.2 + baseLauncher = new CommandLauncher(); + } + + shellLauncher = new PerlScriptCommandLauncher( "bin/antRun.pl", baseLauncher ); + } + else + { + // Generic + shellLauncher = new ScriptCommandLauncher( "bin/antRun", new CommandLauncher() ); + } + } + + /** + * Creates a new execute object using PumpStreamHandler for + * stream handling. + */ + public Execute() + { + this( new PumpStreamHandler(), null ); + } + + /** + * Creates a new execute object. + * + * @param streamHandler the stream handler used to handle the input and + * output streams of the subprocess. + */ + public Execute( ExecuteStreamHandler streamHandler ) + { + this( streamHandler, null ); + } + + /** + * Creates a new execute object. + * + * @param streamHandler the stream handler used to handle the input and + * output streams of the subprocess. + * @param watchdog a watchdog for the subprocess or null to to + * disable a timeout for the subprocess. + */ + public Execute( ExecuteStreamHandler streamHandler, ExecuteWatchdog watchdog ) + { + this.streamHandler = streamHandler; + this.watchdog = watchdog; + } + + /** + * Find the list of environment variables for this process. + * + * @return The ProcEnvironment value + */ + public static synchronized Vector getProcEnvironment() + { + if( procEnvironment != null ) + return procEnvironment; + + procEnvironment = new Vector(); + try + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Execute exe = new Execute( new PumpStreamHandler( out ) ); + exe.setCommandline( getProcEnvCommand() ); + // Make sure we do not recurse forever + exe.setNewenvironment( true ); + int retval = exe.execute(); + if( retval != 0 ) + { + // Just try to use what we got + } + + BufferedReader in = + new BufferedReader( new StringReader( out.toString() ) ); + String var = null; + String line; + String lineSep = System.getProperty( "line.separator" ); + while( ( line = in.readLine() ) != null ) + { + if( line.indexOf( '=' ) == -1 ) + { + // Chunk part of previous env var (UNIX env vars can + // contain embedded new lines). + if( var == null ) + { + var = lineSep + line; + } + else + { + var += lineSep + line; + } + } + else + { + // New env var...append the previous one if we have it. + if( var != null ) + { + procEnvironment.addElement( var ); + } + var = line; + } + } + // Since we "look ahead" before adding, there's one last env var. + procEnvironment.addElement( var ); + } + catch( java.io.IOException exc ) + { + exc.printStackTrace(); + // Just try to see how much we got + } + return procEnvironment; + } + + /** + * A utility method that runs an external command. Writes the output and + * error streams of the command to the project log. + * + * @param task The task that the command is part of. Used for logging + * @param cmdline The command to execute. + * @throws BuildException if the command does not return 0. + */ + public static void runCommand( Task task, String[] cmdline ) + throws BuildException + { + try + { + task.log( Commandline.toString( cmdline ), Project.MSG_VERBOSE ); + Execute exe = new Execute( new LogStreamHandler( task, + Project.MSG_INFO, + Project.MSG_ERR ) ); + exe.setAntRun( task.getProject() ); + exe.setCommandline( cmdline ); + int retval = exe.execute(); + if( retval != 0 ) + { + throw new BuildException( cmdline[ 0 ] + " failed with return code " + retval, task.getLocation() ); + } + } + catch( java.io.IOException exc ) + { + throw new BuildException( "Could not launch " + cmdline[ 0 ] + ": " + exc, task.getLocation() ); + } + } + + private static String[] getProcEnvCommand() + { + if( Os.isFamily( "os/2" ) ) + { + // OS/2 - use same mechanism as Windows 2000 + // Not sure + String[] cmd = {"cmd", "/c", "set"}; + return cmd; + } + else if( Os.isFamily( "windows" ) ) + { + String osname = + System.getProperty( "os.name" ).toLowerCase( Locale.US ); + // Determine if we're running under 2000/NT or 98/95 + if( osname.indexOf( "nt" ) >= 0 || osname.indexOf( "2000" ) >= 0 ) + { + // Windows 2000/NT + String[] cmd = {"cmd", "/c", "set"}; + return cmd; + } + else + { + // Windows 98/95 - need to use an auxiliary script + String[] cmd = {"command.com", "/c", "set"}; + return cmd; + } + } + else if( Os.isFamily( "unix" ) ) + { + // Generic UNIX + // Alternatively one could use: /bin/sh -c env + String[] cmd = {"/usr/bin/env"}; + return cmd; + } + else if( Os.isFamily( "netware" ) ) + { + String[] cmd = {"env"}; + return cmd; + } + else + { + // MAC OS 9 and previous + // TODO: I have no idea how to get it, someone must fix it + String[] cmd = null; + return cmd; + } + } + + /** + * Set the name of the antRun script using the project's value. + * + * @param project the current project. + * @exception BuildException Description of Exception + */ + public void setAntRun( Project project ) + throws BuildException + { + this.project = project; + } + + /** + * Sets the commandline of the subprocess to launch. + * + * @param commandline the commandline of the subprocess to launch + */ + public void setCommandline( String[] commandline ) + { + cmdl = commandline; + } + + /** + * Sets the environment variables for the subprocess to launch. + * + * @param env The new Environment value + */ + public void setEnvironment( String[] env ) + { + this.env = env; + } + + /** + * Set whether to propagate the default environment or not. + * + * @param newenv whether to propagate the process environment. + */ + public void setNewenvironment( boolean newenv ) + { + newEnvironment = newenv; + } + + /** + * Launch this execution through the VM, where possible, rather than through + * the OS's shell. In some cases and operating systems using the shell will + * allow the shell to perform additional processing such as associating an + * executable with a script, etc + * + * @param useVMLauncher The new VMLauncher value + */ + public void setVMLauncher( boolean useVMLauncher ) + { + this.useVMLauncher = useVMLauncher; + } + + /** + * Sets the working directory of the process to execute.

              + * + * This is emulated using the antRun scripts unless the OS is Windows NT in + * which case a cmd.exe is spawned, or MRJ and setting user.dir works, or + * JDK 1.3 and there is official support in java.lang.Runtime. + * + * @param wd the working directory of the process. + */ + public void setWorkingDirectory( File wd ) + { + if( wd == null || wd.getAbsolutePath().equals( antWorkingDirectory ) ) + workingDirectory = null; + else + workingDirectory = wd; + } + + /** + * Returns the commandline used to create a subprocess. + * + * @return the commandline used to create a subprocess + */ + public String[] getCommandline() + { + return cmdl; + } + + /** + * Returns the environment used to create a subprocess. + * + * @return the environment used to create a subprocess + */ + public String[] getEnvironment() + { + if( env == null || newEnvironment ) + return env; + return patchEnvironment(); + } + + /** + * query the exit value of the process. + * + * @return the exit value, 1 if the process was killed, or Project.INVALID + * if no exit value has been received + */ + public int getExitValue() + { + return exitValue; + } + + /** + * Runs a process defined by the command line and returns its exit status. + * + * @return the exit status of the subprocess or INVALID + * @exception IOException Description of Exception + */ + public int execute() + throws IOException + { + CommandLauncher launcher = vmLauncher != null ? vmLauncher : shellLauncher; + if( !useVMLauncher ) + { + launcher = shellLauncher; + } + + final Process process = launcher.exec( project, getCommandline(), getEnvironment(), workingDirectory ); + try + { + streamHandler.setProcessInputStream( process.getOutputStream() ); + streamHandler.setProcessOutputStream( process.getInputStream() ); + streamHandler.setProcessErrorStream( process.getErrorStream() ); + } + catch( IOException e ) + { + process.destroy(); + throw e; + } + streamHandler.start(); + + // add the process to the list of those to destroy if the VM exits + // + processDestroyer.add( process ); + + if( watchdog != null ) + watchdog.start( process ); + waitFor( process ); + + // remove the process to the list of those to destroy if the VM exits + // + processDestroyer.remove( process ); + + if( watchdog != null ) + watchdog.stop(); + streamHandler.stop(); + if( watchdog != null ) + watchdog.checkException(); + return getExitValue(); + } + + /** + * test for an untimely death of the process + * + * @return true iff a watchdog had to kill the process + * @since 1.5 + */ + public boolean killedProcess() + { + return watchdog != null && watchdog.killedProcess(); + } + + protected void setExitValue( int value ) + { + exitValue = value; + } + + protected void waitFor( Process process ) + { + try + { + process.waitFor(); + setExitValue( process.exitValue() ); + } + catch( InterruptedException e ) + { + } + } + + /** + * Patch the current environment with the new values from the user. + * + * @return the patched environment + */ + private String[] patchEnvironment() + { + Vector osEnv = (Vector)getProcEnvironment().clone(); + for( int i = 0; i < env.length; i++ ) + { + int pos = env[ i ].indexOf( '=' ); + // Get key including "=" + String key = env[ i ].substring( 0, pos + 1 ); + int size = osEnv.size(); + for( int j = 0; j < size; j++ ) + { + if( ( (String)osEnv.elementAt( j ) ).startsWith( key ) ) + { + osEnv.removeElementAt( j ); + break; + } + } + osEnv.addElement( env[ i ] ); + } + String[] result = new String[ osEnv.size() ]; + osEnv.copyInto( result ); + return result; + } + + /** + * A command launcher for a particular JVM/OS platform. This class is a + * general purpose command launcher which can only launch commands in the + * current working directory. + * + * @author RT + */ + private static class CommandLauncher + { + /** + * Launches the given command in a new process. + * + * @param project The project that the command is part of + * @param cmd The command to execute + * @param env The environment for the new process. If null, the + * environment of the current proccess is used. + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + if( project != null ) + { + project.log( "Execute:CommandLauncher: " + + Commandline.toString( cmd ), Project.MSG_DEBUG ); + } + return Runtime.getRuntime().exec( cmd, env ); + } + + /** + * Launches the given command in a new process, in the given working + * directory. + * + * @param project The project that the command is part of + * @param cmd The command to execute + * @param env The environment for the new process. If null, the + * environment of the current proccess is used. + * @param workingDir The directory to start the command in. If null, the + * current directory is used + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot execute a process in different directory under this JVM" ); + } + } + + /** + * A command launcher that proxies another command launcher. Sub-classes + * override exec(args, env, workdir) + * + * @author RT + */ + private static class CommandLauncherProxy extends CommandLauncher + { + + private CommandLauncher _launcher; + + CommandLauncherProxy( CommandLauncher launcher ) + { + _launcher = launcher; + } + + /** + * Launches the given command in a new process. Delegates this method to + * the proxied launcher + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + return _launcher.exec( project, cmd, env ); + } + } + + /** + * A command launcher for JDK/JRE 1.1 under Windows. Fixes quoting problems + * in Runtime.exec(). Can only launch commands in the current working + * directory + * + * @author RT + */ + private static class Java11CommandLauncher extends CommandLauncher + { + /** + * Launches the given command in a new process. Needs to quote arguments + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + // Need to quote arguments with spaces, and to escape quote characters + String[] newcmd = new String[ cmd.length ]; + for( int i = 0; i < cmd.length; i++ ) + { + newcmd[ i ] = Commandline.quoteArgument( cmd[ i ] ); + } + if( project != null ) + { + project.log( "Execute:Java11CommandLauncher: " + + Commandline.toString( newcmd ), Project.MSG_DEBUG ); + } + return Runtime.getRuntime().exec( newcmd, env ); + } + } + + /** + * A command launcher for JDK/JRE 1.3 (and higher). Uses the built-in + * Runtime.exec() command + * + * @author RT + */ + private static class Java13CommandLauncher extends CommandLauncher + { + + private Method _execWithCWD; + + public Java13CommandLauncher() + throws NoSuchMethodException + { + // Locate method Runtime.exec(String[] cmdarray, String[] envp, File dir) + _execWithCWD = Runtime.class.getMethod( "exec", new Class[]{String[].class, String[].class, File.class} ); + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + try + { + if( project != null ) + { + project.log( "Execute:Java13CommandLauncher: " + + Commandline.toString( cmd ), Project.MSG_DEBUG ); + } + Object[] arguments = {cmd, env, workingDir}; + return (Process)_execWithCWD.invoke( Runtime.getRuntime(), arguments ); + } + catch( InvocationTargetException exc ) + { + Throwable realexc = exc.getTargetException(); + if( realexc instanceof ThreadDeath ) + { + throw (ThreadDeath)realexc; + } + else if( realexc instanceof IOException ) + { + throw (IOException)realexc; + } + else + { + throw new BuildException( "Unable to execute command", realexc ); + } + } + catch( Exception exc ) + { + // IllegalAccess, IllegalArgument, ClassCast + throw new BuildException( "Unable to execute command", exc ); + } + } + } + + /** + * A command launcher for Mac that uses a dodgy mechanism to change working + * directory before launching commands. + * + * @author RT + */ + private static class MacCommandLauncher extends CommandLauncherProxy + { + MacCommandLauncher( CommandLauncher launcher ) + { + super( launcher ); + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + + System.getProperties().put( "user.dir", workingDir.getAbsolutePath() ); + try + { + return exec( project, cmd, env ); + } + finally + { + System.getProperties().put( "user.dir", antWorkingDirectory ); + } + } + } + + /** + * A command launcher that uses an auxiliary perl script to launch commands + * in directories other than the current working directory. + * + * @author RT + */ + private static class PerlScriptCommandLauncher extends CommandLauncherProxy + { + + private String _script; + + PerlScriptCommandLauncher( String script, CommandLauncher launcher ) + { + super( launcher ); + _script = script; + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( project == null ) + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot locate antRun script: No project provided" ); + } + + // Locate the auxiliary script + String antHome = project.getProperty( "ant.home" ); + if( antHome == null ) + { + throw new IOException( "Cannot locate antRun script: Property 'ant.home' not found" ); + } + String antRun = project.resolveFile( antHome + File.separator + _script ).toString(); + + // Build the command + File commandDir = workingDir; + if( workingDir == null && project != null ) + { + commandDir = project.getBaseDir(); + } + + String[] newcmd = new String[ cmd.length + 3 ]; + newcmd[ 0 ] = "perl"; + newcmd[ 1 ] = antRun; + newcmd[ 2 ] = commandDir.getAbsolutePath(); + System.arraycopy( cmd, 0, newcmd, 3, cmd.length ); + + return exec( project, newcmd, env ); + } + } + + /** + * A command launcher that uses an auxiliary script to launch commands in + * directories other than the current working directory. + * + * @author RT + */ + private static class ScriptCommandLauncher extends CommandLauncherProxy + { + + private String _script; + + ScriptCommandLauncher( String script, CommandLauncher launcher ) + { + super( launcher ); + _script = script; + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( project == null ) + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot locate antRun script: No project provided" ); + } + + // Locate the auxiliary script + String antHome = project.getProperty( "ant.home" ); + if( antHome == null ) + { + throw new IOException( "Cannot locate antRun script: Property 'ant.home' not found" ); + } + String antRun = project.resolveFile( antHome + File.separator + _script ).toString(); + + // Build the command + File commandDir = workingDir; + if( workingDir == null && project != null ) + { + commandDir = project.getBaseDir(); + } + + String[] newcmd = new String[ cmd.length + 2 ]; + newcmd[ 0 ] = antRun; + newcmd[ 1 ] = commandDir.getAbsolutePath(); + System.arraycopy( cmd, 0, newcmd, 2, cmd.length ); + + return exec( project, newcmd, env ); + } + } + + /** + * A command launcher for Windows 2000/NT that uses 'cmd.exe' when launching + * commands in directories other than the current working directory. + * + * @author RT + */ + private static class WinNTCommandLauncher extends CommandLauncherProxy + { + WinNTCommandLauncher( CommandLauncher launcher ) + { + super( launcher ); + } + + /** + * Launches the given command in a new process, in the given working + * directory. + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + File commandDir = workingDir; + if( workingDir == null ) + { + if( project != null ) + { + commandDir = project.getBaseDir(); + } + else + { + return exec( project, cmd, env ); + } + } + + // Use cmd.exe to change to the specified directory before running + // the command + final int preCmdLength = 6; + String[] newcmd = new String[ cmd.length + preCmdLength ]; + newcmd[ 0 ] = "cmd"; + newcmd[ 1 ] = "/c"; + newcmd[ 2 ] = "cd"; + newcmd[ 3 ] = "/d"; + newcmd[ 4 ] = commandDir.getAbsolutePath(); + newcmd[ 5 ] = "&&"; + System.arraycopy( cmd, 0, newcmd, preCmdLength, cmd.length ); + + return exec( project, newcmd, env ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteJava.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteJava.java new file mode 100644 index 000000000..6fc077991 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteJava.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/* + * @author thomas.haas@softwired-inc.com + * @author Stefan Bodewig + */ +public class ExecuteJava +{ + + private Commandline javaCommand = null; + private Path classpath = null; + private CommandlineJava.SysProperties sysProperties = null; + + public void setClasspath( Path p ) + { + classpath = p; + } + + public void setJavaCommand( Commandline javaCommand ) + { + this.javaCommand = javaCommand; + } + + /** + * All output (System.out as well as System.err) will be written to this + * Stream. + * + * @param out The new Output value + * @deprecated manage output at the task level + */ + public void setOutput( PrintStream out ) { } + + public void setSystemProperties( CommandlineJava.SysProperties s ) + { + sysProperties = s; + } + + public void execute( Project project ) + throws BuildException + { + final String classname = javaCommand.getExecutable(); + final Object[] argument = {javaCommand.getArguments()}; + + AntClassLoader loader = null; + try + { + if( sysProperties != null ) + { + sysProperties.setSystem(); + } + + final Class[] param = {Class.forName( "[Ljava.lang.String;" )}; + Class target = null; + if( classpath == null ) + { + target = Class.forName( classname ); + } + else + { + loader = new AntClassLoader( project.getCoreLoader(), project, classpath, false ); + loader.setIsolated( true ); + loader.setThreadContextLoader(); + target = loader.forceLoadClass( classname ); + AntClassLoader.initializeClass( target ); + } + final Method main = target.getMethod( "main", param ); + main.invoke( null, argument ); + } + catch( NullPointerException e ) + { + throw new BuildException( "Could not find main() method in " + classname ); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( "Could not find " + classname + ". Make sure you have it in your classpath" ); + } + catch( InvocationTargetException e ) + { + Throwable t = e.getTargetException(); + if( !( t instanceof SecurityException ) ) + { + throw new BuildException( t ); + } + else + { + throw ( SecurityException )t; + } + } + catch( Exception e ) + { + throw new BuildException( e ); + } + finally + { + if( loader != null ) + { + loader.resetThreadContextLoader(); + loader.cleanup(); + } + if( sysProperties != null ) + { + sysProperties.restoreSystem(); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteOn.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteOn.java new file mode 100644 index 000000000..0a8bd9d53 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteOn.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Executes a given command, supplying a set of files as arguments. + * + * @author Stefan Bodewig + * @author Mariusz Nowostawski + */ +public class ExecuteOn extends ExecTask +{ + + protected Vector filesets = new Vector(); + private boolean relative = false; + private boolean parallel = false; + protected String type = "file"; + protected Commandline.Marker srcFilePos = null; + private boolean skipEmpty = false; + protected Commandline.Marker targetFilePos = null; + protected Mapper mapperElement = null; + protected FileNameMapper mapper = null; + protected File destDir = null; + + /** + * Has <srcfile> been specified before <targetfile> + */ + protected boolean srcIsFirst = true; + + /** + * Set the destination directory. + * + * @param destDir The new Dest value + */ + public void setDest( File destDir ) + { + this.destDir = destDir; + } + + + /** + * Shall the command work on all specified files in parallel? + * + * @param parallel The new Parallel value + */ + public void setParallel( boolean parallel ) + { + this.parallel = parallel; + } + + /** + * Should filenames be returned as relative path names? + * + * @param relative The new Relative value + */ + public void setRelative( boolean relative ) + { + this.relative = relative; + } + + /** + * Should empty filesets be ignored? + * + * @param skip The new SkipEmptyFilesets value + */ + public void setSkipEmptyFilesets( boolean skip ) + { + skipEmpty = skip; + } + + /** + * Shall the command work only on files, directories or both? + * + * @param type The new Type value + */ + public void setType( FileDirBoth type ) + { + this.type = type.getValue(); + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Marker that indicates where the name of the source file should be put on + * the command line. + * + * @return Description of the Returned Value + */ + public Commandline.Marker createSrcfile() + { + if( srcFilePos != null ) + { + throw new BuildException( taskType + " doesn\'t support multiple srcfile elements.", + location ); + } + srcFilePos = cmdl.createMarker(); + return srcFilePos; + } + + /** + * Marker that indicates where the name of the target file should be put on + * the command line. + * + * @return Description of the Returned Value + */ + public Commandline.Marker createTargetfile() + { + if( targetFilePos != null ) + { + throw new BuildException( taskType + " doesn\'t support multiple targetfile elements.", + location ); + } + targetFilePos = cmdl.createMarker(); + srcIsFirst = ( srcFilePos != null ); + return targetFilePos; + } + + /** + * Construct the command line for parallel execution. + * + * @param srcFiles The filenames to add to the commandline + * @param baseDirs Description of Parameter + * @return The Commandline value + */ + protected String[] getCommandline( String[] srcFiles, File[] baseDirs ) + { + Vector targets = new Vector(); + if( targetFilePos != null ) + { + Hashtable addedFiles = new Hashtable(); + for( int i = 0; i < srcFiles.length; i++ ) + { + String[] subTargets = mapper.mapFileName( srcFiles[i] ); + if( subTargets != null ) + { + for( int j = 0; j < subTargets.length; j++ ) + { + String name = null; + if( !relative ) + { + name = + ( new File( destDir, subTargets[j] ) ).getAbsolutePath(); + } + else + { + name = subTargets[j]; + } + if( !addedFiles.contains( name ) ) + { + targets.addElement( name ); + addedFiles.put( name, name ); + } + } + } + } + } + String[] targetFiles = new String[targets.size()]; + targets.copyInto( targetFiles ); + + String[] orig = cmdl.getCommandline(); + String[] result = new String[orig.length + srcFiles.length + targetFiles.length]; + + int srcIndex = orig.length; + if( srcFilePos != null ) + { + srcIndex = srcFilePos.getPosition(); + } + + if( targetFilePos != null ) + { + int targetIndex = targetFilePos.getPosition(); + + if( srcIndex < targetIndex + || ( srcIndex == targetIndex && srcIsFirst ) ) + { + + // 0 --> srcIndex + System.arraycopy( orig, 0, result, 0, srcIndex ); + + // srcIndex --> targetIndex + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length, + targetIndex - srcIndex ); + + // targets are already absolute file names + System.arraycopy( targetFiles, 0, result, + targetIndex + srcFiles.length, + targetFiles.length ); + + // targetIndex --> end + System.arraycopy( orig, targetIndex, result, + targetIndex + srcFiles.length + targetFiles.length, + orig.length - targetIndex ); + } + else + { + // 0 --> targetIndex + System.arraycopy( orig, 0, result, 0, targetIndex ); + + // targets are already absolute file names + System.arraycopy( targetFiles, 0, result, + targetIndex, + targetFiles.length ); + + // targetIndex --> srcIndex + System.arraycopy( orig, targetIndex, result, + targetIndex + targetFiles.length, + srcIndex - targetIndex ); + + // srcIndex --> end + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length + targetFiles.length, + orig.length - srcIndex ); + srcIndex += targetFiles.length; + } + + } + else + {// no targetFilePos + + // 0 --> srcIndex + System.arraycopy( orig, 0, result, 0, srcIndex ); + // srcIndex --> end + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length, + orig.length - srcIndex ); + + } + + // fill in source file names + for( int i = 0; i < srcFiles.length; i++ ) + { + if( !relative ) + { + result[srcIndex + i] = + ( new File( baseDirs[i], srcFiles[i] ) ).getAbsolutePath(); + } + else + { + result[srcIndex + i] = srcFiles[i]; + } + } + return result; + } + + /** + * Construct the command line for serial execution. + * + * @param srcFile The filename to add to the commandline + * @param baseDir filename is relative to this dir + * @return The Commandline value + */ + protected String[] getCommandline( String srcFile, File baseDir ) + { + return getCommandline( new String[]{srcFile}, new File[]{baseDir} ); + } + + /** + * Return the list of Directories from this DirectoryScanner that should be + * included on the command line. + * + * @param baseDir Description of Parameter + * @param ds Description of Parameter + * @return The Dirs value + */ + protected String[] getDirs( File baseDir, DirectoryScanner ds ) + { + if( mapper != null ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + return sfs.restrict( ds.getIncludedDirectories(), baseDir, destDir, + mapper ); + } + else + { + return ds.getIncludedDirectories(); + } + } + + /** + * Return the list of files from this DirectoryScanner that should be + * included on the command line. + * + * @param baseDir Description of Parameter + * @param ds Description of Parameter + * @return The Files value + */ + protected String[] getFiles( File baseDir, DirectoryScanner ds ) + { + if( mapper != null ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + return sfs.restrict( ds.getIncludedFiles(), baseDir, destDir, + mapper ); + } + else + { + return ds.getIncludedFiles(); + } + } + + protected void checkConfiguration() + { + if( "execon".equals( taskName ) ) + { + log( "!! execon is deprecated. Use apply instead. !!" ); + } + + super.checkConfiguration(); + if( filesets.size() == 0 ) + { + throw new BuildException( "no filesets specified", location ); + } + + if( targetFilePos != null || mapperElement != null + || destDir != null ) + { + + if( mapperElement == null ) + { + throw new BuildException( "no mapper specified", location ); + } + if( mapperElement == null ) + { + throw new BuildException( "no dest attribute specified", + location ); + } + mapper = mapperElement.getImplementation(); + } + } + + protected void runExec( Execute exe ) + throws BuildException + { + try + { + + Vector fileNames = new Vector(); + Vector baseDirs = new Vector(); + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + File base = fs.getDir( project ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + + if( !"dir".equals( type ) ) + { + String[] s = getFiles( base, ds ); + for( int j = 0; j < s.length; j++ ) + { + fileNames.addElement( s[j] ); + baseDirs.addElement( base ); + } + } + + if( !"file".equals( type ) ) + { + String[] s = getDirs( base, ds ); + ; + for( int j = 0; j < s.length; j++ ) + { + fileNames.addElement( s[j] ); + baseDirs.addElement( base ); + } + } + + if( fileNames.size() == 0 && skipEmpty ) + { + log( "Skipping fileset for directory " + + base + ". It is empty.", Project.MSG_INFO ); + continue; + } + + if( !parallel ) + { + String[] s = new String[fileNames.size()]; + fileNames.copyInto( s ); + for( int j = 0; j < s.length; j++ ) + { + String[] command = getCommandline( s[j], base ); + log( "Executing " + Commandline.toString( command ), + Project.MSG_VERBOSE ); + exe.setCommandline( command ); + runExecute( exe ); + } + fileNames.removeAllElements(); + baseDirs.removeAllElements(); + } + } + + if( parallel && ( fileNames.size() > 0 || !skipEmpty ) ) + { + String[] s = new String[fileNames.size()]; + fileNames.copyInto( s ); + File[] b = new File[baseDirs.size()]; + baseDirs.copyInto( b ); + String[] command = getCommandline( s, b ); + log( "Executing " + Commandline.toString( command ), + Project.MSG_VERBOSE ); + exe.setCommandline( command ); + runExecute( exe ); + } + + } + catch( IOException e ) + { + throw new BuildException( "Execute failed: " + e, e, location ); + } + finally + { + // close the output file if required + logFlush(); + } + } + + /** + * Enumerated attribute with the values "file", "dir" and "both" for the + * type attribute. + * + * @author RT + */ + public static class FileDirBoth extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"file", "dir", "both"}; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java new file mode 100644 index 000000000..f9f64cca6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Used by Execute to handle input and output stream of + * subprocesses. + * + * @author thomas.haas@softwired-inc.com + */ +public interface ExecuteStreamHandler +{ + + /** + * Install a handler for the input stream of the subprocess. + * + * @param os output stream to write to the standard input stream of the + * subprocess + * @exception IOException Description of Exception + */ + void setProcessInputStream( OutputStream os ) + throws IOException; + + /** + * Install a handler for the error stream of the subprocess. + * + * @param is input stream to read from the error stream from the subprocess + * @exception IOException Description of Exception + */ + void setProcessErrorStream( InputStream is ) + throws IOException; + + /** + * Install a handler for the output stream of the subprocess. + * + * @param is input stream to read from the error stream from the subprocess + * @exception IOException Description of Exception + */ + void setProcessOutputStream( InputStream is ) + throws IOException; + + /** + * Start handling of the streams. + * + * @exception IOException Description of Exception + */ + void start() + throws IOException; + + /** + * Stop handling of the streams - will not be restarted. + */ + void stop(); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java new file mode 100644 index 000000000..318e622aa --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Destroys a process running for too long. For example:

              + * ExecuteWatchdog watchdog = new ExecuteWatchdog(30000);
              + * Execute exec = new Execute(myloghandler, watchdog);
              + * exec.setCommandLine(mycmdline);
              + * int exitvalue = exec.execute();
              + * if (exitvalue != SUCCESS && watchdog.killedProcess()){
              + *              // it was killed on purpose by the watchdog
              + * }
              + * 
              + * + * @author thomas.haas@softwired-inc.com + * @author Stephane Bailliez + * @see Execute + */ +public class ExecuteWatchdog implements Runnable +{ + + /** + * say whether or not the watchog is currently monitoring a process + */ + private boolean watch = false; + + /** + * exception that might be thrown during the process execution + */ + private Exception caught = null; + + /** + * say whether or not the process was killed due to running overtime + */ + private boolean killedProcess = false; + + /** + * the process to execute and watch for duration + */ + private Process process; + + /** + * timeout duration. Once the process running time exceeds this it should be + * killed + */ + private int timeout; + + /** + * Creates a new watchdog with a given timeout. + * + * @param timeout the timeout for the process in milliseconds. It must be + * greather than 0. + */ + public ExecuteWatchdog( int timeout ) + { + if( timeout < 1 ) + { + throw new IllegalArgumentException( "timeout lesser than 1." ); + } + this.timeout = timeout; + } + + /** + * Indicates whether or not the watchdog is still monitoring the process. + * + * @return true if the process is still running, otherwise + * false . + */ + public boolean isWatching() + { + return watch; + } + + /** + * This method will rethrow the exception that was possibly caught during + * the run of the process. It will only remains valid once the process has + * been terminated either by 'error', timeout or manual intervention. + * Information will be discarded once a new process is ran. + * + * @throws BuildException a wrapped exception over the one that was silently + * swallowed and stored during the process run. + */ + public void checkException() + throws BuildException + { + if( caught != null ) + { + throw new BuildException( "Exception in ExecuteWatchdog.run: " + + caught.getMessage(), caught ); + } + } + + /** + * Indicates whether the last process run was killed on timeout or not. + * + * @return true if the process was killed otherwise false + * . + */ + public boolean killedProcess() + { + return killedProcess; + } + + + /** + * Watches the process and terminates it, if it runs for to long. + */ + public synchronized void run() + { + try + { + // This isn't a Task, don't have a Project object to log. + // project.log("ExecuteWatchdog: timeout = "+timeout+" msec", Project.MSG_VERBOSE); + final long until = System.currentTimeMillis() + timeout; + long now; + while( watch && until > ( now = System.currentTimeMillis() ) ) + { + try + { + wait( until - now ); + } + catch( InterruptedException e ) + {} + } + + // if we are here, either someone stopped the watchdog, + // we are on timeout and the process must be killed, or + // we are on timeout and the process has already stopped. + try + { + // We must check if the process was not stopped + // before being here + process.exitValue(); + } + catch( IllegalThreadStateException e ) + { + // the process is not terminated, if this is really + // a timeout and not a manual stop then kill it. + if( watch ) + { + killedProcess = true; + process.destroy(); + } + } + } + catch( Exception e ) + { + caught = e; + } + finally + { + cleanUp(); + } + } + + /** + * Watches the given process and terminates it, if it runs for too long. All + * information from the previous run are reset. + * + * @param process the process to monitor. It cannot be null + * @throws IllegalStateException thrown if a process is still being + * monitored. + */ + public synchronized void start( Process process ) + { + if( process == null ) + { + throw new NullPointerException( "process is null." ); + } + if( this.process != null ) + { + throw new IllegalStateException( "Already running." ); + } + this.caught = null; + this.killedProcess = false; + this.watch = true; + this.process = process; + final Thread thread = new Thread( this, "WATCHDOG" ); + thread.setDaemon( true ); + thread.start(); + } + + /** + * Stops the watcher. It will notify all threads possibly waiting on this + * object. + */ + public synchronized void stop() + { + watch = false; + notifyAll(); + } + + /** + * reset the monitor flag and the process. + */ + protected void cleanUp() + { + watch = false; + process = null; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exit.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exit.java new file mode 100644 index 000000000..60ae24009 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exit.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; + +/** + * Just exit the active build, giving an additional message if available. + * + * @author Nico Seessle + */ +public class Exit extends Task +{ + private String ifCondition, unlessCondition; + private String message; + + public void setIf( String c ) + { + ifCondition = c; + } + + public void setMessage( String value ) + { + this.message = value; + } + + public void setUnless( String c ) + { + unlessCondition = c; + } + + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + public void execute() + throws BuildException + { + if( testIfCondition() && testUnlessCondition() ) + { + if( message != null && message.length() > 0 ) + { + throw new BuildException( message ); + } + else + { + throw new BuildException( "No message" ); + } + } + } + + private boolean testIfCondition() + { + if( ifCondition == null || "".equals( ifCondition ) ) + { + return true; + } + + return project.getProperty( ifCondition ) != null; + } + + private boolean testUnlessCondition() + { + if( unlessCondition == null || "".equals( unlessCondition ) ) + { + return true; + } + return project.getProperty( unlessCondition ) == null; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Expand.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Expand.java new file mode 100644 index 000000000..38ebc9404 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Expand.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; +import org.apache.tools.ant.util.FileUtils; + +/** + * Unzip a file. + * + * @author costin@dnt.ro + * @author Stefan Bodewig + * @author Magesh Umasankar + */ +public class Expand extends MatchingTask +{// req + private boolean overwrite = true; + private Vector patternsets = new Vector(); + private Vector filesets = new Vector(); + private File dest;//req + private File source; + + /** + * Set the destination directory. File will be unzipped into the destination + * directory. + * + * @param d Path to the directory. + */ + public void setDest( File d ) + { + this.dest = d; + } + + /** + * Should we overwrite files in dest, even if they are newer than the + * corresponding entries in the archive? + * + * @param b The new Overwrite value + */ + public void setOverwrite( boolean b ) + { + overwrite = b; + } + + /** + * Set the path to zip-file. + * + * @param s Path to zip-file. + */ + public void setSrc( File s ) + { + this.source = s; + } + + /** + * Add a fileset + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Add a patternset + * + * @param set The feature to be added to the Patternset attribute + */ + public void addPatternset( PatternSet set ) + { + patternsets.addElement( set ); + } + + /** + * Do the work. + * + * @exception BuildException Thrown in unrecoverable error. + */ + public void execute() + throws BuildException + { + if( "expand".equals( taskType ) ) + { + log( "!! expand is deprecated. Use unzip instead. !!" ); + } + + if( source == null && filesets.size() == 0 ) + { + throw new BuildException( "src attribute and/or filesets must be specified" ); + } + + if( dest == null ) + { + throw new BuildException( + "Dest attribute must be specified" ); + } + + if( dest.exists() && !dest.isDirectory() ) + { + throw new BuildException( "Dest must be a directory.", location ); + } + + FileUtils fileUtils = FileUtils.newFileUtils(); + + if( source != null ) + { + if( source.isDirectory() ) + { + throw new BuildException( "Src must not be a directory." + + " Use nested filesets instead.", location ); + } + else + { + expandFile( fileUtils, source, dest ); + } + } + if( filesets.size() > 0 ) + { + for( int j = 0; j < filesets.size(); j++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( j ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] files = ds.getIncludedFiles(); + for( int i = 0; i < files.length; ++i ) + { + File file = new File( fromDir, files[i] ); + expandFile( fileUtils, file, dest ); + } + } + } + } + + /* + * This method is to be overridden by extending unarchival tasks. + */ + protected void expandFile( FileUtils fileUtils, File srcF, File dir ) + { + ZipInputStream zis = null; + try + { + // code from WarExpand + zis = new ZipInputStream( new FileInputStream( srcF ) ); + ZipEntry ze = null; + + while( ( ze = zis.getNextEntry() ) != null ) + { + extractFile( fileUtils, srcF, dir, zis, + ze.getName(), + new Date( ze.getTime() ), + ze.isDirectory() ); + } + + log( "expand complete", Project.MSG_VERBOSE ); + } + catch( IOException ioe ) + { + throw new BuildException( "Error while expanding " + srcF.getPath(), ioe ); + } + finally + { + if( zis != null ) + { + try + { + zis.close(); + } + catch( IOException e ) + {} + } + } + } + + protected void extractFile( FileUtils fileUtils, File srcF, File dir, + InputStream compressedInputStream, + String entryName, + Date entryDate, boolean isDirectory ) + throws IOException + { + + if( patternsets != null && patternsets.size() > 0 ) + { + String name = entryName; + boolean included = false; + for( int v = 0; v < patternsets.size(); v++ ) + { + PatternSet p = ( PatternSet )patternsets.elementAt( v ); + String[] incls = p.getIncludePatterns( project ); + if( incls != null ) + { + for( int w = 0; w < incls.length; w++ ) + { + boolean isIncl = DirectoryScanner.match( incls[w], name ); + if( isIncl ) + { + included = true; + break; + } + } + } + String[] excls = p.getExcludePatterns( project ); + if( excls != null ) + { + for( int w = 0; w < excls.length; w++ ) + { + boolean isExcl = DirectoryScanner.match( excls[w], name ); + if( isExcl ) + { + included = false; + break; + } + } + } + } + if( !included ) + { + //Do not process this file + return; + } + } + + File f = fileUtils.resolveFile( dir, entryName ); + try + { + if( !overwrite && f.exists() + && f.lastModified() >= entryDate.getTime() ) + { + log( "Skipping " + f + " as it is up-to-date", + Project.MSG_DEBUG ); + return; + } + + log( "expanding " + entryName + " to " + f, + Project.MSG_VERBOSE ); + // create intermediary directories - sometimes zip don't add them + File dirF = fileUtils.getParentFile( f ); + dirF.mkdirs(); + + if( isDirectory ) + { + f.mkdirs(); + } + else + { + byte[] buffer = new byte[1024]; + int length = 0; + FileOutputStream fos = null; + try + { + fos = new FileOutputStream( f ); + + while( ( length = + compressedInputStream.read( buffer ) ) >= 0 ) + { + fos.write( buffer, 0, length ); + } + + fos.close(); + fos = null; + } + finally + { + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException e ) + {} + } + } + } + + fileUtils.setFileLastModified( f, entryDate.getTime() ); + } + catch( FileNotFoundException ex ) + { + log( "Unable to expand to file " + f.getPath(), Project.MSG_WARN ); + } + + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Filter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Filter.java new file mode 100644 index 000000000..6970d0ab4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Filter.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * This task sets a token filter that is used by the file copy methods of the + * project to do token substitution, or sets mutiple tokens by reading these + * from a file. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Gero Vermaas gero@xs4all.nl + * @author Michael McCallum + */ +public class Filter extends Task +{ + private File filtersFile; + + private String token; + private String value; + + public void setFiltersfile( File filtersFile ) + { + this.filtersFile = filtersFile; + } + + public void setToken( String token ) + { + this.token = token; + } + + public void setValue( String value ) + { + this.value = value; + } + + public void execute() + throws BuildException + { + boolean isFiltersFromFile = filtersFile != null && token == null && value == null; + boolean isSingleFilter = filtersFile == null && token != null && value != null; + + if( !isFiltersFromFile && !isSingleFilter ) + { + throw new BuildException( "both token and value parameters, or only a filtersFile parameter is required", location ); + } + + if( isSingleFilter ) + { + project.getGlobalFilterSet().addFilter( token, value ); + } + + if( isFiltersFromFile ) + { + readFilters(); + } + } + + protected void readFilters() + throws BuildException + { + log( "Reading filters from " + filtersFile, Project.MSG_VERBOSE ); + project.getGlobalFilterSet().readFiltersFromFile( filtersFile ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/FixCRLF.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/FixCRLF.java new file mode 100644 index 000000000..2cd21df0a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/FixCRLF.java @@ -0,0 +1,1159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Enumeration; +import java.util.NoSuchElementException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.util.FileUtils; + +/** + * Task to convert text source files to local OS formatting conventions, as well + * as repair text files damaged by misconfigured or misguided editors or file + * transfer programs.

              + * + * This task can take the following arguments: + *

                + *
              • srcdir + *
              • destdir + *
              • include + *
              • exclude + *
              • cr + *
              • eol + *
              • tab + *
              • eof + *
              • encoding + *
              + * Of these arguments, only sourcedir is required.

              + * + * When this task executes, it will scan the srcdir based on the include and + * exclude properties.

              + * + * This version generalises the handling of EOL characters, and allows for + * CR-only line endings (which I suspect is the standard on Macs.) Tab handling + * has also been generalised to accommodate any tabwidth from 2 to 80, + * inclusive. Importantly, it will leave untouched any literal TAB characters + * embedded within string or character constants.

              + * + * Warning: do not run on binary files. Caution: run with care + * on carefully formatted files. This may sound obvious, but if you don't + * specify asis, presume that your files are going to be modified. If "tabs" is + * "add" or "remove", whitespace characters may be added or removed as + * necessary. Similarly, for CR's - in fact "eol"="crlf" or cr="add" can result + * in cr characters being removed in one special case accommodated, i.e., CRCRLF + * is regarded as a single EOL to handle cases where other programs have + * converted CRLF into CRCRLF. + * + * @author Sam Ruby rubys@us.ibm.com + * @author Peter B. West + * @version $Revision$ $Name$ + */ + +public class FixCRLF extends MatchingTask +{ + + private final static int UNDEF = -1; + private final static int NOTJAVA = 0; + private final static int LOOKING = 1; + private final static int IN_CHAR_CONST = 2; + private final static int IN_STR_CONST = 3; + private final static int IN_SINGLE_COMMENT = 4; + private final static int IN_MULTI_COMMENT = 5; + + private final static int ASIS = 0; + private final static int CR = 1; + private final static int LF = 2; + private final static int CRLF = 3; + private final static int ADD = 1; + private final static int REMOVE = -1; + private final static int SPACES = -1; + private final static int TABS = 1; + + private final static int INBUFLEN = 8192; + private final static int LINEBUFLEN = 200; + + private final static char CTRLZ = '\u001A'; + + private int tablength = 8; + private String spaces = " "; + private StringBuffer linebuf = new StringBuffer( 1024 ); + private StringBuffer linebuf2 = new StringBuffer( 1024 ); + private boolean javafiles = false; + private File destDir = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + /** + * Encoding to assume for the files + */ + private String encoding = null; + private int ctrlz; + private int eol; + private String eolstr; + + private File srcDir; + private int tabs; + + /** + * Defaults the properties based on the system type. + *

                + *
              • Unix: eol="LF" tab="asis" eof="remove" + *
              • Mac: eol="CR" tab="asis" eof="remove" + *
              • DOS: eol="CRLF" tab="asis" eof="asis" + *
              + * + */ + public FixCRLF() + { + tabs = ASIS; + if( System.getProperty( "path.separator" ).equals( ":" ) ) + { + ctrlz = REMOVE; + if( System.getProperty( "os.name" ).indexOf( "Mac" ) > -1 ) + { + eol = CR; + eolstr = "\r"; + } + else + { + eol = LF; + eolstr = "\n"; + } + } + else + { + ctrlz = ASIS; + eol = CRLF; + eolstr = "\r\n"; + } + } + + /** + * Specify how carriage return (CR) characters are to be handled + * + * @param attr The new Cr value + * @deprecated use {@link #setEol setEol} instead. + */ + public void setCr( AddAsisRemove attr ) + { + log( "DEPRECATED: The cr attribute has been deprecated,", + Project.MSG_WARN ); + log( "Please us the eol attribute instead", Project.MSG_WARN ); + String option = attr.getValue(); + CrLf c = new CrLf(); + if( option.equals( "remove" ) ) + { + c.setValue( "lf" ); + } + else if( option.equals( "asis" ) ) + { + c.setValue( "asis" ); + } + else + { + // must be "add" + c.setValue( "crlf" ); + } + setEol( c ); + } + + /** + * Set the destination where the fixed files should be placed. Default is to + * replace the original file. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Specifies the encoding Ant expects the files to be in - defaults to the + * platforms default encoding. + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Specify how DOS EOF (control-z) charaters are to be handled + * + * @param attr The new Eof value + */ + public void setEof( AddAsisRemove attr ) + { + String option = attr.getValue(); + if( option.equals( "remove" ) ) + { + ctrlz = REMOVE; + } + else if( option.equals( "asis" ) ) + { + ctrlz = ASIS; + } + else + { + // must be "add" + ctrlz = ADD; + } + } + + + /** + * Specify how EndOfLine characters are to be handled + * + * @param attr The new Eol value + */ + public void setEol( CrLf attr ) + { + String option = attr.getValue(); + if( option.equals( "asis" ) ) + { + eol = ASIS; + } + else if( option.equals( "cr" ) ) + { + eol = CR; + eolstr = "\r"; + } + else if( option.equals( "lf" ) ) + { + eol = LF; + eolstr = "\n"; + } + else + { + // Must be "crlf" + eol = CRLF; + eolstr = "\r\n"; + } + } + + /** + * Fixing Java source files? + * + * @param javafiles The new Javafiles value + */ + public void setJavafiles( boolean javafiles ) + { + this.javafiles = javafiles; + } + + /** + * Set the source dir to find the source text files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Specify how tab characters are to be handled + * + * @param attr The new Tab value + */ + public void setTab( AddAsisRemove attr ) + { + String option = attr.getValue(); + if( option.equals( "remove" ) ) + { + tabs = SPACES; + } + else if( option.equals( "asis" ) ) + { + tabs = ASIS; + } + else + { + // must be "add" + tabs = TABS; + } + } + + /** + * Specify tab length in characters + * + * @param tlength specify the length of tab in spaces, + * @exception BuildException Description of Exception + */ + public void setTablength( int tlength ) + throws BuildException + { + if( tlength < 2 || tlength > 80 ) + { + throw new BuildException( "tablength must be between 2 and 80", + location ); + } + tablength = tlength; + StringBuffer sp = new StringBuffer(); + for( int i = 0; i < tablength; i++ ) + { + sp.append( ' ' ); + } + spaces = sp.toString(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir and destdir + + if( srcDir == null ) + { + throw new BuildException( "srcdir attribute must be set!" ); + } + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir does not exist!" ); + } + if( !srcDir.isDirectory() ) + { + throw new BuildException( "srcdir is not a directory!" ); + } + if( destDir != null ) + { + if( !destDir.exists() ) + { + throw new BuildException( "destdir does not exist!" ); + } + if( !destDir.isDirectory() ) + { + throw new BuildException( "destdir is not a directory!" ); + } + } + + // log options used + log( "options:" + + " eol=" + + ( eol == ASIS ? "asis" : eol == CR ? "cr" : eol == LF ? "lf" : "crlf" ) + + " tab=" + ( tabs == TABS ? "add" : tabs == ASIS ? "asis" : "remove" ) + + " eof=" + ( ctrlz == ADD ? "add" : ctrlz == ASIS ? "asis" : "remove" ) + + " tablength=" + tablength + + " encoding=" + ( encoding == null ? "default" : encoding ), + Project.MSG_VERBOSE ); + + DirectoryScanner ds = super.getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + + for( int i = 0; i < files.length; i++ ) + { + processFile( files[i] ); + } + } + + /** + * Creates a Reader reading from a given file an taking the user defined + * encoding into account. + * + * @param f Description of Parameter + * @return The Reader value + * @exception IOException Description of Exception + */ + private Reader getReader( File f ) + throws IOException + { + return ( encoding == null ) ? new FileReader( f ) + : new InputStreamReader( new FileInputStream( f ), encoding ); + } + + + /** + * Scan a BufferLine forward from the 'next' pointer for the end of a + * character constant. Set 'lookahead' pointer to the character following + * the terminating quote. + * + * @param bufline Description of Parameter + * @param terminator Description of Parameter + * @exception BuildException Description of Exception + */ + private void endOfCharConst( OneLiner.BufferLine bufline, char terminator ) + throws BuildException + { + int ptr = bufline.getNext(); + int eol = bufline.length(); + char c; + ptr++;// skip past initial quote + while( ptr < eol ) + { + if( ( c = bufline.getChar( ptr++ ) ) == '\\' ) + { + ptr++; + } + else + { + if( c == terminator ) + { + bufline.setLookahead( ptr ); + return; + } + } + }// end of while (ptr < eol) + // Must have fallen through to the end of the line + throw new BuildException( "endOfCharConst: unterminated char constant" ); + } + + /** + * Scan a BufferLine for the next state changing token: the beginning of a + * single or multi-line comment, a character or a string constant. As a + * side-effect, sets the buffer state to the next state, and sets field + * lookahead to the first character of the state-changing token, or to the + * next eol character. + * + * @param bufline Description of Parameter + * @exception BuildException Description of Exception + */ + private void nextStateChange( OneLiner.BufferLine bufline ) + throws BuildException + { + int eol = bufline.length(); + int ptr = bufline.getNext(); + + // Look for next single or double quote, double slash or slash star + while( ptr < eol ) + { + switch ( bufline.getChar( ptr++ ) ) + { + case '\'': + bufline.setState( IN_CHAR_CONST ); + bufline.setLookahead( --ptr ); + return; + case '\"': + bufline.setState( IN_STR_CONST ); + bufline.setLookahead( --ptr ); + return; + case '/': + if( ptr < eol ) + { + if( bufline.getChar( ptr ) == '*' ) + { + bufline.setState( IN_MULTI_COMMENT ); + bufline.setLookahead( --ptr ); + return; + } + else if( bufline.getChar( ptr ) == '/' ) + { + bufline.setState( IN_SINGLE_COMMENT ); + bufline.setLookahead( --ptr ); + return; + } + } + break; + }// end of switch (bufline.getChar(ptr++)) + + }// end of while (ptr < eol) + // Eol is the next token + bufline.setLookahead( ptr ); + } + + + /** + * Process a BufferLine string which is not part of of a string constant. + * The start position of the string is given by the 'next' field. Sets the + * 'next' and 'column' fields in the BufferLine. + * + * @param bufline Description of Parameter + * @param end Description of Parameter + * @param outWriter Description of Parameter + */ + private void notInConstant( OneLiner.BufferLine bufline, int end, + BufferedWriter outWriter ) + { + // N.B. both column and string index are zero-based + // Process a string not part of a constant; + // i.e. convert tabs<->spaces as required + // This is NOT called for ASIS tab handling + int nextTab; + int nextStop; + int tabspaces; + String line = bufline.substring( bufline.getNext(), end ); + int place = 0;// Zero-based + int col = bufline.getColumn();// Zero-based + + // process sequences of white space + // first convert all tabs to spaces + linebuf.setLength( 0 ); + while( ( nextTab = line.indexOf( ( int )'\t', place ) ) >= 0 ) + { + linebuf.append( line.substring( place, nextTab ) );// copy to the TAB + col += nextTab - place; + tabspaces = tablength - ( col % tablength ); + linebuf.append( spaces.substring( 0, tabspaces ) ); + col += tabspaces; + place = nextTab + 1; + }// end of while + linebuf.append( line.substring( place, line.length() ) ); + // if converting to spaces, all finished + String linestring = new String( linebuf.toString() ); + if( tabs == REMOVE ) + { + try + { + outWriter.write( linestring ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + } + else + {// tabs == ADD + int tabCol; + linebuf2.setLength( 0 ); + place = 0; + col = bufline.getColumn(); + int placediff = col - 0; + // for the length of the string, cycle through the tab stop + // positions, checking for a space preceded by at least one + // other space at the tab stop. if so replace the longest possible + // preceding sequence of spaces with a tab. + nextStop = col + ( tablength - col % tablength ); + if( nextStop - col < 2 ) + { + linebuf2.append( linestring.substring( + place, nextStop - placediff ) ); + place = nextStop - placediff; + nextStop += tablength; + } + + for( ; nextStop - placediff <= linestring.length() + ; nextStop += tablength ) + { + for( tabCol = nextStop; + --tabCol - placediff >= place + && linestring.charAt( tabCol - placediff ) == ' ' + ; ) + { + ;// Loop for the side-effects + } + // tabCol is column index of the last non-space character + // before the next tab stop + if( nextStop - tabCol > 2 ) + { + linebuf2.append( linestring.substring( + place, ++tabCol - placediff ) ); + linebuf2.append( '\t' ); + } + else + { + linebuf2.append( linestring.substring( + place, nextStop - placediff ) ); + }// end of else + + place = nextStop - placediff; + }// end of for (nextStop ... ) + + // pick up that last bit, if any + linebuf2.append( linestring.substring( place, linestring.length() ) ); + + try + { + outWriter.write( linebuf2.toString() ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + }// end of else tabs == ADD + + // Set column position as modified by this method + bufline.setColumn( bufline.getColumn() + linestring.length() ); + bufline.setNext( end ); + + } + + + private void processFile( String file ) + throws BuildException + { + File srcFile = new File( srcDir, file ); + File destD = destDir == null ? srcDir : destDir; + File tmpFile = null; + BufferedWriter outWriter; + OneLiner.BufferLine line; + + // read the contents of the file + OneLiner lines = new OneLiner( srcFile ); + + try + { + // Set up the output Writer + try + { + tmpFile = fileUtils.createTempFile( "fixcrlf", "", destD ); + Writer writer = ( encoding == null ) ? new FileWriter( tmpFile ) + : new OutputStreamWriter( new FileOutputStream( tmpFile ), encoding ); + outWriter = new BufferedWriter( writer ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + while( lines.hasMoreElements() ) + { + // In-line states + int endComment; + + try + { + line = ( OneLiner.BufferLine )lines.nextElement(); + } + catch( NoSuchElementException e ) + { + throw new BuildException( e ); + } + + String lineString = line.getLineString(); + int linelen = line.length(); + + // Note - all of the following processing NOT done for + // tabs ASIS + + if( tabs == ASIS ) + { + // Just copy the body of the line across + try + { + outWriter.write( lineString ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + } + else + {// (tabs != ASIS) + int ptr; + + while( ( ptr = line.getNext() ) < linelen ) + { + + switch ( lines.getState() ) + { + + case NOTJAVA: + notInConstant( line, line.length(), outWriter ); + break; + case IN_MULTI_COMMENT: + if( ( endComment = + lineString.indexOf( "*/", line.getNext() ) + ) >= 0 ) + { + // End of multiLineComment on this line + endComment += 2;// Include the end token + lines.setState( LOOKING ); + } + else + { + endComment = linelen; + } + + notInConstant( line, endComment, outWriter ); + break; + case IN_SINGLE_COMMENT: + notInConstant( line, line.length(), outWriter ); + lines.setState( LOOKING ); + break; + case IN_CHAR_CONST: + case IN_STR_CONST: + // Got here from LOOKING by finding an opening "\'" + // next points to that quote character. + // Find the end of the constant. Watch out for + // backslashes. Literal tabs are left unchanged, and + // the column is adjusted accordingly. + + int begin = line.getNext(); + char terminator = ( lines.getState() == IN_STR_CONST + ? '\"' + : '\'' ); + endOfCharConst( line, terminator ); + while( line.getNext() < line.getLookahead() ) + { + if( line.getNextCharInc() == '\t' ) + { + line.setColumn( + line.getColumn() + + tablength - + line.getColumn() % tablength ); + } + else + { + line.incColumn(); + } + } + + // Now output the substring + try + { + outWriter.write( line.substring( begin, line.getNext() ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + lines.setState( LOOKING ); + + break; + + case LOOKING: + nextStateChange( line ); + notInConstant( line, line.getLookahead(), outWriter ); + break; + }// end of switch (state) + + }// end of while (line.getNext() < linelen) + + }// end of else (tabs != ASIS) + + try + { + outWriter.write( eolstr ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + }// end of while (lines.hasNext()) + + try + { + // Handle CTRLZ + if( ctrlz == ASIS ) + { + outWriter.write( lines.getEofStr() ); + } + else if( ctrlz == ADD ) + { + outWriter.write( CTRLZ ); + } + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + try + { + outWriter.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + File destFile = new File( destD, file ); + + try + { + lines.close(); + lines = null; + } + catch( IOException e ) + { + throw new BuildException( "Unable to close source file " + srcFile ); + } + + if( destFile.exists() ) + { + // Compare the destination with the temp file + log( "destFile exists", Project.MSG_DEBUG ); + if( !fileUtils.contentEquals( destFile, tmpFile ) ) + { + log( destFile + " is being written", Project.MSG_DEBUG ); + if( !destFile.delete() ) + { + throw new BuildException( "Unable to delete " + + destFile ); + } + if( !tmpFile.renameTo( destFile ) ) + { + throw new BuildException( + "Failed to transform " + srcFile + + " to " + destFile + + ". Couldn't rename temporary file: " + + tmpFile ); + } + + } + else + {// destination is equal to temp file + log( destFile + + " is not written, as the contents are identical", + Project.MSG_DEBUG ); + if( !tmpFile.delete() ) + { + throw new BuildException( "Unable to delete " + + tmpFile ); + } + } + } + else + {// destFile does not exist - write the temp file + log( "destFile does not exist", Project.MSG_DEBUG ); + if( !tmpFile.renameTo( destFile ) ) + { + throw new BuildException( + "Failed to transform " + srcFile + + " to " + destFile + + ". Couldn't rename temporary file: " + + tmpFile ); + } + } + + tmpFile = null; + + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + try + { + if( lines != null ) + { + lines.close(); + } + } + catch( IOException io ) + { + log( "Error closing " + srcFile, Project.MSG_ERR ); + }// end of catch + + if( tmpFile != null ) + { + tmpFile.delete(); + } + }// end of finally + } + + /** + * Enumerated attribute with the values "asis", "add" and "remove". + * + * @author RT + */ + public static class AddAsisRemove extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"add", "asis", "remove"}; + } + } + + /** + * Enumerated attribute with the values "asis", "cr", "lf" and "crlf". + * + * @author RT + */ + public static class CrLf extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"asis", "cr", "lf", "crlf"}; + } + } + + + class OneLiner implements Enumeration + { + + private int state = javafiles ? LOOKING : NOTJAVA; + + private StringBuffer eolStr = new StringBuffer( LINEBUFLEN ); + private StringBuffer eofStr = new StringBuffer(); + private StringBuffer line = new StringBuffer(); + private boolean reachedEof = false; + + private BufferedReader reader; + + public OneLiner( File srcFile ) + throws BuildException + { + try + { + reader = new BufferedReader + ( getReader( srcFile ), INBUFLEN ); + nextLine(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + public void setState( int state ) + { + this.state = state; + } + + public String getEofStr() + { + return eofStr.toString(); + } + + public int getState() + { + return state; + } + + public void close() + throws IOException + { + if( reader != null ) + { + reader.close(); + } + } + + public boolean hasMoreElements() + { + return !reachedEof; + } + + public Object nextElement() + throws NoSuchElementException + { + if( !hasMoreElements() ) + { + throw new NoSuchElementException( "OneLiner" ); + } + BufferLine tmpLine = + new BufferLine( line.toString(), eolStr.toString() ); + nextLine(); + return tmpLine; + } + + protected void nextLine() + throws BuildException + { + int ch = -1; + int eolcount = 0; + + eolStr.setLength( 0 ); + line.setLength( 0 ); + + try + { + ch = reader.read(); + while( ch != -1 && ch != '\r' && ch != '\n' ) + { + line.append( ( char )ch ); + ch = reader.read(); + } + + if( ch == -1 && line.length() == 0 ) + { + // Eof has been reached + reachedEof = true; + return; + } + + switch ( ( char )ch ) + { + case '\r': + // Check for \r, \r\n and \r\r\n + // Regard \r\r not followed by \n as two lines + ++eolcount; + eolStr.append( '\r' ); + switch ( ( char )( ch = reader.read() ) ) + { + case '\r': + if( ( char )( ch = reader.read() ) == '\n' ) + { + eolcount += 2; + eolStr.append( "\r\n" ); + } + break; + case '\n': + ++eolcount; + eolStr.append( '\n' ); + break; + }// end of switch ((char)(ch = reader.read())) + break; + case '\n': + ++eolcount; + eolStr.append( '\n' ); + break; + }// end of switch ((char) ch) + + // if at eolcount == 0 and trailing characters of string + // are CTRL-Zs, set eofStr + if( eolcount == 0 ) + { + int i = line.length(); + while( --i >= 0 && line.charAt( i ) == CTRLZ ) + { + // keep searching for the first ^Z + } + if( i < line.length() - 1 ) + { + // Trailing characters are ^Zs + // Construct new line and eofStr + eofStr.append( line.toString().substring( i + 1 ) ); + if( i < 0 ) + { + line.setLength( 0 ); + reachedEof = true; + } + else + { + line.setLength( i + 1 ); + } + } + + }// end of if (eolcount == 0) + + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + class BufferLine + { + private int next = 0; + private int column = 0; + private int lookahead = UNDEF; + private String eolStr; + private String line; + + public BufferLine( String line, String eolStr ) + throws BuildException + { + next = 0; + column = 0; + this.line = line; + this.eolStr = eolStr; + } + + public void setColumn( int col ) + { + column = col; + } + + public void setLookahead( int lookahead ) + { + this.lookahead = lookahead; + } + + public void setNext( int next ) + { + this.next = next; + } + + public void setState( int state ) + { + OneLiner.this.setState( state ); + } + + public char getChar( int i ) + { + return line.charAt( i ); + } + + public int getColumn() + { + return column; + } + + public String getEol() + { + return eolStr; + } + + public int getEolLength() + { + return eolStr.length(); + } + + public String getLineString() + { + return line; + } + + public int getLookahead() + { + return lookahead; + } + + public int getNext() + { + return next; + } + + public char getNextChar() + { + return getChar( next ); + } + + public char getNextCharInc() + { + return getChar( next++ ); + } + + public int getState() + { + return OneLiner.this.getState(); + } + + public int incColumn() + { + return column++; + } + + public int length() + { + return line.length(); + } + + public String substring( int begin ) + { + return line.substring( begin ); + } + + public String substring( int begin, int end ) + { + return line.substring( begin, end ); + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GUnzip.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GUnzip.java new file mode 100644 index 000000000..a4001fba5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GUnzip.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; +import org.apache.tools.ant.BuildException; + +/** + * Expands a file that has been compressed with the GZIP algorithm. Normally + * used to compress non-compressed archives such as TAR files. + * + * @author Stefan Bodewig + * @author Magesh Umasankar + */ + +public class GUnzip extends Unpack +{ + + private final static String DEFAULT_EXTENSION = ".gz"; + + protected String getDefaultExtension() + { + return DEFAULT_EXTENSION; + } + + protected void extract() + { + if( source.lastModified() > dest.lastModified() ) + { + log( "Expanding " + source.getAbsolutePath() + " to " + + dest.getAbsolutePath() ); + + FileOutputStream out = null; + GZIPInputStream zIn = null; + FileInputStream fis = null; + try + { + out = new FileOutputStream( dest ); + fis = new FileInputStream( source ); + zIn = new GZIPInputStream( fis ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = zIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + catch( IOException ioe ) + { + String msg = "Problem expanding gzip " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException ioex ) + {} + } + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + if( zIn != null ) + { + try + { + zIn.close(); + } + catch( IOException ioex ) + {} + } + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GZip.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GZip.java new file mode 100644 index 000000000..df35b0daf --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GZip.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.GZIPOutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Pack; + +/** + * Compresses a file with the GZIP algorithm. Normally used to compress + * non-compressed archives such as TAR files. + * + * @author James Davidson duncan@x180.com + * @author Jon S. Stevens jon@clearink.com + * @author Magesh Umasankar + */ + +public class GZip extends Pack +{ + protected void pack() + { + GZIPOutputStream zOut = null; + try + { + zOut = new GZIPOutputStream( new FileOutputStream( zipFile ) ); + zipFile( source, zOut ); + } + catch( IOException ioe ) + { + String msg = "Problem creating gzip " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( zOut != null ) + { + try + { + // close up + zOut.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GenerateKey.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GenerateKey.java new file mode 100644 index 000000000..06a0425ea --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GenerateKey.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; + +/** + * Generates a key. + * + * @author Peter Donald + */ +public class GenerateKey extends Task +{ + + /** + * The alias of signer. + */ + protected String alias; + protected String dname; + protected DistinguishedName expandedDname; + protected String keyalg; + protected String keypass; + protected int keysize; + + /** + * The name of keystore file. + */ + protected String keystore; + + protected String sigalg; + protected String storepass; + protected String storetype; + protected int validity; + protected boolean verbose; + + public void setAlias( final String alias ) + { + this.alias = alias; + } + + public void setDname( final String dname ) + { + if( null != expandedDname ) + { + throw new BuildException( "It is not possible to specify dname both " + + "as attribute and element." ); + } + this.dname = dname; + } + + public void setKeyalg( final String keyalg ) + { + this.keyalg = keyalg; + } + + public void setKeypass( final String keypass ) + { + this.keypass = keypass; + } + + public void setKeysize( final String keysize ) + throws BuildException + { + try + { + this.keysize = Integer.parseInt( keysize ); + } + catch( final NumberFormatException nfe ) + { + throw new BuildException( "KeySize attribute should be a integer" ); + } + } + + public void setKeystore( final String keystore ) + { + this.keystore = keystore; + } + + public void setSigalg( final String sigalg ) + { + this.sigalg = sigalg; + } + + public void setStorepass( final String storepass ) + { + this.storepass = storepass; + } + + public void setStoretype( final String storetype ) + { + this.storetype = storetype; + } + + public void setValidity( final String validity ) + throws BuildException + { + try + { + this.validity = Integer.parseInt( validity ); + } + catch( final NumberFormatException nfe ) + { + throw new BuildException( "Validity attribute should be a integer" ); + } + } + + public void setVerbose( final boolean verbose ) + { + this.verbose = verbose; + } + + public DistinguishedName createDname() + throws BuildException + { + if( null != expandedDname ) + { + throw new BuildException( "DName sub-element can only be specified once." ); + } + if( null != dname ) + { + throw new BuildException( "It is not possible to specify dname both " + + "as attribute and element." ); + } + expandedDname = new DistinguishedName(); + return expandedDname; + } + + public void execute() + throws BuildException + { + if( project.getJavaVersion().equals( Project.JAVA_1_1 ) ) + { + throw new BuildException( "The genkey task is only available on JDK" + + " versions 1.2 or greater" ); + } + + if( null == alias ) + { + throw new BuildException( "alias attribute must be set" ); + } + + if( null == storepass ) + { + throw new BuildException( "storepass attribute must be set" ); + } + + if( null == dname && null == expandedDname ) + { + throw new BuildException( "dname must be set" ); + } + + final StringBuffer sb = new StringBuffer(); + + sb.append( "keytool -genkey " ); + + if( verbose ) + { + sb.append( "-v " ); + } + + sb.append( "-alias \"" ); + sb.append( alias ); + sb.append( "\" " ); + + if( null != dname ) + { + sb.append( "-dname \"" ); + sb.append( dname ); + sb.append( "\" " ); + } + + if( null != expandedDname ) + { + sb.append( "-dname \"" ); + sb.append( expandedDname ); + sb.append( "\" " ); + } + + if( null != keystore ) + { + sb.append( "-keystore \"" ); + sb.append( keystore ); + sb.append( "\" " ); + } + + if( null != storepass ) + { + sb.append( "-storepass \"" ); + sb.append( storepass ); + sb.append( "\" " ); + } + + if( null != storetype ) + { + sb.append( "-storetype \"" ); + sb.append( storetype ); + sb.append( "\" " ); + } + + sb.append( "-keypass \"" ); + if( null != keypass ) + { + sb.append( keypass ); + } + else + { + sb.append( storepass ); + } + sb.append( "\" " ); + + if( null != sigalg ) + { + sb.append( "-sigalg \"" ); + sb.append( sigalg ); + sb.append( "\" " ); + } + + if( null != keyalg ) + { + sb.append( "-keyalg \"" ); + sb.append( keyalg ); + sb.append( "\" " ); + } + + if( 0 < keysize ) + { + sb.append( "-keysize \"" ); + sb.append( keysize ); + sb.append( "\" " ); + } + + if( 0 < validity ) + { + sb.append( "-validity \"" ); + sb.append( validity ); + sb.append( "\" " ); + } + + log( "Generating Key for " + alias ); + final ExecTask cmd = ( ExecTask )project.createTask( "exec" ); + cmd.setCommand( new Commandline( sb.toString() ) ); + cmd.setFailonerror( true ); + cmd.setTaskName( getTaskName() ); + cmd.execute(); + } + + public static class DistinguishedName + { + + private Vector params = new Vector(); + private String name; + private String path; + + public Enumeration getParams() + { + return params.elements(); + } + + public Object createParam() + { + DnameParam param = new DnameParam(); + params.addElement( param ); + + return param; + } + + public String encode( final String string ) + { + int end = string.indexOf( ',' ); + + if( -1 == end ) + return string; + + final StringBuffer sb = new StringBuffer(); + + int start = 0; + + while( -1 != end ) + { + sb.append( string.substring( start, end ) ); + sb.append( "\\," ); + start = end + 1; + end = string.indexOf( ',', start ); + } + + sb.append( string.substring( start ) ); + + return sb.toString(); + } + + public String toString() + { + final int size = params.size(); + final StringBuffer sb = new StringBuffer(); + boolean firstPass = true; + + for( int i = 0; i < size; i++ ) + { + if( !firstPass ) + { + sb.append( " ," ); + } + firstPass = false; + + final DnameParam param = ( DnameParam )params.elementAt( i ); + sb.append( encode( param.getName() ) ); + sb.append( '=' ); + sb.append( encode( param.getValue() ) ); + } + + return sb.toString(); + } + } + + public static class DnameParam + { + private String name; + private String value; + + public void setName( String name ) + { + this.name = name; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Get.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Get.java new file mode 100644 index 000000000..6c1eb4451 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Get.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.Date; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Get a particular file from a URL source. Options include verbose reporting, + * timestamp based fetches and controlling actions on failures. NB: access + * through a firewall only works if the whole Java runtime is correctly + * configured. + * + * @author costin@dnt.ro + * @author gg@grtmail.com (Added Java 1.1 style HTTP basic auth) + */ +public class Get extends Task +{// required + private boolean verbose = false; + private boolean useTimestamp = false;//off by default + private boolean ignoreErrors = false; + private String uname = null; + private String pword = null;// required + private File dest; + private URL source; + + /** + * Where to copy the source file. + * + * @param dest Path to file. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Don't stop if get fails if set to "true". + * + * @param v if "true" then don't report download errors up to ant + */ + public void setIgnoreErrors( boolean v ) + { + ignoreErrors = v; + } + + /** + * password for the basic auth. + * + * @param p password for authentication + */ + public void setPassword( String p ) + { + this.pword = p; + } + + /** + * Set the URL. + * + * @param u URL for the file. + */ + public void setSrc( URL u ) + { + this.source = u; + } + + /** + * Use timestamps, if set to "true".

              + * + * In this situation, the if-modified-since header is set so that the file + * is only fetched if it is newer than the local file (or there is no local + * file) This flag is only valid on HTTP connections, it is ignored in other + * cases. When the flag is set, the local copy of the downloaded file will + * also have its timestamp set to the remote file time.
              + * Note that remote files of date 1/1/1970 (GMT) are treated as 'no + * timestamp', and web servers often serve files with a timestamp in the + * future by replacing their timestamp with that of the current time. Also, + * inter-computer clock differences can cause no end of grief. + * + * @param v "true" to enable file time fetching + */ + public void setUseTimestamp( boolean v ) + { + if( project.getJavaVersion() != Project.JAVA_1_1 ) + { + useTimestamp = v; + } + } + + + /** + * Username for basic auth. + * + * @param u username for authentication + */ + public void setUsername( String u ) + { + this.uname = u; + } + + /** + * Be verbose, if set to "true". + * + * @param v if "true" then be verbose + */ + public void setVerbose( boolean v ) + { + verbose = v; + } + + + /** + * Does the work. + * + * @exception BuildException Thrown in unrecoverable error. + */ + public void execute() + throws BuildException + { + if( source == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( dest == null ) + { + throw new BuildException( "dest attribute is required", location ); + } + + if( dest.exists() && dest.isDirectory() ) + { + throw new BuildException( "The specified destination is a directory", + location ); + } + + if( dest.exists() && !dest.canWrite() ) + { + throw new BuildException( "Can't write to " + dest.getAbsolutePath(), + location ); + } + + try + { + + log( "Getting: " + source ); + + //set the timestamp to the file date. + long timestamp = 0; + + boolean hasTimestamp = false; + if( useTimestamp && dest.exists() ) + { + timestamp = dest.lastModified(); + if( verbose ) + { + Date t = new Date( timestamp ); + log( "local file date : " + t.toString() ); + } + + hasTimestamp = true; + } + + //set up the URL connection + URLConnection connection = source.openConnection(); + //modify the headers + //NB: things like user authentication could go in here too. + if( useTimestamp && hasTimestamp ) + { + connection.setIfModifiedSince( timestamp ); + } + // prepare Java 1.1 style credentials + if( uname != null || pword != null ) + { + String up = uname + ":" + pword; + String encoding; + // check to see if sun's Base64 encoder is available. + try + { + sun.misc.BASE64Encoder encoder = + ( sun.misc.BASE64Encoder )Class.forName( "sun.misc.BASE64Encoder" ).newInstance(); + encoding = encoder.encode( up.getBytes() ); + + } + catch( Exception ex ) + {// sun's base64 encoder isn't available + Base64Converter encoder = new Base64Converter(); + encoding = encoder.encode( up.getBytes() ); + } + connection.setRequestProperty( "Authorization", "Basic " + encoding ); + } + + //connect to the remote site (may take some time) + connection.connect(); + //next test for a 304 result (HTTP only) + if( connection instanceof HttpURLConnection ) + { + HttpURLConnection httpConnection = ( HttpURLConnection )connection; + if( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED ) + { + //not modified so no file download. just return instead + //and trace out something so the user doesn't think that the + //download happened when it didnt + log( "Not modified - so not downloaded" ); + return; + } + // test for 401 result (HTTP only) + if( httpConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED ) + { + log( "Not authorized - check " + dest + " for details" ); + return; + } + + } + + //REVISIT: at this point even non HTTP connections may support the if-modified-since + //behaviour -we just check the date of the content and skip the write if it is not + //newer. Some protocols (FTP) dont include dates, of course. + + FileOutputStream fos = new FileOutputStream( dest ); + + InputStream is = null; + for( int i = 0; i < 3; i++ ) + { + try + { + is = connection.getInputStream(); + break; + } + catch( IOException ex ) + { + log( "Error opening connection " + ex ); + } + } + if( is == null ) + { + log( "Can't get " + source + " to " + dest ); + if( ignoreErrors ) + return; + throw new BuildException( "Can't get " + source + " to " + dest, + location ); + } + + byte[] buffer = new byte[100 * 1024]; + int length; + + while( ( length = is.read( buffer ) ) >= 0 ) + { + fos.write( buffer, 0, length ); + if( verbose ) + System.out.print( "." ); + } + if( verbose ) + System.out.println(); + fos.close(); + is.close(); + + //if (and only if) the use file time option is set, then the + //saved file now has its timestamp set to that of the downloaded file + if( useTimestamp ) + { + long remoteTimestamp = connection.getLastModified(); + if( verbose ) + { + Date t = new Date( remoteTimestamp ); + log( "last modified = " + t.toString() + + ( ( remoteTimestamp == 0 ) ? " - using current time instead" : "" ) ); + } + if( remoteTimestamp != 0 ) + touchFile( dest, remoteTimestamp ); + } + } + catch( IOException ioe ) + { + log( "Error getting " + source + " to " + dest ); + if( ignoreErrors ) + return; + throw new BuildException( ioe); + } + } + + /** + * set the timestamp of a named file to a specified time. + * + * @param file Description of Parameter + * @param timemillis Description of Parameter + * @return true if it succeeded. False means that this is a java1.1 system + * and that file times can not be set + * @exception BuildException Thrown in unrecoverable error. Likely this + * comes from file access failures. + */ + protected boolean touchFile( File file, long timemillis ) + throws BuildException + { + + if( project.getJavaVersion() != Project.JAVA_1_1 ) + { + Touch touch = ( Touch )project.createTask( "touch" ); + touch.setOwningTarget( target ); + touch.setTaskName( getTaskName() ); + touch.setLocation( getLocation() ); + touch.setFile( file ); + touch.setMillis( timemillis ); + touch.touch(); + return true; + } + else + { + return false; + } + } + + /** + * BASE 64 encoding of a String or an array of bytes. Based on RFC 1421. + * + * @author Unknown + * @author Gautam Guliani + */ + + class Base64Converter + { + + public final char[] alphabet = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0 to 7 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 8 to 15 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 16 to 23 + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 24 to 31 + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 32 to 39 + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 40 to 47 + 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 48 to 55 + '4', '5', '6', '7', '8', '9', '+', '/'};// 56 to 63 + + + public String encode( String s ) + { + return encode( s.getBytes() ); + } + + public String encode( byte[] octetString ) + { + int bits24; + int bits6; + + char[] out + = new char[( ( octetString.length - 1 ) / 3 + 1 ) * 4]; + + int outIndex = 0; + int i = 0; + + while( ( i + 3 ) <= octetString.length ) + { + // store the octets + bits24 = ( octetString[i++] & 0xFF ) << 16; + bits24 |= ( octetString[i++] & 0xFF ) << 8; + + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x00000FC0 ) >> 6; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0000003F ); + out[outIndex++] = alphabet[bits6]; + } + + if( octetString.length - i == 2 ) + { + // store the octets + bits24 = ( octetString[i] & 0xFF ) << 16; + bits24 |= ( octetString[i + 1] & 0xFF ) << 8; + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x00000FC0 ) >> 6; + out[outIndex++] = alphabet[bits6]; + + // padding + out[outIndex++] = '='; + } + else if( octetString.length - i == 1 ) + { + // store the octets + bits24 = ( octetString[i] & 0xFF ) << 16; + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + + // padding + out[outIndex++] = '='; + out[outIndex++] = '='; + } + + return new String( out ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Input.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Input.java new file mode 100644 index 000000000..caf080870 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Input.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.*; + + +/** + * Ant task to read input line from console. + * + * @author Ulrich Schmidt + */ +public class Input extends Task +{ + private String validargs = null; + private String message = ""; + private String addproperty = null; + private String input = null; + + /** + * No arg constructor. + */ + public Input() { } + + /** + * Defines the name of a property to be created from input. Behaviour is + * according to property task which means that existing properties cannot be + * overriden. + * + * @param addproperty Name for the property to be created from input + */ + public void setAddproperty( String addproperty ) + { + this.addproperty = addproperty; + } + + /** + * Sets the Message which gets displayed to the user during the build run. + * + * @param message The message to be displayed. + */ + public void setMessage( String message ) + { + this.message = message; + } + + /** + * Sets surrogate input to allow automated testing. + * + * @param testinput The new Testinput value + */ + public void setTestinput( String testinput ) + { + this.input = testinput; + } + + /** + * Defines valid input parameters as comma separated String. If set, input + * task will reject any input not defined as accepted and requires the user + * to reenter it. Validargs are case sensitive. If you want 'a' and 'A' to + * be accepted you need to define both values as accepted arguments. + * + * @param validargs A comma separated String defining valid input args. + */ + public void setValidargs( String validargs ) + { + this.validargs = validargs; + } + + // copied n' pasted from org.apache.tools.ant.taskdefs.Exit + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + /** + * Actual test method executed by jakarta-ant. + * + * @exception BuildException + */ + public void execute() + throws BuildException + { + Vector accept = null; + if( validargs != null ) + { + accept = new Vector(); + StringTokenizer stok = new StringTokenizer( validargs, ",", false ); + while( stok.hasMoreTokens() ) + { + accept.addElement( stok.nextToken() ); + } + } + log( message, Project.MSG_WARN ); + if( input == null ) + { + try + { + BufferedReader in = new BufferedReader( new InputStreamReader( System.in ) ); + input = in.readLine(); + if( accept != null ) + { + while( !accept.contains( input ) ) + { + log( message, Project.MSG_WARN ); + input = in.readLine(); + } + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to read input from Console.", e ); + } + } + // not quite the original intention of this task but for the sake + // of testing ;-) + else + { + if( accept != null && ( !accept.contains( input ) ) ) + { + throw new BuildException( "Invalid input please reenter." ); + } + } + // adopted from org.apache.tools.ant.taskdefs.Property + if( addproperty != null ) + { + if( project.getProperty( addproperty ) == null ) + { + project.setProperty( addproperty, input ); + } + else + { + log( "Override ignored for " + addproperty, Project.MSG_VERBOSE ); + } + } + } +} + + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jar.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jar.java new file mode 100644 index 000000000..1b2d8a976 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jar.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.*; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.FileScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + +/** + * Creates a JAR archive. + * + * @author James Davidson duncan@x180.com + */ +public class Jar extends Zip +{ + /** + * The index file name. + */ + private final static String INDEX_NAME = "META-INF/INDEX.LIST"; + + /** + * true if a manifest has been specified in the task + */ + private boolean buildFileManifest = false; + + /** + * jar index is JDK 1.3+ only + */ + private boolean index = false; + private Manifest execManifest; + private Manifest manifest; + + private File manifestFile; + + /** + * constructor + */ + public Jar() + { + super(); + archiveType = "jar"; + emptyBehavior = "create"; + setEncoding( "UTF8" ); + } + + /** + * Set whether or not to create an index list for classes to speed up + * classloading. + * + * @param flag The new Index value + */ + public void setIndex( boolean flag ) + { + index = flag; + } + + /** + * @param jarFile The new Jarfile value + * @deprecated use setFile(File) instead. + */ + public void setJarfile( File jarFile ) + { + log( "DEPRECATED - The jarfile attribute is deprecated. Use file attribute instead." ); + setFile( jarFile ); + } + + public void setManifest( File manifestFile ) + { + if( !manifestFile.exists() ) + { + throw new BuildException( "Manifest file: " + manifestFile + " does not exist.", + getLocation() ); + } + + this.manifestFile = manifestFile; + + Reader r = null; + try + { + r = new FileReader( manifestFile ); + Manifest newManifest = new Manifest( r ); + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + manifest.merge( newManifest ); + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest: " + manifestFile, e, getLocation() ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest file: " + manifestFile, e ); + } + finally + { + if( r != null ) + { + try + { + r.close(); + } + catch( IOException e ) + { + // do nothing + } + } + } + } + + public void setWhenempty( WhenEmpty we ) + { + log( "JARs are never empty, they contain at least a manifest file", + Project.MSG_WARN ); + } + + public void addConfiguredManifest( Manifest newManifest ) + throws ManifestException + { + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + manifest.merge( newManifest ); + buildFileManifest = true; + } + + public void addMetainf( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "META-INF/" ); + super.addFileset( fs ); + } + + /** + * Check whether the archive is up-to-date; + * + * @param scanners list of prepared scanners containing files to archive + * @param zipFile intended archive file (may or may not exist) + * @return true if nothing need be done (may have done something already); + * false if archive creation should proceed + * @exception BuildException if it likes + */ + protected boolean isUpToDate( FileScanner[] scanners, File zipFile ) + throws BuildException + { + // need to handle manifest as a special check + if( buildFileManifest || manifestFile == null ) + { + java.util.zip.ZipFile theZipFile = null; + try + { + theZipFile = new java.util.zip.ZipFile( zipFile ); + java.util.zip.ZipEntry entry = theZipFile.getEntry( "META-INF/MANIFEST.MF" ); + if( entry == null ) + { + log( "Updating jar since the current jar has no manifest", Project.MSG_VERBOSE ); + return false; + } + Manifest currentManifest = new Manifest( new InputStreamReader( theZipFile.getInputStream( entry ) ) ); + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + if( !currentManifest.equals( manifest ) ) + { + log( "Updating jar since jar manifest has changed", Project.MSG_VERBOSE ); + return false; + } + } + catch( Exception e ) + { + // any problems and we will rebuild + log( "Updating jar since cannot read current jar manifest: " + e.getClass().getName() + e.getMessage(), + Project.MSG_VERBOSE ); + return false; + } + finally + { + if( theZipFile != null ) + { + try + { + theZipFile.close(); + } + catch( IOException e ) + { + //ignore + } + } + } + } + else if( manifestFile.lastModified() > zipFile.lastModified() ) + { + return false; + } + return super.isUpToDate( scanners, zipFile ); + } + + /** + * Make sure we don't think we already have a MANIFEST next time this task + * gets executed. + */ + protected void cleanUp() + { + super.cleanUp(); + } + + protected boolean createEmptyZip( File zipFile ) + { + // Jar files always contain a manifest and can never be empty + return false; + } + + protected void finalizeZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + if( index ) + { + createIndexList( zOut ); + } + } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + try + { + execManifest = Manifest.getDefaultManifest(); + + if( manifest != null ) + { + execManifest.merge( manifest ); + } + for( Enumeration e = execManifest.getWarnings(); e.hasMoreElements(); ) + { + log( "Manifest warning: " + ( String )e.nextElement(), Project.MSG_WARN ); + } + + zipDir( null, zOut, "META-INF/" ); + // time to write the manifest + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter( baos ); + execManifest.write( writer ); + writer.flush(); + + ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() ); + super.zipFile( bais, zOut, "META-INF/MANIFEST.MF", System.currentTimeMillis() ); + super.initZipOutputStream( zOut ); + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest", e, getLocation() ); + } + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is META-INF/MANIFEST.MF, we warn if it's not the + // one specified in the "manifest" attribute - or if it's being added twice, + // meaning the same file is specified by the "manifeset" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "META-INF/MANIFEST.MF" ) ) + { + log( "Warning: selected " + archiveType + " files include a META-INF/MANIFEST.MF which will be ignored " + + "(please use manifest attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + } + + } + + protected void zipFile( InputStream is, ZipOutputStream zOut, String vPath, long lastModified ) + throws IOException + { + // If the file being added is META-INF/MANIFEST.MF, we merge it with the + // current manifest + if( vPath.equalsIgnoreCase( "META-INF/MANIFEST.MF" ) ) + { + try + { + zipManifestEntry( is ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest file: ", e ); + } + } + else + { + super.zipFile( is, zOut, vPath, lastModified ); + } + } + + /** + * Create the index list to speed up classloading. This is a JDK 1.3+ + * specific feature and is enabled by default. {@link + * http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#JAR%20Index} + * + * @param zOut the zip stream representing the jar being built. + * @throws IOException thrown if there is an error while creating the index + * and adding it to the zip stream. + */ + private void createIndexList( ZipOutputStream zOut ) + throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // encoding must be UTF8 as specified in the specs. + PrintWriter writer = new PrintWriter( new OutputStreamWriter( baos, "UTF8" ) ); + + // version-info blankline + writer.println( "JarIndex-Version: 1.0" ); + writer.println(); + + // header newline + writer.println( zipFile.getName() ); + + // JarIndex is sorting the directories by ascending order. + // it's painful to do in JDK 1.1 and it has no value but cosmetic + // since it will be read into a hashtable by the classloader. + Enumeration enum = addedDirs.keys(); + while( enum.hasMoreElements() ) + { + String dir = ( String )enum.nextElement(); + + // try to be smart, not to be fooled by a weird directory name + // @fixme do we need to check for directories starting by ./ ? + dir = dir.replace( '\\', '/' ); + int pos = dir.lastIndexOf( '/' ); + if( pos != -1 ) + { + dir = dir.substring( 0, pos ); + } + + // looks like nothing from META-INF should be added + // and the check is not case insensitive. + // see sun.misc.JarIndex + if( dir.startsWith( "META-INF" ) ) + { + continue; + } + // name newline + writer.println( dir ); + } + + writer.flush(); + ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() ); + super.zipFile( bais, zOut, INDEX_NAME, System.currentTimeMillis() ); + } + + + + /** + * Handle situation when we encounter a manifest file If we haven't been + * given one, we use this one. If we have, we merge the manifest in, + * provided it is a new file and not the old one from the JAR we are + * updating + * + * @param is Description of Parameter + * @exception IOException Description of Exception + */ + private void zipManifestEntry( InputStream is ) + throws IOException + { + try + { + if( execManifest == null ) + { + execManifest = new Manifest( new InputStreamReader( is ) ); + } + else if( isAddingNewFiles() ) + { + execManifest.merge( new Manifest( new InputStreamReader( is ) ) ); + } + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest", e, getLocation() ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Java.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Java.java new file mode 100644 index 000000000..060a331c6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Java.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ExitException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * This task acts as a loader for java applications but allows to use the same + * JVM for the called application thus resulting in much faster operation. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Stefan Bodewig + */ +public class Java extends Task +{ + + private CommandlineJava cmdl = new CommandlineJava(); + private boolean fork = false; + private File dir = null; + private PrintStream outStream = null; + private boolean failOnError = false; + private File out; + + /** + * Set the command line arguments for the class. + * + * @param s The new Args value + */ + public void setArgs( String s ) + { + log( "The args attribute is deprecated. " + + "Please use nested arg elements.", + Project.MSG_WARN ); + cmdl.createArgument().setLine( s ); + } + + /** + * Set the class name. + * + * @param s The new Classname value + * @exception BuildException Description of Exception + */ + public void setClassname( String s ) + throws BuildException + { + if( cmdl.getJar() != null ) + { + throw new BuildException( "Cannot use 'jar' and 'classname' attributes in same command" ); + } + cmdl.setClassname( s ); + } + + /** + * Set the classpath to be used for this compilation. + * + * @param s The new Classpath value + */ + public void setClasspath( Path s ) + { + createClasspath().append( s ); + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * The working directory of the process + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * Throw a BuildException if process returns non 0. + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Set the forking flag. + * + * @param s The new Fork value + */ + public void setFork( boolean s ) + { + this.fork = s; + } + + public void setJVMVersion( String value ) + { + cmdl.setVmversion( value ); + } + + /** + * set the jar name... + * + * @param jarfile The new Jar value + * @exception BuildException Description of Exception + */ + public void setJar( File jarfile ) + throws BuildException + { + if( cmdl.getClassname() != null ) + { + throw new BuildException( "Cannot use 'jar' and 'classname' attributes in same command." ); + } + cmdl.setJar( jarfile.getAbsolutePath() ); + } + + /** + * Set the command used to start the VM (only if fork==false). + * + * @param s The new Jvm value + */ + public void setJvm( String s ) + { + cmdl.setVm( s ); + } + + /** + * Set the command line arguments for the JVM. + * + * @param s The new Jvmargs value + */ + public void setJvmargs( String s ) + { + log( "The jvmargs attribute is deprecated. " + + "Please use nested jvmarg elements.", + Project.MSG_WARN ); + cmdl.createVmArgument().setLine( s ); + } + + /** + * -mx or -Xmx depending on VM version + * + * @param max The new Maxmemory value + */ + public void setMaxmemory( String max ) + { + cmdl.setMaxmemory( max ); + } + + /** + * File the output of the process is redirected to. + * + * @param out The new Output value + */ + public void setOutput( File out ) + { + this.out = out; + } + + /** + * Add a nested sysproperty element. + * + * @param sysp The feature to be added to the Sysproperty attribute + */ + public void addSysproperty( Environment.Variable sysp ) + { + cmdl.addSysproperty( sysp ); + } + + /** + * Clear out the arguments to this java task. + */ + public void clearArgs() + { + cmdl.clearJavaArgs(); + } + + /** + * Creates a nested arg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createArg() + { + return cmdl.createArgument(); + } + + /** + * Creates a nested classpath element + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return cmdl.createClasspath( project ).createPath(); + } + + /** + * Creates a nested jvmarg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createJvmarg() + { + return cmdl.createVmArgument(); + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + int err = -1; + if( ( err = executeJava() ) != 0 ) + { + if( failOnError ) + { + throw new BuildException( "Java returned: " + err, location ); + } + else + { + log( "Java Result: " + err, Project.MSG_ERR ); + } + } + } + + /** + * Do the execution and return a return code. + * + * @return the return code from the execute java class if it was executed in + * a separate VM (fork = "yes"). + * @exception BuildException Description of Exception + */ + public int executeJava() + throws BuildException + { + String classname = cmdl.getClassname(); + if( classname == null && cmdl.getJar() == null ) + { + throw new BuildException( "Classname must not be null." ); + } + if( !fork && cmdl.getJar() != null ) + { + throw new BuildException( "Cannot execute a jar in non-forked mode. Please set fork='true'. " ); + } + + if( fork ) + { + log( "Forking " + cmdl.toString(), Project.MSG_VERBOSE ); + + return run( cmdl.getCommandline() ); + } + else + { + if( cmdl.getVmCommand().size() > 1 ) + { + log( "JVM args ignored when same JVM is used.", Project.MSG_WARN ); + } + if( dir != null ) + { + log( "Working directory ignored when same JVM is used.", Project.MSG_WARN ); + } + + log( "Running in same VM " + cmdl.getJavaCommand().toString(), + Project.MSG_VERBOSE ); + try + { + run( cmdl ); + return 0; + } + catch( ExitException ex ) + { + return ex.getStatus(); + } + } + } + + protected void handleErrorOutput( String line ) + { + if( outStream != null ) + { + outStream.println( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( outStream != null ) + { + outStream.println( line ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Executes the given classname with the given arguments as it was a command + * line application. + * + * @param classname Description of Parameter + * @param args Description of Parameter + * @exception BuildException Description of Exception + */ + protected void run( String classname, Vector args ) + throws BuildException + { + CommandlineJava cmdj = new CommandlineJava(); + cmdj.setClassname( classname ); + for( int i = 0; i < args.size(); i++ ) + { + cmdj.createArgument().setValue( ( String )args.elementAt( i ) ); + } + run( cmdj ); + } + + /** + * Executes the given classname with the given arguments as it was a command + * line application. + * + * @param command Description of Parameter + * @exception BuildException Description of Exception + */ + private void run( CommandlineJava command ) + throws BuildException + { + ExecuteJava exe = new ExecuteJava(); + exe.setJavaCommand( command.getJavaCommand() ); + exe.setClasspath( command.getClasspath() ); + exe.setSystemProperties( command.getSystemProperties() ); + if( out != null ) + { + try + { + outStream = new PrintStream( new FileOutputStream( out ) ); + exe.execute( project ); + } + catch( IOException io ) + { + throw new BuildException( io ); + } + finally + { + if( outStream != null ) + { + outStream.close(); + } + } + } + else + { + exe.execute( project ); + } + } + + /** + * Executes the given classname with the given arguments in a separate VM. + * + * @param command Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int run( String[] command ) + throws BuildException + { + FileOutputStream fos = null; + try + { + Execute exe = null; + if( out == null ) + { + exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), + null ); + } + else + { + fos = new FileOutputStream( out ); + exe = new Execute( new PumpStreamHandler( fos ), null ); + } + + exe.setAntRun( project ); + + if( dir == null ) + { + dir = project.getBaseDir(); + } + else if( !dir.exists() || !dir.isDirectory() ) + { + throw new BuildException( dir.getAbsolutePath() + " is not a valid directory", + location ); + } + + exe.setWorkingDirectory( dir ); + + exe.setCommandline( command ); + try + { + return exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + catch( IOException io ) + { + throw new BuildException( io ); + } + finally + { + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException io ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javac.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javac.java new file mode 100644 index 000000000..7793bc4f2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javac.java @@ -0,0 +1,941 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.compilers.CompilerAdapter; +import org.apache.tools.ant.taskdefs.compilers.CompilerAdapterFactory; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.GlobPatternMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Task to compile Java source files. This task can take the following + * arguments: + *

                + *
              • sourcedir + *
              • destdir + *
              • deprecation + *
              • classpath + *
              • bootclasspath + *
              • extdirs + *
              • optimize + *
              • debug + *
              • encoding + *
              • target + *
              • depend + *
              • vebose + *
              • failonerror + *
              • includeantruntime + *
              • includejavaruntime + *
              • source + *
              + * Of these arguments, the sourcedir and destdir are required.

              + * + * When this task executes, it will recursively scan the sourcedir and destdir + * looking for Java source files to compile. This task makes its compile + * decision based on timestamp. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ + +public class Javac extends MatchingTask +{ + + private final static String FAIL_MSG + = "Compile failed, messages should have been provided."; + private boolean debug = false; + private boolean optimize = false; + private boolean deprecation = false; + private boolean depend = false; + private boolean verbose = false; + private boolean includeAntRuntime = true; + private boolean includeJavaRuntime = false; + private String fork = "false"; + private String forkedExecutable = null; + private boolean nowarn = false; + private Vector implementationSpecificArgs = new Vector(); + + protected boolean failOnError = true; + protected File[] compileList = new File[0]; + private Path bootclasspath; + private Path compileClasspath; + private String debugLevel; + private File destDir; + private String encoding; + private Path extdirs; + private String memoryInitialSize; + private String memoryMaximumSize; + + private String source; + + private Path src; + private String target; + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + /** + * Sets the bootclasspath that will be used to compile the classes against. + * + * @param bootclasspath The new Bootclasspath value + */ + public void setBootclasspath( Path bootclasspath ) + { + if( this.bootclasspath == null ) + { + this.bootclasspath = bootclasspath; + } + else + { + this.bootclasspath.append( bootclasspath ); + } + } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the debug flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Set the value of debugLevel. + * + * @param v Value to assign to debugLevel. + */ + public void setDebugLevel( String v ) + { + this.debugLevel = v; + } + + /** + * Set the depend flag. + * + * @param depend The new Depend value + */ + public void setDepend( boolean depend ) + { + this.depend = depend; + } + + /** + * Set the deprecation flag. + * + * @param deprecation The new Deprecation value + */ + public void setDeprecation( boolean deprecation ) + { + this.deprecation = deprecation; + } + + /** + * Set the destination directory into which the Java source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the Java source file encoding name. + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Sets the extension directories that will be used during the compilation. + * + * @param extdirs The new Extdirs value + */ + public void setExtdirs( Path extdirs ) + { + if( this.extdirs == null ) + { + this.extdirs = extdirs; + } + else + { + this.extdirs.append( extdirs ); + } + } + + /** + * Throw a BuildException if compilation fails + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Sets whether to fork the javac compiler. + * + * @param f "true|false|on|off|yes|no" or the name of the javac executable. + */ + public void setFork( String f ) + { + if( f.equalsIgnoreCase( "on" ) + || f.equalsIgnoreCase( "true" ) + || f.equalsIgnoreCase( "yes" ) ) + { + fork = "true"; + forkedExecutable = getSystemJavac(); + } + else if( f.equalsIgnoreCase( "off" ) + || f.equalsIgnoreCase( "false" ) + || f.equalsIgnoreCase( "no" ) ) + { + fork = "false"; + forkedExecutable = null; + } + else + { + fork = "true"; + forkedExecutable = f; + } + } + + /** + * Include ant's own classpath in this task's classpath? + * + * @param include The new Includeantruntime value + */ + public void setIncludeantruntime( boolean include ) + { + includeAntRuntime = include; + } + + /** + * Sets whether or not to include the java runtime libraries to this task's + * classpath. + * + * @param include The new Includejavaruntime value + */ + public void setIncludejavaruntime( boolean include ) + { + includeJavaRuntime = include; + } + + /** + * Set the memoryInitialSize flag. + * + * @param memoryInitialSize The new MemoryInitialSize value + */ + public void setMemoryInitialSize( String memoryInitialSize ) + { + this.memoryInitialSize = memoryInitialSize; + } + + /** + * Set the memoryMaximumSize flag. + * + * @param memoryMaximumSize The new MemoryMaximumSize value + */ + public void setMemoryMaximumSize( String memoryMaximumSize ) + { + this.memoryMaximumSize = memoryMaximumSize; + } + + /** + * Sets whether the -nowarn option should be used. + * + * @param flag The new Nowarn value + */ + public void setNowarn( boolean flag ) + { + this.nowarn = flag; + } + + /** + * Set the optimize flag. + * + * @param optimize The new Optimize value + */ + public void setOptimize( boolean optimize ) + { + this.optimize = optimize; + } + + /** + * Proceed if compilation fails + * + * @param proceed The new Proceed value + */ + public void setProceed( boolean proceed ) + { + failOnError = !proceed; + } + + /** + * Set the value of source. + * + * @param v Value to assign to source. + */ + public void setSource( String v ) + { + this.source = v; + } + + /** + * Set the source dirs to find the source Java files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( Path srcDir ) + { + if( src == null ) + { + src = srcDir; + } + else + { + src.append( srcDir ); + } + } + + /** + * Sets the target VM that the classes will be compiled for. Valid strings + * are "1.1", "1.2", and "1.3". + * + * @param target The new Target value + */ + public void setTarget( String target ) + { + this.target = target; + } + + /** + * Set the verbose flag. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + this.verbose = verbose; + } + + /** + * Gets the bootclasspath that will be used to compile the classes against. + * + * @return The Bootclasspath value + */ + public Path getBootclasspath() + { + return bootclasspath; + } + + /** + * Gets the classpath to be used for this compilation. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return compileClasspath; + } + + /** + * Get the additional implementation specific command line arguments. + * + * @return array of command line arguments, guaranteed to be non-null. + */ + public String[] getCurrentCompilerArgs() + { + Vector args = new Vector(); + for( Enumeration enum = implementationSpecificArgs.elements(); + enum.hasMoreElements(); + ) + { + String[] curr = + ( ( ImplementationSpecificArgument )enum.nextElement() ).getParts(); + for( int i = 0; i < curr.length; i++ ) + { + args.addElement( curr[i] ); + } + } + String[] res = new String[args.size()]; + args.copyInto( res ); + return res; + } + + /** + * Gets the debug flag. + * + * @return The Debug value + */ + public boolean getDebug() + { + return debug; + } + + /** + * Get the value of debugLevel. + * + * @return value of debugLevel. + */ + public String getDebugLevel() + { + return debugLevel; + } + + /** + * Gets the depend flag. + * + * @return The Depend value + */ + public boolean getDepend() + { + return depend; + } + + /** + * Gets the deprecation flag. + * + * @return The Deprecation value + */ + public boolean getDeprecation() + { + return deprecation; + } + + /** + * Gets the destination directory into which the java source files should be + * compiled. + * + * @return The Destdir value + */ + public File getDestdir() + { + return destDir; + } + + /** + * Gets the java source file encoding name. + * + * @return The Encoding value + */ + public String getEncoding() + { + return encoding; + } + + /** + * Gets the extension directories that will be used during the compilation. + * + * @return The Extdirs value + */ + public Path getExtdirs() + { + return extdirs; + } + + /** + * Gets the failonerror flag. + * + * @return The Failonerror value + */ + public boolean getFailonerror() + { + return failOnError; + } + + /** + * Gets the list of files to be compiled. + * + * @return The FileList value + */ + public File[] getFileList() + { + return compileList; + } + + /** + * Gets whether or not the ant classpath is to be included in the task's + * classpath. + * + * @return The Includeantruntime value + */ + public boolean getIncludeantruntime() + { + return includeAntRuntime; + } + + /** + * Gets whether or not the java runtime should be included in this task's + * classpath. + * + * @return The Includejavaruntime value + */ + public boolean getIncludejavaruntime() + { + return includeJavaRuntime; + } + + /** + * The name of the javac executable to use in fork-mode. + * + * @return The JavacExecutable value + */ + public String getJavacExecutable() + { + if( forkedExecutable == null && isForkedJavac() ) + { + forkedExecutable = getSystemJavac(); + } + else if( forkedExecutable != null && !isForkedJavac() ) + { + forkedExecutable = null; + } + return forkedExecutable; + } + + /** + * Gets the memoryInitialSize flag. + * + * @return The MemoryInitialSize value + */ + public String getMemoryInitialSize() + { + return memoryInitialSize; + } + + /** + * Gets the memoryMaximumSize flag. + * + * @return The MemoryMaximumSize value + */ + public String getMemoryMaximumSize() + { + return memoryMaximumSize; + } + + /** + * Should the -nowarn option be used. + * + * @return The Nowarn value + */ + public boolean getNowarn() + { + return nowarn; + } + + /** + * Gets the optimize flag. + * + * @return The Optimize value + */ + public boolean getOptimize() + { + return optimize; + } + + /** + * Get the value of source. + * + * @return value of source. + */ + public String getSource() + { + return source; + } + + /** + * Gets the source dirs to find the source java files. + * + * @return The Srcdir value + */ + public Path getSrcdir() + { + return src; + } + + /** + * Gets the target VM that the classes will be compiled for. + * + * @return The Target value + */ + public String getTarget() + { + return target; + } + + /** + * Gets the verbose flag. + * + * @return The Verbose value + */ + public boolean getVerbose() + { + return verbose; + } + + /** + * Is this a forked invocation of JDK's javac? + * + * @return The ForkedJavac value + */ + public boolean isForkedJavac() + { + return !"false".equals( fork ) || + "extJavac".equals( project.getProperty( "build.compiler" ) ); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath.createPath(); + } + + /** + * Adds an implementation specific command line argument. + * + * @return Description of the Returned Value + */ + public ImplementationSpecificArgument createCompilerArg() + { + ImplementationSpecificArgument arg = + new ImplementationSpecificArgument(); + implementationSpecificArgs.addElement( arg ); + return arg; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createExtdirs() + { + if( extdirs == null ) + { + extdirs = new Path( project ); + } + return extdirs.createPath(); + } + + /** + * Create a nested src element for multiple source path support. + * + * @return a nested src element. + */ + public Path createSrc() + { + if( src == null ) + { + src = new Path( project ); + } + return src.createPath(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + + if( src == null ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + String[] list = src.list(); + if( list.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + + if( destDir != null && !destDir.isDirectory() ) + { + throw new BuildException( "destination directory \"" + destDir + "\" does not exist or is not a directory", location ); + } + + // scan source directories and dest directory to build up + // compile lists + resetFileLists(); + for( int i = 0; i < list.length; i++ ) + { + File srcDir = ( File )project.resolveFile( list[i] ); + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir \"" + srcDir.getPath() + "\" does not exist!", location ); + } + + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + + scanDir( srcDir, destDir != null ? destDir : srcDir, files ); + } + + // compile the source files + + String compiler = determineCompiler(); + + if( compileList.length > 0 ) + { + + CompilerAdapter adapter = CompilerAdapterFactory.getCompiler( + compiler, this ); + log( "Compiling " + compileList.length + + " source file" + + ( compileList.length == 1 ? "" : "s" ) + + ( destDir != null ? " to " + destDir : "" ) ); + + // now we need to populate the compiler adapter + adapter.setJavac( this ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + if( failOnError ) + { + throw new BuildException( FAIL_MSG, location ); + } + else + { + log( FAIL_MSG, Project.MSG_ERR ); + } + } + } + } + + protected String getSystemJavac() + { + // This is the most common extension case - exe for windows and OS/2, + // nothing for *nix. + String extension = Os.isFamily( "dos" ) ? ".exe" : ""; + + // Look for java in the java.home/../bin directory. Unfortunately + // on Windows java.home doesn't always refer to the correct location, + // so we need to fall back to assuming java is somewhere on the + // PATH. + java.io.File jExecutable = + new java.io.File( System.getProperty( "java.home" ) + + "/../bin/javac" + extension ); + + if( jExecutable.exists() && !Os.isFamily( "netware" ) ) + { + return jExecutable.getAbsolutePath(); + } + else + { + return "javac"; + } + } + + protected boolean isJdkCompiler( String compiler ) + { + return "modern".equals( compiler ) || + "classic".equals( compiler ) || + "javac1.1".equals( compiler ) || + "javac1.2".equals( compiler ) || + "javac1.3".equals( compiler ) || + "javac1.4".equals( compiler ); + } + + /** + * Recreate src + * + * @return a nested src element. + */ + protected Path recreateSrc() + { + src = null; + return createSrc(); + } + + /** + * Clear the list of files to be compiled and copied.. + */ + protected void resetFileLists() + { + compileList = new File[0]; + } + + /** + * Scans the directory looking for source files to be compiled. The results + * are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, File destDir, String files[] ) + { + GlobPatternMapper m = new GlobPatternMapper(); + m.setFrom( "*.java" ); + m.setTo( "*.class" ); + SourceFileScanner sfs = new SourceFileScanner( this ); + File[] newFiles = sfs.restrictAsFiles( files, srcDir, destDir, m ); + + if( newFiles.length > 0 ) + { + File[] newCompileList = new File[compileList.length + + newFiles.length]; + System.arraycopy( compileList, 0, newCompileList, 0, + compileList.length ); + System.arraycopy( newFiles, 0, newCompileList, + compileList.length, newFiles.length ); + compileList = newCompileList; + } + } + + private String determineCompiler() + { + String compiler = project.getProperty( "build.compiler" ); + + if( !"false".equals( fork ) ) + { + if( compiler != null ) + { + if( isJdkCompiler( compiler ) ) + { + log( "Since fork is true, ignoring build.compiler setting.", + Project.MSG_WARN ); + compiler = "extJavac"; + } + else + { + log( "Since build.compiler setting isn't classic or modern, ignoring fork setting.", Project.MSG_WARN ); + } + } + else + { + compiler = "extJavac"; + } + } + + if( compiler == null ) + { + if( Project.getJavaVersion() != Project.JAVA_1_1 && + Project.getJavaVersion() != Project.JAVA_1_2 ) + { + compiler = "modern"; + } + else + { + compiler = "classic"; + } + } + return compiler; + } + + /** + * Adds an "implementation" attribute to Commandline$Attribute used to + * filter command line attributes based on the current implementation. + * + * @author RT + */ + public class ImplementationSpecificArgument + extends Commandline.Argument + { + + private String impl; + + public void setImplementation( String impl ) + { + this.impl = impl; + } + + public String[] getParts() + { + if( impl == null || impl.equals( determineCompiler() ) ) + { + return super.getParts(); + } + else + { + return new String[0]; + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JavacOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JavacOutputStream.java new file mode 100644 index 000000000..4d959fbf8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JavacOutputStream.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Task; + +/** + * Serves as an output stream to Javac. This let's us print messages out to the + * log and detect whether or not Javac had an error while compiling. + * + * @author James Duncan Davidson (duncan@x180.com) + * @deprecated use returnvalue of compile to detect compilation failure. + */ + +class JavacOutputStream extends OutputStream +{ + private boolean errorFlag = false; + private StringBuffer line; + + private Task task; + + /** + * Constructs a new JavacOutputStream with the given task as the output + * source for messages. + * + * @param task Description of Parameter + */ + + JavacOutputStream( Task task ) + { + this.task = task; + line = new StringBuffer(); + } + + /** + * Write a character to the output stream. This method looks to make sure + * that there isn't an error being reported and will flush each line of + * input out to the project's log stream. + * + * @param c Description of Parameter + * @exception IOException Description of Exception + */ + + public void write( int c ) + throws IOException + { + char cc = ( char )c; + if( cc == '\r' || cc == '\n' ) + { + // line feed + if( line.length() > 0 ) + { + processLine(); + } + } + else + { + line.append( cc ); + } + } + + /** + * Returns the error status of the compile. If no errors occured, this + * method will return false, else this method will return true. + * + * @return The ErrorFlag value + */ + + boolean getErrorFlag() + { + return errorFlag; + } + + /** + * Processes a line of input and determines if an error occured. + */ + + private void processLine() + { + String s = line.toString(); + if( s.indexOf( "error" ) > -1 ) + { + errorFlag = true; + } + task.log( s ); + line = new StringBuffer(); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javadoc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javadoc.java new file mode 100644 index 000000000..e95245de7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javadoc.java @@ -0,0 +1,1473 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + +/** + * This task makes it easy to generate Javadoc documentation for a collection of + * source code.

              + * + * Current known limitations are:

              + * + * + *

                + *
              • patterns must be of the form "xxx.*", every other pattern doesn't + * work. + *
              • the java comment-stripper reader is horribly slow + *
              • there is no control on arguments sanity since they are left to the + * javadoc implementation. + *
              • argument J in javadoc1 is not supported (what is that for anyway?) + * + *
              + *

              + * + * If no doclet is set, then the version and author + * are by default "yes".

              + * + * Note: This task is run on another VM because the Javadoc code calls System.exit() + * which would break Ant functionality. + * + * @author Jon S. Stevens jon@clearink.com + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Patrick Chanezon + * chanezon@netscape.com + * @author Ernst de Haan ernst@jollem.com + * @author Stefan Bodewig + */ + +public class Javadoc extends Task +{ + private static boolean javadoc1 = + ( Project.getJavaVersion() == Project.JAVA_1_1 ); + + private Commandline cmd = new Commandline(); + + private boolean foundJavaFile = false; + private boolean failOnError = false; + private Path sourcePath = null; + private File destDir = null; + private Vector sourceFiles = new Vector(); + private Vector packageNames = new Vector( 5 ); + private Vector excludePackageNames = new Vector( 1 ); + private boolean author = true; + private boolean version = true; + private DocletInfo doclet = null; + private Path classpath = null; + private Path bootclasspath = null; + private String group = null; + private Vector compileList = new Vector( 10 ); + private String packageList = null; + private Vector links = new Vector( 2 ); + private Vector groups = new Vector( 2 ); + private boolean useDefaultExcludes = true; + private Html doctitle = null; + private Html header = null; + private Html footer = null; + private Html bottom = null; + private boolean useExternalFile = false; + private File tmpList = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + public void setAccess( AccessType at ) + { + cmd.createArgument().setValue( "-" + at.getValue() ); + } + + public void setAdditionalparam( String add ) + { + cmd.createArgument().setLine( add ); + } + + public void setAuthor( boolean src ) + { + author = src; + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + public void setBootclasspath( Path src ) + { + if( bootclasspath == null ) + { + bootclasspath = src; + } + else + { + bootclasspath.append( src ); + } + } + + public void setBottom( String src ) + { + Html h = new Html(); + h.addText( src ); + addBottom( h ); + } + + public void setCharset( String src ) + { + this.add12ArgIfNotEmpty( "-charset", src ); + } + + public void setClasspath( Path src ) + { + if( classpath == null ) + { + classpath = src; + } + else + { + classpath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + public void setDestdir( File dir ) + { + destDir = dir; + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + public void setDocencoding( String enc ) + { + cmd.createArgument().setValue( "-docencoding" ); + cmd.createArgument().setValue( enc ); + } + + public void setDoclet( String src ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.setName( src ); + } + + public void setDocletPath( Path src ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.setPath( src ); + } + + public void setDocletPathRef( Reference r ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.createPath().setRefid( r ); + } + + public void setDoctitle( String src ) + { + Html h = new Html(); + h.addText( src ); + addDoctitle( h ); + } + + public void setEncoding( String enc ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( enc ); + } + + public void setExcludePackageNames( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addExcludePackage( pn ); + } + } + + public void setExtdirs( String src ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-extdirs" ); + cmd.createArgument().setValue( src ); + } + } + + /** + * Should the build process fail if javadoc fails (as indicated by a non + * zero return code)?

              + * + * Default is false.

              + * + * @param b The new Failonerror value + */ + public void setFailonerror( boolean b ) + { + failOnError = b; + } + + public void setFooter( String src ) + { + Html h = new Html(); + h.addText( src ); + addFooter( h ); + } + + public void setGroup( String src ) + { + group = src; + } + + public void setHeader( String src ) + { + Html h = new Html(); + h.addText( src ); + addHeader( h ); + } + + public void setHelpfile( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-helpfile" ); + cmd.createArgument().setFile( f ); + } + } + + public void setLink( String src ) + { + if( !javadoc1 ) + { + createLink().setHref( src ); + } + } + + public void setLinkoffline( String src ) + { + if( !javadoc1 ) + { + LinkArgument le = createLink(); + le.setOffline( true ); + String linkOfflineError = "The linkoffline attribute must include a URL and " + + "a package-list file location separated by a space"; + if( src.trim().length() == 0 ) + { + throw new BuildException( linkOfflineError ); + } + StringTokenizer tok = new StringTokenizer( src, " ", false ); + le.setHref( tok.nextToken() ); + + if( !tok.hasMoreTokens() ) + { + throw new BuildException( linkOfflineError ); + } + le.setPackagelistLoc( project.resolveFile( tok.nextToken() ) ); + } + } + + public void setLocale( String src ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-locale" ); + cmd.createArgument().setValue( src ); + } + } + + public void setMaxmemory( String max ) + { + if( javadoc1 ) + { + cmd.createArgument().setValue( "-J-mx" + max ); + } + else + { + cmd.createArgument().setValue( "-J-Xmx" + max ); + } + } + + public void setNodeprecated( boolean b ) + { + addArgIf( b, "-nodeprecated" ); + } + + public void setNodeprecatedlist( boolean b ) + { + add12ArgIf( b, "-nodeprecatedlist" ); + } + + public void setNohelp( boolean b ) + { + add12ArgIf( b, "-nohelp" ); + } + + public void setNoindex( boolean b ) + { + addArgIf( b, "-noindex" ); + } + + public void setNonavbar( boolean b ) + { + add12ArgIf( b, "-nonavbar" ); + } + + public void setNotree( boolean b ) + { + addArgIf( b, "-notree" ); + } + + public void setOld( boolean b ) + { + add12ArgIf( b, "-1.1" ); + } + + public void setOverview( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-overview" ); + cmd.createArgument().setFile( f ); + } + } + + public void setPackage( boolean b ) + { + addArgIf( b, "-package" ); + } + + public void setPackageList( String src ) + { + packageList = src; + } + + public void setPackagenames( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addPackage( pn ); + } + } + + public void setPrivate( boolean b ) + { + addArgIf( b, "-private" ); + } + + public void setProtected( boolean b ) + { + addArgIf( b, "-protected" ); + } + + public void setPublic( boolean b ) + { + addArgIf( b, "-public" ); + } + + public void setSerialwarn( boolean b ) + { + add12ArgIf( b, "-serialwarn" ); + } + + public void setSourcefiles( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String f = tok.nextToken(); + SourceFile sf = new SourceFile(); + sf.setFile( project.resolveFile( f ) ); + addSource( sf ); + } + } + + public void setSourcepath( Path src ) + { + if( sourcePath == null ) + { + sourcePath = src; + } + else + { + sourcePath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new SourcepathRef value + */ + public void setSourcepathRef( Reference r ) + { + createSourcepath().setRefid( r ); + } + + public void setSplitindex( boolean b ) + { + add12ArgIf( b, "-splitindex" ); + } + + public void setStylesheetfile( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-stylesheetfile" ); + cmd.createArgument().setFile( f ); + } + } + + public void setUse( boolean b ) + { + add12ArgIf( b, "-use" ); + } + + /** + * Work around command line length limit by using an external file for the + * sourcefiles. + * + * @param b The new UseExternalFile value + */ + public void setUseExternalFile( boolean b ) + { + if( !javadoc1 ) + { + useExternalFile = b; + } + } + + public void setVerbose( boolean b ) + { + add12ArgIf( b, "-verbose" ); + } + + public void setVersion( boolean src ) + { + version = src; + } + + public void setWindowtitle( String src ) + { + add12ArgIfNotEmpty( "-windowtitle", src ); + } + + public void addBottom( Html text ) + { + if( !javadoc1 ) + { + bottom = text; + } + } + + public void addDoctitle( Html text ) + { + if( !javadoc1 ) + { + doctitle = text; + } + } + + public void addExcludePackage( PackageName pn ) + { + excludePackageNames.addElement( pn ); + } + + public void addFooter( Html text ) + { + if( !javadoc1 ) + { + footer = text; + } + } + + public void addHeader( Html text ) + { + if( !javadoc1 ) + { + header = text; + } + } + + public void addPackage( PackageName pn ) + { + packageNames.addElement( pn ); + } + + public void addSource( SourceFile sf ) + { + sourceFiles.addElement( sf ); + } + + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + public DocletInfo createDoclet() + { + doclet = new DocletInfo(); + return doclet; + } + + public GroupArgument createGroup() + { + GroupArgument ga = new GroupArgument(); + groups.addElement( ga ); + return ga; + } + + public LinkArgument createLink() + { + LinkArgument la = new LinkArgument(); + links.addElement( la ); + return la; + } + + public Path createSourcepath() + { + if( sourcePath == null ) + { + sourcePath = new Path( project ); + } + return sourcePath.createPath(); + } + + public void execute() + throws BuildException + { + if( "javadoc2".equals( taskType ) ) + { + log( "!! javadoc2 is deprecated. Use javadoc instead. !!" ); + } + + if( sourcePath == null ) + { + String msg = "sourcePath attribute must be set!"; + throw new BuildException( msg ); + } + + log( "Generating Javadoc", Project.MSG_INFO ); + + if( doctitle != null ) + { + cmd.createArgument().setValue( "-doctitle" ); + cmd.createArgument().setValue( expand( doctitle.getText() ) ); + } + if( header != null ) + { + cmd.createArgument().setValue( "-header" ); + cmd.createArgument().setValue( expand( header.getText() ) ); + } + if( footer != null ) + { + cmd.createArgument().setValue( "-footer" ); + cmd.createArgument().setValue( expand( footer.getText() ) ); + } + if( bottom != null ) + { + cmd.createArgument().setValue( "-bottom" ); + cmd.createArgument().setValue( expand( bottom.getText() ) ); + } + + Commandline toExecute = ( Commandline )cmd.clone(); + toExecute.setExecutable( getJavadocExecutableName() ); + +// ------------------------------------------------ general javadoc arguments + if( classpath == null ) + classpath = Path.systemClasspath; + else + classpath = classpath.concatSystemClasspath( "ignore" ); + + if( !javadoc1 ) + { + toExecute.createArgument().setValue( "-classpath" ); + toExecute.createArgument().setPath( classpath ); + toExecute.createArgument().setValue( "-sourcepath" ); + toExecute.createArgument().setPath( sourcePath ); + } + else + { + toExecute.createArgument().setValue( "-classpath" ); + toExecute.createArgument().setValue( sourcePath.toString() + + System.getProperty( "path.separator" ) + classpath.toString() ); + } + + if( version && doclet == null ) + toExecute.createArgument().setValue( "-version" ); + if( author && doclet == null ) + toExecute.createArgument().setValue( "-author" ); + + if( javadoc1 || doclet == null ) + { + if( destDir == null ) + { + String msg = "destDir attribute must be set!"; + throw new BuildException( msg ); + } + } + +// --------------------------------- javadoc2 arguments for default doclet + +// XXX: how do we handle a custom doclet? + + if( !javadoc1 ) + { + if( doclet != null ) + { + if( doclet.getName() == null ) + { + throw new BuildException( "The doclet name must be specified.", location ); + } + else + { + toExecute.createArgument().setValue( "-doclet" ); + toExecute.createArgument().setValue( doclet.getName() ); + if( doclet.getPath() != null ) + { + toExecute.createArgument().setValue( "-docletpath" ); + toExecute.createArgument().setPath( doclet.getPath() ); + } + for( Enumeration e = doclet.getParams(); e.hasMoreElements(); ) + { + DocletParam param = ( DocletParam )e.nextElement(); + if( param.getName() == null ) + { + throw new BuildException( "Doclet parameters must have a name" ); + } + + toExecute.createArgument().setValue( param.getName() ); + if( param.getValue() != null ) + { + toExecute.createArgument().setValue( param.getValue() ); + } + } + } + } + if( bootclasspath != null ) + { + toExecute.createArgument().setValue( "-bootclasspath" ); + toExecute.createArgument().setPath( bootclasspath ); + } + + // add the links arguments + if( links.size() != 0 ) + { + for( Enumeration e = links.elements(); e.hasMoreElements(); ) + { + LinkArgument la = ( LinkArgument )e.nextElement(); + + if( la.getHref() == null ) + { + throw new BuildException( "Links must provide the URL to the external class documentation." ); + } + + if( la.isLinkOffline() ) + { + File packageListLocation = la.getPackagelistLoc(); + if( packageListLocation == null ) + { + throw new BuildException( "The package list location for link " + la.getHref() + + " must be provided because the link is offline" ); + } + File packageList = new File( packageListLocation, "package-list" ); + if( packageList.exists() ) + { + toExecute.createArgument().setValue( "-linkoffline" ); + toExecute.createArgument().setValue( la.getHref() ); + toExecute.createArgument().setValue( packageListLocation.getAbsolutePath() ); + } + else + { + log( "Warning: No package list was found at " + packageListLocation, + Project.MSG_VERBOSE ); + } + } + else + { + toExecute.createArgument().setValue( "-link" ); + toExecute.createArgument().setValue( la.getHref() ); + } + } + } + + // add the single group arguments + // Javadoc 1.2 rules: + // Multiple -group args allowed. + // Each arg includes 3 strings: -group [name] [packagelist]. + // Elements in [packagelist] are colon-delimited. + // An element in [packagelist] may end with the * wildcard. + + // Ant javadoc task rules for group attribute: + // Args are comma-delimited. + // Each arg is 2 space-delimited strings. + // E.g., group="XSLT_Packages org.apache.xalan.xslt*,XPath_Packages org.apache.xalan.xpath*" + if( group != null ) + { + StringTokenizer tok = new StringTokenizer( group, ",", false ); + while( tok.hasMoreTokens() ) + { + String grp = tok.nextToken().trim(); + int space = grp.indexOf( " " ); + if( space > 0 ) + { + String name = grp.substring( 0, space ); + String pkgList = grp.substring( space + 1 ); + toExecute.createArgument().setValue( "-group" ); + toExecute.createArgument().setValue( name ); + toExecute.createArgument().setValue( pkgList ); + } + } + } + + // add the group arguments + if( groups.size() != 0 ) + { + for( Enumeration e = groups.elements(); e.hasMoreElements(); ) + { + GroupArgument ga = ( GroupArgument )e.nextElement(); + String title = ga.getTitle(); + String packages = ga.getPackages(); + if( title == null || packages == null ) + { + throw new BuildException( "The title and packages must be specified for group elements." ); + } + toExecute.createArgument().setValue( "-group" ); + toExecute.createArgument().setValue( expand( title ) ); + toExecute.createArgument().setValue( packages ); + } + } + + } + + tmpList = null; + if( packageNames.size() > 0 ) + { + Vector packages = new Vector(); + Enumeration enum = packageNames.elements(); + while( enum.hasMoreElements() ) + { + PackageName pn = ( PackageName )enum.nextElement(); + String name = pn.getName().trim(); + if( name.endsWith( ".*" ) ) + { + packages.addElement( name ); + } + else + { + toExecute.createArgument().setValue( name ); + } + } + + Vector excludePackages = new Vector(); + if( excludePackageNames.size() > 0 ) + { + enum = excludePackageNames.elements(); + while( enum.hasMoreElements() ) + { + PackageName pn = ( PackageName )enum.nextElement(); + excludePackages.addElement( pn.getName().trim() ); + } + } + if( packages.size() > 0 ) + { + evaluatePackages( toExecute, sourcePath, packages, excludePackages ); + } + } + + if( sourceFiles.size() > 0 ) + { + PrintWriter srcListWriter = null; + try + { + + /** + * Write sourcefiles to a temporary file if requested. + */ + if( useExternalFile ) + { + if( tmpList == null ) + { + tmpList = fileUtils.createTempFile( "javadoc", "", null ); + toExecute.createArgument().setValue( "@" + tmpList.getAbsolutePath() ); + } + srcListWriter = new PrintWriter( new FileWriter( tmpList.getAbsolutePath(), + true ) ); + } + + Enumeration enum = sourceFiles.elements(); + while( enum.hasMoreElements() ) + { + SourceFile sf = ( SourceFile )enum.nextElement(); + String sourceFileName = sf.getFile().getAbsolutePath(); + if( useExternalFile ) + { + srcListWriter.println( sourceFileName ); + } + else + { + toExecute.createArgument().setValue( sourceFileName ); + } + } + + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", + e, location ); + } + finally + { + if( srcListWriter != null ) + { + srcListWriter.close(); + } + } + } + + if( packageList != null ) + { + toExecute.createArgument().setValue( "@" + packageList ); + } + log( "Javadoc args: " + toExecute, Project.MSG_VERBOSE ); + + log( "Javadoc execution", Project.MSG_INFO ); + + JavadocOutputStream out = new JavadocOutputStream( Project.MSG_INFO ); + JavadocOutputStream err = new JavadocOutputStream( Project.MSG_WARN ); + Execute exe = new Execute( new PumpStreamHandler( out, err ) ); + exe.setAntRun( project ); + + /* + * No reason to change the working directory as all filenames and + * path components have been resolved already. + * + * Avoid problems with command line length in some environments. + */ + exe.setWorkingDirectory( null ); + try + { + exe.setCommandline( toExecute.getCommandline() ); + int ret = exe.execute(); + if( ret != 0 && failOnError ) + { + throw new BuildException( "Javadoc returned " + ret, location ); + } + } + catch( IOException e ) + { + throw new BuildException( "Javadoc failed: " + e, e, location ); + } + finally + { + + if( tmpList != null ) + { + tmpList.delete(); + tmpList = null; + } + + out.logFlush(); + err.logFlush(); + try + { + out.close(); + err.close(); + } + catch( IOException e ) + {} + } + } + + /** + * Convenience method to expand properties. + * + * @param content Description of Parameter + * @return Description of the Returned Value + */ + protected String expand( String content ) + { + return project.replaceProperties( content ); + } + + private String getJavadocExecutableName() + { + // This is the most common extension case - exe for windows and OS/2, + // nothing for *nix. + String extension = Os.isFamily( "dos" ) ? ".exe" : ""; + + // Look for javadoc in the java.home/../bin directory. Unfortunately + // on Windows java.home doesn't always refer to the correct location, + // so we need to fall back to assuming javadoc is somewhere on the + // PATH. + File jdocExecutable = new File( System.getProperty( "java.home" ) + + "/../bin/javadoc" + extension ); + + if( jdocExecutable.exists() && !Os.isFamily( "netware" ) ) + { + return jdocExecutable.getAbsolutePath(); + } + else + { + if( !Os.isFamily( "netware" ) ) + { + log( "Unable to locate " + jdocExecutable.getAbsolutePath() + + ". Using \"javadoc\" instead.", Project.MSG_VERBOSE ); + } + return "javadoc"; + } + } + + private void add11ArgIf( boolean b, String arg ) + { + if( javadoc1 && b ) + { + cmd.createArgument().setValue( arg ); + } + } + + private void add12ArgIf( boolean b, String arg ) + { + if( !javadoc1 && b ) + { + cmd.createArgument().setValue( arg ); + } + } + + private void add12ArgIfNotEmpty( String key, String value ) + { + if( !javadoc1 ) + { + if( value != null && value.length() != 0 ) + { + cmd.createArgument().setValue( key ); + cmd.createArgument().setValue( value ); + } + else + { + project.log( this, + "Warning: Leaving out empty argument '" + key + "'", + Project.MSG_WARN ); + } + } + } + + + private void addArgIf( boolean b, String arg ) + { + if( b ) + { + cmd.createArgument().setValue( arg ); + } + } + + /** + * Given a source path, a list of package patterns, fill the given list with + * the packages found in that path subdirs matching one of the given + * patterns. + * + * @param toExecute Description of Parameter + * @param sourcePath Description of Parameter + * @param packages Description of Parameter + * @param excludePackages Description of Parameter + */ + private void evaluatePackages( Commandline toExecute, Path sourcePath, + Vector packages, Vector excludePackages ) + { + log( "Source path = " + sourcePath.toString(), Project.MSG_VERBOSE ); + StringBuffer msg = new StringBuffer( "Packages = " ); + for( int i = 0; i < packages.size(); i++ ) + { + if( i > 0 ) + { + msg.append( "," ); + } + msg.append( packages.elementAt( i ) ); + } + log( msg.toString(), Project.MSG_VERBOSE ); + + msg.setLength( 0 ); + msg.append( "Exclude Packages = " ); + for( int i = 0; i < excludePackages.size(); i++ ) + { + if( i > 0 ) + { + msg.append( "," ); + } + msg.append( excludePackages.elementAt( i ) ); + } + log( msg.toString(), Project.MSG_VERBOSE ); + + Vector addedPackages = new Vector(); + + String[] list = sourcePath.list(); + if( list == null ) + list = new String[0]; + + FileSet fs = new FileSet(); + fs.setDefaultexcludes( useDefaultExcludes ); + + Enumeration e = packages.elements(); + while( e.hasMoreElements() ) + { + String pkg = ( String )e.nextElement(); + pkg = pkg.replace( '.', '/' ); + if( pkg.endsWith( "*" ) ) + { + pkg += "*"; + } + + fs.createInclude().setName( pkg ); + }// while + + e = excludePackages.elements(); + while( e.hasMoreElements() ) + { + String pkg = ( String )e.nextElement(); + pkg = pkg.replace( '.', '/' ); + if( pkg.endsWith( "*" ) ) + { + pkg += "*"; + } + + fs.createExclude().setName( pkg ); + } + + PrintWriter packageListWriter = null; + try + { + if( useExternalFile ) + { + tmpList = fileUtils.createTempFile( "javadoc", "", null ); + toExecute.createArgument().setValue( "@" + tmpList.getAbsolutePath() ); + packageListWriter = new PrintWriter( new FileWriter( tmpList ) ); + } + + for( int j = 0; j < list.length; j++ ) + { + File source = project.resolveFile( list[j] ); + fs.setDir( source ); + + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] packageDirs = ds.getIncludedDirectories(); + + for( int i = 0; i < packageDirs.length; i++ ) + { + File pd = new File( source, packageDirs[i] ); + String[] files = pd.list( + new FilenameFilter() + { + public boolean accept( File dir1, String name ) + { + if( name.endsWith( ".java" ) ) + { + return true; + } + return false;// ignore dirs + } + } ); + + if( files.length > 0 ) + { + String pkgDir = packageDirs[i].replace( '/', '.' ).replace( '\\', '.' ); + if( !addedPackages.contains( pkgDir ) ) + { + if( useExternalFile ) + { + packageListWriter.println( pkgDir ); + } + else + { + toExecute.createArgument().setValue( pkgDir ); + } + addedPackages.addElement( pkgDir ); + } + } + } + } + } + catch( IOException ioex ) + { + throw new BuildException( "Error creating temporary file", + ioex, location ); + } + finally + { + if( packageListWriter != null ) + { + packageListWriter.close(); + } + } + } + + public static class AccessType extends EnumeratedAttribute + { + public String[] getValues() + { + // Protected first so if any GUI tool offers a default + // based on enum #0, it will be right. + return new String[]{"protected", "public", "package", "private"}; + } + } + + public static class Html + { + private StringBuffer text = new StringBuffer(); + + public String getText() + { + return text.toString(); + } + + public void addText( String t ) + { + text.append( t ); + } + } + + public static class PackageName + { + private String name; + + public void setName( String name ) + { + this.name = name; + } + + public String getName() + { + return name; + } + + public String toString() + { + return getName(); + } + } + + public static class SourceFile + { + private File file; + + public void setFile( File file ) + { + this.file = file; + } + + public File getFile() + { + return file; + } + } + + public class DocletInfo + { + + private Vector params = new Vector(); + private String name; + private Path path; + + public void setName( String name ) + { + this.name = name; + } + + public void setPath( Path path ) + { + if( this.path == null ) + { + this.path = path; + } + else + { + this.path.append( path ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new PathRef value + */ + public void setPathRef( Reference r ) + { + createPath().setRefid( r ); + } + + public String getName() + { + return name; + } + + public Enumeration getParams() + { + return params.elements(); + } + + public Path getPath() + { + return path; + } + + public DocletParam createParam() + { + DocletParam param = new DocletParam(); + params.addElement( param ); + + return param; + } + + public Path createPath() + { + if( path == null ) + { + path = new Path( getProject() ); + } + return path.createPath(); + } + } + + public class DocletParam + { + private String name; + private String value; + + public void setName( String name ) + { + this.name = name; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + } + + public class GroupArgument + { + private Vector packages = new Vector( 3 ); + private Html title; + + public GroupArgument() { } + + public void setPackages( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addPackage( pn ); + } + } + + public void setTitle( String src ) + { + Html h = new Html(); + h.addText( src ); + addTitle( h ); + } + + public String getPackages() + { + StringBuffer p = new StringBuffer(); + for( int i = 0; i < packages.size(); i++ ) + { + if( i > 0 ) + { + p.append( ":" ); + } + p.append( packages.elementAt( i ).toString() ); + } + return p.toString(); + } + + public String getTitle() + { + return title != null ? title.getText() : null; + } + + public void addPackage( PackageName pn ) + { + packages.addElement( pn ); + } + + public void addTitle( Html text ) + { + title = text; + } + } + + public class LinkArgument + { + private boolean offline = false; + private String href; + private File packagelistLoc; + + public LinkArgument() { } + + public void setHref( String hr ) + { + href = hr; + } + + public void setOffline( boolean offline ) + { + this.offline = offline; + } + + public void setPackagelistLoc( File src ) + { + packagelistLoc = src; + } + + public String getHref() + { + return href; + } + + public File getPackagelistLoc() + { + return packagelistLoc; + } + + public boolean isLinkOffline() + { + return offline; + } + } + + private class JavadocOutputStream extends LogOutputStream + { + + // + // Override the logging of output in order to filter out Generating + // messages. Generating messages are set to a priority of VERBOSE + // unless they appear after what could be an informational message. + // + private String queuedLine = null; + + JavadocOutputStream( int level ) + { + super( Javadoc.this, level ); + } + + + protected void logFlush() + { + if( queuedLine != null ) + { + super.processLine( queuedLine, Project.MSG_VERBOSE ); + queuedLine = null; + } + } + + protected void processLine( String line, int messageLevel ) + { + if( messageLevel == Project.MSG_INFO && line.startsWith( "Generating " ) ) + { + if( queuedLine != null ) + { + super.processLine( queuedLine, Project.MSG_VERBOSE ); + } + queuedLine = line; + } + else + { + if( queuedLine != null ) + { + if( line.startsWith( "Building " ) ) + super.processLine( queuedLine, Project.MSG_VERBOSE ); + else + super.processLine( queuedLine, Project.MSG_INFO ); + queuedLine = null; + } + super.processLine( line, messageLevel ); + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jikes.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jikes.java new file mode 100644 index 000000000..4e8700635 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jikes.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Random; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Encapsulates a Jikes compiler, by directly executing an external process. + * + * @author skanthak@muehlheim.de + * @deprecated merged into the class Javac. + */ +public class Jikes +{ + protected String command; + protected JikesOutputParser jop; + protected Project project; + + /** + * Constructs a new Jikes obect. + * + * @param jop - Parser to send jike's output to + * @param command - name of jikes executeable + * @param project Description of Parameter + */ + protected Jikes( JikesOutputParser jop, String command, Project project ) + { + super(); + this.jop = jop; + this.command = command; + this.project = project; + } + + /** + * Do the compile with the specified arguments. + * + * @param args - arguments to pass to process on command line + */ + protected void compile( String[] args ) + { + String[] commandArray = null; + File tmpFile = null; + + try + { + String myos = System.getProperty( "os.name" ); + + // Windows has a 32k limit on total arg size, so + // create a temporary file to store all the arguments + + // There have been reports that 300 files could be compiled + // so 250 is a conservative approach + if( myos.toLowerCase().indexOf( "windows" ) >= 0 + && args.length > 250 ) + { + PrintWriter out = null; + try + { + tmpFile = new File( "jikes" + ( new Random( System.currentTimeMillis() ) ).nextLong() ); + out = new PrintWriter( new FileWriter( tmpFile ) ); + for( int i = 0; i < args.length; i++ ) + { + out.println( args[i] ); + } + out.flush(); + commandArray = new String[]{command, + "@" + tmpFile.getAbsolutePath()}; + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", e ); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( Throwable t ) + {} + } + } + } + else + { + commandArray = new String[args.length + 1]; + commandArray[0] = command; + System.arraycopy( args, 0, commandArray, 1, args.length ); + } + + // We assume, that everything jikes writes goes to + // standard output, not to standard error. The option + // -Xstdout that is given to Jikes in Javac.doJikesCompile() + // should guarantee this. At least I hope so. :) + try + { + Execute exe = new Execute( jop ); + exe.setAntRun( project ); + exe.setWorkingDirectory( project.getBaseDir() ); + exe.setCommandline( commandArray ); + exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Error running Jikes compiler", e ); + } + } + finally + { + if( tmpFile != null ) + { + tmpFile.delete(); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JikesOutputParser.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JikesOutputParser.java new file mode 100644 index 000000000..3376ff97f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JikesOutputParser.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Parses output from jikes and passes errors and warnings into the right + * logging channels of Project. TODO: Parsing could be much better + * + * @author skanthak@muehlheim.de + * @deprecated use Jikes' exit value to detect compilation failure. + */ +public class JikesOutputParser implements ExecuteStreamHandler +{ + protected boolean errorFlag = false; + protected boolean error = false; + + protected BufferedReader br; + protected boolean emacsMode;// no errors so far + protected int errors, warnings; + protected Task task; + + /** + * Construct a new Parser object + * + * @param task - task in whichs context we are called + * @param emacsMode Description of Parameter + */ + protected JikesOutputParser( Task task, boolean emacsMode ) + { + super(); + this.task = task; + this.emacsMode = emacsMode; + } + + /** + * Ignore. + * + * @param is The new ProcessErrorStream value + */ + public void setProcessErrorStream( InputStream is ) { } + + /** + * Ignore. + * + * @param os The new ProcessInputStream value + */ + public void setProcessInputStream( OutputStream os ) { } + + /** + * Set the inputstream + * + * @param is The new ProcessOutputStream value + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + br = new BufferedReader( new InputStreamReader( is ) ); + } + + /** + * Invokes parseOutput. + * + * @exception IOException Description of Exception + */ + public void start() + throws IOException + { + parseOutput( br ); + } + + /** + * Ignore. + */ + public void stop() { } + + /** + * Indicate if there were errors during the compile + * + * @return if errors ocured + */ + protected boolean getErrorFlag() + { + return errorFlag; + } + + /** + * Parse the output of a jikes compiler + * + * @param reader - Reader used to read jikes's output + * @exception IOException Description of Exception + */ + protected void parseOutput( BufferedReader reader ) + throws IOException + { + if( emacsMode ) + parseEmacsOutput( reader ); + else + parseStandardOutput( reader ); + } + + private void setError( boolean err ) + { + error = err; + if( error ) + errorFlag = true; + } + + private void log( String line ) + { + if( !emacsMode ) + { + task.log( "", ( error ? Project.MSG_ERR : Project.MSG_WARN ) ); + } + task.log( line, ( error ? Project.MSG_ERR : Project.MSG_WARN ) ); + } + + private void parseEmacsOutput( BufferedReader reader ) + throws IOException + { + // This may change, if we add advanced parsing capabilities. + parseStandardOutput( reader ); + } + + private void parseStandardOutput( BufferedReader reader ) + throws IOException + { + String line; + String lower; + // We assume, that every output, jike does, stands for an error/warning + // XXX + // Is this correct? + + // TODO: + // A warning line, that shows code, which contains a variable + // error will cause some trouble. The parser should definitely + // be much better. + + while( ( line = reader.readLine() ) != null ) + { + lower = line.toLowerCase(); + if( line.trim().equals( "" ) ) + continue; + if( lower.indexOf( "error" ) != -1 ) + setError( true ); + else if( lower.indexOf( "warning" ) != -1 ) + setError( false ); + else + { + // If we don't know the type of the line + // and we are in emacs mode, it will be + // an error, because in this mode, jikes won't + // always print "error", but sometimes other + // keywords like "Syntax". We should look for + // all those keywords. + if( emacsMode ) + setError( true ); + } + log( line ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/KeySubst.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/KeySubst.java new file mode 100644 index 000000000..22fbf44f3 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/KeySubst.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Hashtable; +import java.util.StringTokenizer; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Keyword substitution. Input file is written to output file. Do not make input + * file same as output file. Keywords in input files look like this: + * + * @author Jon S. Stevens jon@clearink.com + * @foo@. See the docs for the setKeys method to understand how to do the + * substitutions. + * @deprecated KeySubst is deprecated. Use Filter + CopyDir instead. + */ +public class KeySubst extends Task +{ + private File source = null; + private File dest = null; + private String sep = "*"; + private Hashtable replacements = new Hashtable(); + + + public static void main( String[] args ) + { + try + { + Hashtable hash = new Hashtable(); + hash.put( "VERSION", "1.0.3" ); + hash.put( "b", "ffff" ); + System.out.println( KeySubst.replace( "$f ${VERSION} f ${b} jj $", hash ) ); + } + catch( Exception e ) + { + e.printStackTrace(); + } + } + + /** + * Does replacement on text using the hashtable of keys. + * + * @param origString Description of Parameter + * @param keys Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @returns the string with the replacements in it. + */ + public static String replace( String origString, Hashtable keys ) + throws BuildException + { + StringBuffer finalString = new StringBuffer(); + int index = 0; + int i = 0; + String key = null; + while( ( index = origString.indexOf( "${", i ) ) > -1 ) + { + key = origString.substring( index + 2, origString.indexOf( "}", index + 3 ) ); + finalString.append( origString.substring( i, index ) ); + if( keys.containsKey( key ) ) + { + finalString.append( keys.get( key ) ); + } + else + { + finalString.append( "${" ); + finalString.append( key ); + finalString.append( "}" ); + } + i = index + 3 + key.length(); + } + finalString.append( origString.substring( i ) ); + return finalString.toString(); + } + + /** + * Set the destination file. + * + * @param dest The new Dest value + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Format string is like this:

              + * + * name=value*name2=value

              + * + * Names are case sensitive.

              + * + * Use the setSep() method to change the * to something else if you need to + * use * as a name or value. + * + * @param keys The new Keys value + */ + public void setKeys( String keys ) + { + if( keys != null && keys.length() > 0 ) + { + StringTokenizer tok = + new StringTokenizer( keys, this.sep, false ); + while( tok.hasMoreTokens() ) + { + String token = tok.nextToken().trim(); + StringTokenizer itok = + new StringTokenizer( token, "=", false ); + + String name = itok.nextToken(); + String value = itok.nextToken(); +// log ( "Name: " + name ); +// log ( "Value: " + value ); + replacements.put( name, value ); + } + } + } + + /** + * Sets the seperator between name=value arguments in setKeys(). By default + * it is "*". + * + * @param sep The new Sep value + */ + public void setSep( String sep ) + { + this.sep = sep; + } + + /** + * Set the source file. + * + * @param s The new Src value + */ + public void setSrc( File s ) + { + this.source = s; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + log( "!! KeySubst is deprecated. Use Filter + CopyDir instead. !!" ); + log( "Performing Substitions" ); + if( source == null || dest == null ) + { + log( "Source and destinations must not be null" ); + return; + } + BufferedReader br = null; + BufferedWriter bw = null; + try + { + br = new BufferedReader( new FileReader( source ) ); + dest.delete(); + bw = new BufferedWriter( new FileWriter( dest ) ); + + String line = null; + String newline = null; + int length; + line = br.readLine(); + while( line != null ) + { + if( line.length() == 0 ) + { + bw.newLine(); + } + else + { + newline = KeySubst.replace( line, replacements ); + bw.write( newline ); + bw.newLine(); + } + line = br.readLine(); + } + bw.flush(); + bw.close(); + br.close(); + } + catch( IOException ioe ) + { + ioe.printStackTrace(); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogOutputStream.java new file mode 100644 index 000000000..f715beaaa --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogOutputStream.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + + +/** + * Logs each line written to this stream to the log system of ant. Tries to be + * smart about line separators.
              + * TODO: This class can be split to implement other line based processing of + * data written to the stream. + * + * @author thomas.haas@softwired-inc.com + */ +public class LogOutputStream extends OutputStream +{ + + private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private boolean skip = false; + private int level = Project.MSG_INFO; + + private Task task; + + /** + * Creates a new instance of this class. + * + * @param task the task for whom to log + * @param level loglevel used to log data written to this stream. + */ + public LogOutputStream( Task task, int level ) + { + this.task = task; + this.level = level; + } + + public int getMessageLevel() + { + return level; + } + + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + if( buffer.size() > 0 ) + processBuffer(); + super.close(); + } + + + /** + * Write the data to the buffer and flush the buffer, if a line separator is + * detected. + * + * @param cc data to log (byte). + * @exception IOException Description of Exception + */ + public void write( int cc ) + throws IOException + { + final byte c = ( byte )cc; + if( ( c == '\n' ) || ( c == '\r' ) ) + { + if( !skip ) + processBuffer(); + } + else + buffer.write( cc ); + skip = ( c == '\r' ); + } + + + /** + * Converts the buffer to a string and sends it to processLine + */ + protected void processBuffer() + { + processLine( buffer.toString() ); + buffer.reset(); + } + + /** + * Logs a line to the log system of ant. + * + * @param line the line to log. + */ + protected void processLine( String line ) + { + processLine( line, level ); + } + + /** + * Logs a line to the log system of ant. + * + * @param line the line to log. + * @param level Description of Parameter + */ + protected void processLine( String line, int level ) + { + task.log( line, level ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogStreamHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogStreamHandler.java new file mode 100644 index 000000000..b7e6c849b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogStreamHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Logs standard output and error of a subprocess to the log system of ant. + * + * @author thomas.haas@softwired-inc.com + */ +public class LogStreamHandler extends PumpStreamHandler +{ + + /** + * Creates a new instance of this class. + * + * @param task the task for whom to log + * @param outlevel the loglevel used to log standard output + * @param errlevel the loglevel used to log standard error + */ + public LogStreamHandler( Task task, int outlevel, int errlevel ) + { + super( new LogOutputStream( task, outlevel ), + new LogOutputStream( task, errlevel ) ); + } + + public void stop() + { + super.stop(); + try + { + getErr().close(); + getOut().close(); + } + catch( IOException e ) + { + // plain impossible + throw new BuildException( e ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Manifest.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Manifest.java new file mode 100644 index 000000000..242b0fa5e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Manifest.java @@ -0,0 +1,950 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Class to manage Manifest information + * + * @author Conor MacNeill + * @author Stefan Bodewig + */ +public class Manifest extends Task +{ + /** + * The standard manifest version header + */ + public final static String ATTRIBUTE_MANIFEST_VERSION = "Manifest-Version"; + + /** + * The standard Signature Version header + */ + public final static String ATTRIBUTE_SIGNATURE_VERSION = "Signature-Version"; + + /** + * The Name Attribute is the first in a named section + */ + public final static String ATTRIBUTE_NAME = "Name"; + + /** + * The From Header is disallowed in a Manifest + */ + public final static String ATTRIBUTE_FROM = "From"; + + /** + * The Class-Path Header is special - it can be duplicated + */ + public final static String ATTRIBUTE_CLASSPATH = "class-path"; + + /** + * Default Manifest version if one is not specified + */ + public final static String DEFAULT_MANIFEST_VERSION = "1.0"; + + /** + * The max length of a line in a Manifest + */ + public final static int MAX_LINE_LENGTH = 70; + + /** + * The version of this manifest + */ + private String manifestVersion = DEFAULT_MANIFEST_VERSION; + + /** + * The main section of this manifest + */ + private Section mainSection = new Section(); + + /** + * The named sections of this manifest + */ + private Hashtable sections = new Hashtable(); + + private File manifestFile; + + private Mode mode; + + /** + * Construct an empty manifest + */ + public Manifest() + { + mode = new Mode(); + mode.setValue( "replace" ); + manifestVersion = null; + } + + /** + * Read a manifest file from the given reader + * + * @param r Description of Parameter + * @exception ManifestException Description of Exception + * @exception IOException Description of Exception + * @throws ManifestException if the manifest is not valid according to the + * JAR spec + * @throws IOException if the manifest cannot be read from the reader. + */ + public Manifest( Reader r ) + throws ManifestException, IOException + { + BufferedReader reader = new BufferedReader( r ); + // This should be the manifest version + String nextSectionName = mainSection.read( reader ); + String readManifestVersion = mainSection.getAttributeValue( ATTRIBUTE_MANIFEST_VERSION ); + if( readManifestVersion != null ) + { + manifestVersion = readManifestVersion; + mainSection.removeAttribute( ATTRIBUTE_MANIFEST_VERSION ); + } + + String line = null; + while( ( line = reader.readLine() ) != null ) + { + if( line.length() == 0 ) + { + continue; + } + + Section section = new Section(); + if( nextSectionName == null ) + { + Attribute sectionName = new Attribute( line ); + if( !sectionName.getName().equalsIgnoreCase( ATTRIBUTE_NAME ) ) + { + throw new ManifestException( "Manifest sections should start with a \"" + ATTRIBUTE_NAME + + "\" attribute and not \"" + sectionName.getName() + "\"" ); + } + nextSectionName = sectionName.getValue(); + } + else + { + // we have already started reading this section + // this line is the first attribute. set it and then let the normal + // read handle the rest + Attribute firstAttribute = new Attribute( line ); + section.addAttributeAndCheck( firstAttribute ); + } + + section.setName( nextSectionName ); + nextSectionName = section.read( reader ); + addConfiguredSection( section ); + } + } + + /** + * Construct a manifest from Ant's default manifest file. + * + * @return The DefaultManifest value + * @exception BuildException Description of Exception + */ + public static Manifest getDefaultManifest() + throws BuildException + { + try + { + String s = "/org/apache/tools/ant/defaultManifest.mf"; + InputStream in = Manifest.class.getResourceAsStream( s ); + if( in == null ) + { + throw new BuildException( "Could not find default manifest: " + s ); + } + try + { + return new Manifest( new InputStreamReader( in, "ASCII" ) ); + } + catch( UnsupportedEncodingException e ) + { + return new Manifest( new InputStreamReader( in ) ); + } + } + catch( ManifestException e ) + { + throw new BuildException( "Default manifest is invalid !!" ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read default manifest", e ); + } + } + + /** + * The name of the manifest file to write (if used as a task). + * + * @param f The new File value + */ + public void setFile( File f ) + { + manifestFile = f; + } + + /** + * Shall we update or replace an existing manifest? + * + * @param m The new Mode value + */ + public void setMode( Mode m ) + { + mode = m; + } + + /** + * Get the warnings for this manifest. + * + * @return an enumeration of warning strings + */ + public Enumeration getWarnings() + { + Vector warnings = new Vector(); + + for( Enumeration e2 = mainSection.getWarnings(); e2.hasMoreElements(); ) + { + warnings.addElement( e2.nextElement() ); + } + + // create a vector and add in the warnings for all the sections + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + for( Enumeration e2 = section.getWarnings(); e2.hasMoreElements(); ) + { + warnings.addElement( e2.nextElement() ); + } + } + + return warnings.elements(); + } + + public void addConfiguredAttribute( Attribute attribute ) + throws ManifestException + { + mainSection.addConfiguredAttribute( attribute ); + } + + public void addConfiguredSection( Section section ) + throws ManifestException + { + if( section.getName() == null ) + { + throw new BuildException( "Sections must have a name" ); + } + sections.put( section.getName().toLowerCase(), section ); + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Manifest ) ) + { + return false; + } + + Manifest rhsManifest = ( Manifest )rhs; + if( manifestVersion == null ) + { + if( rhsManifest.manifestVersion != null ) + { + return false; + } + } + else if( !manifestVersion.equals( rhsManifest.manifestVersion ) ) + { + return false; + } + if( sections.size() != rhsManifest.sections.size() ) + { + return false; + } + + if( !mainSection.equals( rhsManifest.mainSection ) ) + { + return false; + } + + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + Section rhsSection = ( Section )rhsManifest.sections.get( section.getName().toLowerCase() ); + if( !section.equals( rhsSection ) ) + { + return false; + } + } + + return true; + } + + /** + * Create or update the Manifest when used as a task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( manifestFile == null ) + { + throw new BuildException( "the file attribute is required" ); + } + + Manifest toWrite = getDefaultManifest(); + + if( mode.getValue().equals( "update" ) && manifestFile.exists() ) + { + FileReader f = null; + try + { + f = new FileReader( manifestFile ); + toWrite.merge( new Manifest( f ) ); + } + catch( ManifestException m ) + { + throw new BuildException( "Existing manifest " + manifestFile + + " is invalid", m, location ); + } + catch( IOException e ) + { + throw new BuildException( "Failed to read " + manifestFile, + e, location ); + } + finally + { + if( f != null ) + { + try + { + f.close(); + } + catch( IOException e ) + {} + } + } + } + + try + { + toWrite.merge( this ); + } + catch( ManifestException m ) + { + throw new BuildException( "Manifest is invalid", m, location ); + } + + PrintWriter w = null; + try + { + w = new PrintWriter( new FileWriter( manifestFile ) ); + toWrite.write( w ); + } + catch( IOException e ) + { + throw new BuildException( "Failed to write " + manifestFile, + e, location ); + } + finally + { + if( w != null ) + { + w.close(); + } + } + } + + /** + * Merge the contents of the given manifest into this manifest + * + * @param other the Manifest to be merged with this one. + * @throws ManifestException if there is a problem merging the manfest + * according to the Manifest spec. + */ + public void merge( Manifest other ) + throws ManifestException + { + if( other.manifestVersion != null ) + { + manifestVersion = other.manifestVersion; + } + mainSection.merge( other.mainSection ); + for( Enumeration e = other.sections.keys(); e.hasMoreElements(); ) + { + String sectionName = ( String )e.nextElement(); + Section ourSection = ( Section )sections.get( sectionName ); + Section otherSection = ( Section )other.sections.get( sectionName ); + if( ourSection == null ) + { + sections.put( sectionName.toLowerCase(), otherSection ); + } + else + { + ourSection.merge( otherSection ); + } + } + + } + + /** + * Convert the manifest to its string representation + * + * @return a multiline string with the Manifest as it appears in a Manifest + * file. + */ + public String toString() + { + StringWriter sw = new StringWriter(); + try + { + write( new PrintWriter( sw ) ); + } + catch( IOException e ) + { + return null; + } + return sw.toString(); + } + + /** + * Write the manifest out to a print writer. + * + * @param writer the Writer to which the manifest is written + * @throws IOException if the manifest cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + writer.println( ATTRIBUTE_MANIFEST_VERSION + ": " + manifestVersion ); + String signatureVersion = mainSection.getAttributeValue( ATTRIBUTE_SIGNATURE_VERSION ); + if( signatureVersion != null ) + { + writer.println( ATTRIBUTE_SIGNATURE_VERSION + ": " + signatureVersion ); + mainSection.removeAttribute( ATTRIBUTE_SIGNATURE_VERSION ); + } + mainSection.write( writer ); + if( signatureVersion != null ) + { + try + { + mainSection.addConfiguredAttribute( new Attribute( ATTRIBUTE_SIGNATURE_VERSION, signatureVersion ) ); + } + catch( ManifestException e ) + { + // shouldn't happen - ignore + } + } + + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + section.write( writer ); + } + } + + /** + * Class to hold manifest attributes + * + * @author RT + */ + public static class Attribute + { + /** + * The attribute's name + */ + private String name = null; + + /** + * The attribute's value + */ + private String value = null; + + /** + * Construct an empty attribute + */ + public Attribute() { } + + /** + * Construct an attribute by parsing a line from the Manifest + * + * @param line the line containing the attribute name and value + * @exception ManifestException Description of Exception + * @throws ManifestException if the line is not valid + */ + public Attribute( String line ) + throws ManifestException + { + parse( line ); + } + + /** + * Construct a manifest by specifying its name and value + * + * @param name the attribute's name + * @param value the Attribute's value + */ + public Attribute( String name, String value ) + { + this.name = name; + this.value = value; + } + + /** + * Set the Attribute's name + * + * @param name the attribute's name + */ + public void setName( String name ) + { + this.name = name; + } + + /** + * Set the Attribute's value + * + * @param value the attribute's value + */ + public void setValue( String value ) + { + this.value = value; + } + + /** + * Get the Attribute's name + * + * @return the attribute's name. + */ + public String getName() + { + return name; + } + + /** + * Get the Attribute's value + * + * @return the attribute's value. + */ + public String getValue() + { + return value; + } + + /** + * Add a continuation line from the Manifest file When lines are too + * long in a manifest, they are continued on the next line by starting + * with a space. This method adds the continuation data to the attribute + * value by skipping the first character. + * + * @param line The feature to be added to the Continuation attribute + */ + public void addContinuation( String line ) + { + value += line.substring( 1 ); + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Attribute ) ) + { + return false; + } + + Attribute rhsAttribute = ( Attribute )rhs; + return ( name != null && rhsAttribute.name != null && + name.toLowerCase().equals( rhsAttribute.name.toLowerCase() ) && + value != null && value.equals( rhsAttribute.value ) ); + } + + /** + * Parse a line into name and value pairs + * + * @param line the line to be parsed + * @throws ManifestException if the line does not contain a colon + * separating the name and value + */ + public void parse( String line ) + throws ManifestException + { + int index = line.indexOf( ": " ); + if( index == -1 ) + { + throw new ManifestException( "Manifest line \"" + line + "\" is not valid as it does not " + + "contain a name and a value separated by ': ' " ); + } + name = line.substring( 0, index ); + value = line.substring( index + 2 ); + } + + /** + * Write the attribute out to a print writer. + * + * @param writer the Writer to which the attribute is written + * @throws IOException if the attribte value cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + String line = name + ": " + value; + while( line.getBytes().length > MAX_LINE_LENGTH ) + { + // try to find a MAX_LINE_LENGTH byte section + int breakIndex = MAX_LINE_LENGTH; + String section = line.substring( 0, breakIndex ); + while( section.getBytes().length > MAX_LINE_LENGTH && breakIndex > 0 ) + { + breakIndex--; + section = line.substring( 0, breakIndex ); + } + if( breakIndex == 0 ) + { + throw new IOException( "Unable to write manifest line " + name + ": " + value ); + } + writer.println( section ); + line = " " + line.substring( breakIndex ); + } + writer.println( line ); + } + } + + /** + * Helper class for Manifest's mode attribute. + * + * @author RT + */ + public static class Mode extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"update", "replace"}; + } + } + + /** + * Class to represent an individual section in the Manifest. A section + * consists of a set of attribute values, separated from other sections by a + * blank line. + * + * @author RT + */ + public static class Section + { + private Vector warnings = new Vector(); + + /** + * The section's name if any. The main section in a manifest is unnamed. + */ + private String name = null; + + /** + * The section's attributes. + */ + private Hashtable attributes = new Hashtable(); + + /** + * Set the Section's name + * + * @param name the section's name + */ + public void setName( String name ) + { + this.name = name; + } + + /** + * Get the value of the attribute with the name given. + * + * @param attributeName the name of the attribute to be returned. + * @return the attribute's value or null if the attribute does not exist + * in the section + */ + public String getAttributeValue( String attributeName ) + { + Object attribute = attributes.get( attributeName.toLowerCase() ); + if( attribute == null ) + { + return null; + } + if( attribute instanceof Attribute ) + { + return ( ( Attribute )attribute ).getValue(); + } + else + { + String value = ""; + for( Enumeration e = ( ( Vector )attribute ).elements(); e.hasMoreElements(); ) + { + Attribute classpathAttribute = ( Attribute )e.nextElement(); + value += classpathAttribute.getValue() + " "; + } + return value.trim(); + } + } + + /** + * Get the Section's name + * + * @return the section's name. + */ + public String getName() + { + return name; + } + + public Enumeration getWarnings() + { + return warnings.elements(); + } + + /** + * Add an attribute to the section + * + * @param attribute the attribute to be added. + * @return the value of the attribute if it is a name attribute - null + * other wise + * @throws ManifestException if the attribute already exists in this + * section. + */ + public String addAttributeAndCheck( Attribute attribute ) + throws ManifestException + { + if( attribute.getName() == null || attribute.getValue() == null ) + { + throw new BuildException( "Attributes must have name and value" ); + } + if( attribute.getName().equalsIgnoreCase( ATTRIBUTE_NAME ) ) + { + warnings.addElement( "\"" + ATTRIBUTE_NAME + "\" attributes should not occur in the " + + "main section and must be the first element in all " + + "other sections: \"" + attribute.getName() + ": " + attribute.getValue() + "\"" ); + return attribute.getValue(); + } + + if( attribute.getName().toLowerCase().startsWith( ATTRIBUTE_FROM.toLowerCase() ) ) + { + warnings.addElement( "Manifest attributes should not start with \"" + + ATTRIBUTE_FROM + "\" in \"" + attribute.getName() + ": " + attribute.getValue() + "\"" ); + } + else + { + // classpath attributes go into a vector + String attributeName = attribute.getName().toLowerCase(); + if( attributeName.equals( ATTRIBUTE_CLASSPATH ) ) + { + Vector classpathAttrs = ( Vector )attributes.get( attributeName ); + if( classpathAttrs == null ) + { + classpathAttrs = new Vector(); + attributes.put( attributeName, classpathAttrs ); + } + classpathAttrs.addElement( attribute ); + } + else if( attributes.containsKey( attributeName ) ) + { + throw new ManifestException( "The attribute \"" + attribute.getName() + "\" may not " + + "occur more than once in the same section" ); + } + else + { + attributes.put( attributeName, attribute ); + } + } + return null; + } + + public void addConfiguredAttribute( Attribute attribute ) + throws ManifestException + { + String check = addAttributeAndCheck( attribute ); + if( check != null ) + { + throw new BuildException( "Specify the section name using the \"name\" attribute of the

              element rather " + + "than using a \"Name\" manifest attribute" ); + } + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Section ) ) + { + return false; + } + + Section rhsSection = ( Section )rhs; + if( attributes.size() != rhsSection.attributes.size() ) + { + return false; + } + + for( Enumeration e = attributes.elements(); e.hasMoreElements(); ) + { + Attribute attribute = ( Attribute )e.nextElement(); + Attribute rshAttribute = ( Attribute )rhsSection.attributes.get( attribute.getName().toLowerCase() ); + if( !attribute.equals( rshAttribute ) ) + { + return false; + } + } + + return true; + } + + /** + * Merge in another section + * + * @param section the section to be merged with this one. + * @throws ManifestException if the sections cannot be merged. + */ + public void merge( Section section ) + throws ManifestException + { + if( name == null && section.getName() != null || + name != null && !( name.equalsIgnoreCase( section.getName() ) ) ) + { + throw new ManifestException( "Unable to merge sections with different names" ); + } + + for( Enumeration e = section.attributes.keys(); e.hasMoreElements(); ) + { + String attributeName = ( String )e.nextElement(); + if( attributeName.equals( ATTRIBUTE_CLASSPATH ) && + attributes.containsKey( attributeName ) ) + { + // classpath entries are vetors which are merged + Vector classpathAttrs = ( Vector )section.attributes.get( attributeName ); + Vector ourClasspathAttrs = ( Vector )attributes.get( attributeName ); + for( Enumeration e2 = classpathAttrs.elements(); e2.hasMoreElements(); ) + { + ourClasspathAttrs.addElement( e2.nextElement() ); + } + } + else + { + // the merge file always wins + attributes.put( attributeName, section.attributes.get( attributeName ) ); + } + } + + // add in the warnings + for( Enumeration e = section.warnings.elements(); e.hasMoreElements(); ) + { + warnings.addElement( e.nextElement() ); + } + } + + /** + * Read a section through a reader + * + * @param reader the reader from which the section is read + * @return the name of the next section if it has been read as part of + * this section - This only happens if the Manifest is malformed. + * @throws ManifestException if the section is not valid according to + * the JAR spec + * @throws IOException if the section cannot be read from the reader. + */ + public String read( BufferedReader reader ) + throws ManifestException, IOException + { + Attribute attribute = null; + while( true ) + { + String line = reader.readLine(); + if( line == null || line.length() == 0 ) + { + return null; + } + if( line.charAt( 0 ) == ' ' ) + { + // continuation line + if( attribute == null ) + { + if( name != null ) + { + // a continuation on the first line is a continuation of the name - concatenate + // this line and the name + name += line.substring( 1 ); + } + else + { + throw new ManifestException( "Can't start an attribute with a continuation line " + line ); + } + } + else + { + attribute.addContinuation( line ); + } + } + else + { + attribute = new Attribute( line ); + String nameReadAhead = addAttributeAndCheck( attribute ); + if( nameReadAhead != null ) + { + return nameReadAhead; + } + } + } + } + + /** + * Remove tge given attribute from the section + * + * @param attributeName the name of the attribute to be removed. + */ + public void removeAttribute( String attributeName ) + { + attributes.remove( attributeName.toLowerCase() ); + } + + /** + * Write the section out to a print writer. + * + * @param writer the Writer to which the section is written + * @throws IOException if the section cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + if( name != null ) + { + Attribute nameAttr = new Attribute( ATTRIBUTE_NAME, name ); + nameAttr.write( writer ); + } + for( Enumeration e = attributes.elements(); e.hasMoreElements(); ) + { + Object object = e.nextElement(); + if( object instanceof Attribute ) + { + Attribute attribute = ( Attribute )object; + attribute.write( writer ); + } + else + { + Vector attrList = ( Vector )object; + for( Enumeration e2 = attrList.elements(); e2.hasMoreElements(); ) + { + Attribute attribute = ( Attribute )e2.nextElement(); + attribute.write( writer ); + } + } + } + writer.println(); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ManifestException.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ManifestException.java new file mode 100644 index 000000000..813da555e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ManifestException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + + + +/** + * Exception thrown indicating problems in a JAR Manifest + * + * @author Conor MacNeill + */ +public class ManifestException extends Exception +{ + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of or information about the exception. + */ + public ManifestException( String msg ) + { + super( msg ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/MatchingTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/MatchingTask.java new file mode 100644 index 000000000..a03cb9be7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/MatchingTask.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + +/** + * This is an abstract task that should be used by all those tasks that require + * to include or exclude files based on pattern matching. + * + * @author Arnout J. Kuiper ajkuiper@wxs.nl + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Sam Ruby rubys@us.ibm.com + * @author Jon S. Stevens jon@clearink.com + * @author Stefan Bodewig + */ + +public abstract class MatchingTask extends Task +{ + + protected boolean useDefaultExcludes = true; + protected FileSet fileset = new FileSet(); + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + fileset.setExcludes( excludes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param excludesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setExcludesfile( File excludesfile ) + { + fileset.setExcludesfile( excludesfile ); + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + fileset.setIncludes( includes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param includesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setIncludesfile( File includesfile ) + { + fileset.setIncludesfile( includesfile ); + } + + /** + * List of filenames and directory names to not include. They should be + * either , or " " (space) separated. The ignored files will be logged. + * + * @param ignoreString the string containing the files to ignore. + */ + public void XsetIgnore( String ignoreString ) + { + log( "The ignore attribute is deprecated." + + "Please use the excludes attribute.", + Project.MSG_WARN ); + if( ignoreString != null && ignoreString.length() > 0 ) + { + Vector tmpExcludes = new Vector(); + StringTokenizer tok = new StringTokenizer( ignoreString, ", ", false ); + while( tok.hasMoreTokens() ) + { + createExclude().setName( "**/" + tok.nextToken().trim() + "/**" ); + } + } + } + + /** + * Set this to be the items in the base directory that you want to be + * included. You can also specify "*" for the items (ie: items="*") and it + * will include all the items in the base directory. + * + * @param itemString the string containing the files to include. + */ + public void XsetItems( String itemString ) + { + log( "The items attribute is deprecated. " + + "Please use the includes attribute.", + Project.MSG_WARN ); + if( itemString == null || itemString.equals( "*" ) + || itemString.equals( "." ) ) + { + createInclude().setName( "**" ); + } + else + { + StringTokenizer tok = new StringTokenizer( itemString, ", " ); + while( tok.hasMoreTokens() ) + { + String pattern = tok.nextToken().trim(); + if( pattern.length() > 0 ) + { + createInclude().setName( pattern + "/**" ); + } + } + } + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + return fileset.createExclude(); + } + + /** + * add a name entry on the include files list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExcludesFile() + { + return fileset.createExcludesFile(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + return fileset.createInclude(); + } + + /** + * add a name entry on the include files list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createIncludesFile() + { + return fileset.createIncludesFile(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + return fileset.createPatternSet(); + } + + /** + * Returns the directory scanner needed to access the files to process. + * + * @param baseDir Description of Parameter + * @return The DirectoryScanner value + */ + protected DirectoryScanner getDirectoryScanner( File baseDir ) + { + fileset.setDir( baseDir ); + fileset.setDefaultexcludes( useDefaultExcludes ); + return fileset.getDirectoryScanner( project ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Mkdir.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Mkdir.java new file mode 100644 index 000000000..c323430a4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Mkdir.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Creates a given directory. + * + * @author duncan@x180.com + */ + +public class Mkdir extends Task +{ + + private File dir; + + public void setDir( File dir ) + { + this.dir = dir; + } + + public void execute() + throws BuildException + { + if( dir == null ) + { + throw new BuildException( "dir attribute is required", location ); + } + + if( dir.isFile() ) + { + throw new BuildException( "Unable to create directory as a file already exists with that name: " + dir.getAbsolutePath() ); + } + + if( !dir.exists() ) + { + boolean result = dir.mkdirs(); + if( result == false ) + { + String msg = "Directory " + dir.getAbsolutePath() + " creation was not " + + "successful for an unknown reason"; + throw new BuildException( msg, location ); + } + log( "Created dir: " + dir.getAbsolutePath() ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Move.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Move.java new file mode 100644 index 000000000..75811008d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Move.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; + +/** + * Moves a file or directory to a new file or directory. By default, the + * destination is overwriten when existing. When overwrite is turned off, then + * files are only moved if the source file is newer than the destination file, + * or when the destination file does not exist.

              + * + * Source files and directories are only deleted when the file or directory has + * been copied to the destination successfully. Filtering also works.

              + * + * This implementation is based on Arnout Kuiper's initial design document, the + * following mailing list discussions, and the copyfile/copydir tasks.

              + * + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Magesh Umasankar + */ +public class Move extends Copy +{ + + public Move() + { + super(); + forceOverwrite = true; + } + + /** + * Go and delete the directory tree. + * + * @param d Description of Parameter + */ + protected void deleteDir( File d ) + { + String[] list = d.list(); + if( list == null ) + return;// on an io error list() can return null + + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + deleteDir( f ); + } + else + { + throw new BuildException( "UNEXPECTED ERROR - The file " + f.getAbsolutePath() + " should not exist!" ); + } + } + log( "Deleting directory " + d.getAbsolutePath(), verbosity ); + if( !d.delete() ) + { + throw new BuildException( "Unable to delete directory " + d.getAbsolutePath() ); + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + protected void doFileOperations() + { + //Attempt complete directory renames, if any, first. + if( completeDirMap.size() > 0 ) + { + Enumeration e = completeDirMap.keys(); + while( e.hasMoreElements() ) + { + File fromDir = ( File )e.nextElement(); + File toDir = ( File )completeDirMap.get( fromDir ); + try + { + log( "Attempting to rename dir: " + fromDir + + " to " + toDir, verbosity ); + renameFile( fromDir, toDir, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to rename dir " + fromDir + + " to " + toDir + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + if( fileCopyMap.size() > 0 ) + {// files to move + log( "Moving " + fileCopyMap.size() + " files to " + + destDir.getAbsolutePath() ); + + Enumeration e = fileCopyMap.keys(); + while( e.hasMoreElements() ) + { + String fromFile = ( String )e.nextElement(); + String toFile = ( String )fileCopyMap.get( fromFile ); + + if( fromFile.equals( toFile ) ) + { + log( "Skipping self-move of " + fromFile, verbosity ); + continue; + } + + boolean moved = false; + File f = new File( fromFile ); + + if( f.exists() ) + {//Is this file still available to be moved? + File d = new File( toFile ); + + try + { + log( "Attempting to rename: " + fromFile + + " to " + toFile, verbosity ); + moved = renameFile( f, d, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to rename " + fromFile + + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + + if( !moved ) + { + try + { + log( "Moving " + fromFile + " to " + toFile, verbosity ); + + FilterSetCollection executionFilters = new FilterSetCollection(); + if( filtering ) + { + executionFilters.addFilterSet( project.getGlobalFilterSet() ); + } + for( Enumeration filterEnum = getFilterSets().elements(); filterEnum.hasMoreElements(); ) + { + executionFilters.addFilterSet( ( FilterSet )filterEnum.nextElement() ); + } + getFileUtils().copyFile( f, d, executionFilters, + forceOverwrite ); + + f = new File( fromFile ); + if( !f.delete() ) + { + throw new BuildException( "Unable to delete file " + + f.getAbsolutePath() ); + } + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + } + } + + if( includeEmpty ) + { + Enumeration e = dirCopyMap.elements(); + int count = 0; + while( e.hasMoreElements() ) + { + File d = new File( ( String )e.nextElement() ); + if( !d.exists() ) + { + if( !d.mkdirs() ) + { + log( "Unable to create directory " + d.getAbsolutePath(), Project.MSG_ERR ); + } + else + { + count++; + } + } + } + + if( count > 0 ) + { + log( "Moved " + count + " empty directories to " + destDir.getAbsolutePath() ); + } + } + + if( filesets.size() > 0 ) + { + Enumeration e = filesets.elements(); + while( e.hasMoreElements() ) + { + FileSet fs = ( FileSet )e.nextElement(); + File dir = fs.getDir( project ); + + if( okToDelete( dir ) ) + { + deleteDir( dir ); + } + } + } + } + + /** + * Its only ok to delete a directory tree if there are no files in it. + * + * @param d Description of Parameter + * @return Description of the Returned Value + */ + protected boolean okToDelete( File d ) + { + String[] list = d.list(); + if( list == null ) + return false;// maybe io error? + + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + if( !okToDelete( f ) ) + return false; + } + else + { + return false;// found a file + } + } + + return true; + } + + /** + * Attempts to rename a file from a source to a destination. If overwrite is + * set to true, this method overwrites existing file even if the destination + * file is newer. Otherwise, the source file is renamed only if the + * destination file is older than it. Method then checks if token filtering + * is used. If it is, this method returns false assuming it is the + * responsibility to the copyFile method. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @throws IOException + */ + protected boolean renameFile( File sourceFile, File destFile, + boolean filtering, boolean overwrite ) + throws IOException, BuildException + { + + boolean renamed = true; + if( !filtering ) + { + // ensure that parent dir of dest file exists! + // not using getParentFile method to stay 1.1 compat + String parentPath = destFile.getParent(); + if( parentPath != null ) + { + File parent = new File( parentPath ); + if( !parent.exists() ) + { + parent.mkdirs(); + } + } + + if( destFile.exists() ) + { + if( !destFile.delete() ) + { + throw new BuildException( "Unable to remove existing file " + + destFile ); + } + } + renamed = sourceFile.renameTo( destFile ); + } + else + { + renamed = false; + } + return renamed; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Pack.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Pack.java new file mode 100644 index 000000000..3a9d0701d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Pack.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Abstract Base class for pack tasks. + * + * @author Magesh Umasankar + */ + +public abstract class Pack extends Task +{ + protected File source; + + protected File zipFile; + + public void setSrc( File src ) + { + source = src; + } + + public void setZipfile( File zipFile ) + { + this.zipFile = zipFile; + } + + public void execute() + throws BuildException + { + validate(); + log( "Building: " + zipFile.getAbsolutePath() ); + pack(); + } + + protected abstract void pack(); + + protected void zipFile( File file, OutputStream zOut ) + throws IOException + { + FileInputStream fIn = new FileInputStream( file ); + try + { + zipFile( fIn, zOut ); + } + finally + { + fIn.close(); + } + } + + private void validate() + { + if( zipFile == null ) + { + throw new BuildException( "zipfile attribute is required", location ); + } + + if( source == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( source.isDirectory() ) + { + throw new BuildException( "Src attribute must not " + + "represent a directory!", location ); + } + } + + private void zipFile( InputStream in, OutputStream zOut ) + throws IOException + { + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + zOut.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Parallel.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Parallel.java new file mode 100644 index 000000000..1203369ac --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Parallel.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; + + +/** + * Implements a multi threaded task execution.

              + * + * + * + * @author Thomas Christen chr@active.ch + * @author Conor MacNeill + */ +public class Parallel extends Task + implements TaskContainer +{ + + /** + * Collection holding the nested tasks + */ + private Vector nestedTasks = new Vector(); + + + /** + * Add a nested task to execute parallel (asynchron).

              + * + * + * + * @param nestedTask Nested task to be executed in parallel + * @exception BuildException Description of Exception + */ + public void addTask( Task nestedTask ) + throws BuildException + { + nestedTasks.addElement( nestedTask ); + } + + /** + * Block execution until the specified time or for a specified amount of + * milliseconds and if defined, execute the wait status. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + TaskThread[] threads = new TaskThread[nestedTasks.size()]; + int threadNumber = 0; + for( Enumeration e = nestedTasks.elements(); e.hasMoreElements(); threadNumber++ ) + { + Task nestedTask = ( Task )e.nextElement(); + threads[threadNumber] = new TaskThread( threadNumber, nestedTask ); + } + + // now start all threads + for( int i = 0; i < threads.length; ++i ) + { + threads[i].start(); + } + + // now join to all the threads + for( int i = 0; i < threads.length; ++i ) + { + try + { + threads[i].join(); + } + catch( InterruptedException ie ) + { + // who would interrupt me at a time like this? + } + } + + // now did any of the threads throw an exception + StringBuffer exceptionMessage = new StringBuffer(); + String lSep = System.getProperty( "line.separator" ); + int numExceptions = 0; + Throwable firstException = null; + Location firstLocation = Location.UNKNOWN_LOCATION; + ; + for( int i = 0; i < threads.length; ++i ) + { + Throwable t = threads[i].getException(); + if( t != null ) + { + numExceptions++; + if( firstException == null ) + { + firstException = t; + } + if( t instanceof BuildException && + firstLocation == Location.UNKNOWN_LOCATION ) + { + firstLocation = ( ( BuildException )t ).getLocation(); + } + exceptionMessage.append( lSep ); + exceptionMessage.append( t.getMessage() ); + } + } + + if( numExceptions == 1 ) + { + if( firstException instanceof BuildException ) + { + throw ( BuildException )firstException; + } + else + { + throw new BuildException( firstException ); + } + } + else if( numExceptions > 1 ) + { + throw new BuildException( exceptionMessage.toString(), firstLocation ); + } + } + + class TaskThread extends Thread + { + private Throwable exception; + private Task task; + private int taskNumber; + + /** + * Construct a new TaskThread

              + * + * + * + * @param task the Task to be executed in a seperate thread + * @param taskNumber Description of Parameter + */ + TaskThread( int taskNumber, Task task ) + { + this.task = task; + this.taskNumber = taskNumber; + } + + public Throwable getException() + { + return exception; + } + + /** + * Executes the task within a thread and takes care about Exceptions + * raised within the task. + */ + public void run() + { + try + { + task.perform(); + } + catch( Throwable t ) + { + exception = t; + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Patch.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Patch.java new file mode 100644 index 000000000..8c7657b12 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Patch.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; + +/** + * Task as a layer on top of patch. Patch applies a diff file to an original. + * + * @author Stefan Bodewig + */ +public class Patch extends Task +{ + private boolean havePatchfile = false; + private Commandline cmd = new Commandline(); + + private File originalFile; + + /** + * Shall patch write backups. + * + * @param backups The new Backups value + */ + public void setBackups( boolean backups ) + { + if( backups ) + { + cmd.createArgument().setValue( "-b" ); + } + } + + /** + * Ignore whitespace differences. + * + * @param ignore The new Ignorewhitespace value + */ + public void setIgnorewhitespace( boolean ignore ) + { + if( ignore ) + { + cmd.createArgument().setValue( "-l" ); + } + } + + /** + * The file to patch. + * + * @param file The new Originalfile value + */ + public void setOriginalfile( File file ) + { + originalFile = file; + } + + /** + * The file containing the diff output. + * + * @param file The new Patchfile value + */ + public void setPatchfile( File file ) + { + if( !file.exists() ) + { + throw new BuildException( "patchfile " + file + " doesn\'t exist", + location ); + } + cmd.createArgument().setValue( "-i" ); + cmd.createArgument().setFile( file ); + havePatchfile = true; + } + + /** + * Work silently unless an error occurs. + * + * @param q The new Quiet value + */ + public void setQuiet( boolean q ) + { + if( q ) + { + cmd.createArgument().setValue( "-s" ); + } + } + + /** + * Assume patch was created with old and new files swapped. + * + * @param r The new Reverse value + */ + public void setReverse( boolean r ) + { + if( r ) + { + cmd.createArgument().setValue( "-R" ); + } + } + + /** + * Strip the smallest prefix containing num leading slashes from + * filenames.

              + * + * patch's -p option. + * + * @param num The new Strip value + * @exception BuildException Description of Exception + */ + public void setStrip( int num ) + throws BuildException + { + if( num < 0 ) + { + throw new BuildException( "strip has to be >= 0", location ); + } + cmd.createArgument().setValue( "-p" + num ); + } + + public void execute() + throws BuildException + { + if( !havePatchfile ) + { + throw new BuildException( "patchfile argument is required", + location ); + } + + Commandline toExecute = ( Commandline )cmd.clone(); + toExecute.setExecutable( "patch" ); + + if( originalFile != null ) + { + toExecute.createArgument().setFile( originalFile ); + } + + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), + null ); + exe.setCommandline( toExecute.getCommandline() ); + try + { + exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + +}// Patch diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PathConvert.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PathConvert.java new file mode 100644 index 000000000..e508b580a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PathConvert.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * This task converts path and classpath information to a specific target OS + * format. The resulting formatted path is placed into a specified property.

              + * + * LIMITATION: Currently this implementation groups all machines into one of two + * types: Unix or Windows. Unix is defined as NOT windows. + * + * @author Larry Streepy + * streepy@healthlanguage.com + */ +public class PathConvert extends Task +{ + + // Members + private Path path = null;// Path to be converted + private Reference refid = null;// Reference to path/fileset to convert + private String targetOS = null;// The target OS type + private boolean targetWindows = false;// Set when targetOS is set + private boolean onWindows = false;// Set if we're running on windows + private String property = null;// The property to receive the results + private Vector prefixMap = new Vector();// Path prefix map + private String pathSep = null;// User override on path sep char + private String dirSep = null; + + /** + * Override the default directory separator string for the target os + * + * @param sep The new DirSep value + */ + public void setDirSep( String sep ) + { + dirSep = sep; + } + + /** + * Override the default path separator string for the target os + * + * @param sep The new PathSep value + */ + public void setPathSep( String sep ) + { + pathSep = sep; + } + + /** + * Set the value of the proprty attribute - this is the property into which + * our converted path will be placed. + * + * @param p The new Property value + */ + public void setProperty( String p ) + { + property = p; + } + + /** + * Adds a reference to a PATH or FILESET defined elsewhere. + * + * @param r The new Refid value + */ + public void setRefid( Reference r ) + { + if( path != null ) + throw noChildrenAllowed(); + + refid = r; + } + + /** + * Set the value of the targetos attribute + * + * @param target The new Targetos value + */ + public void setTargetos( String target ) + { + + targetOS = target.toLowerCase(); + + if( !targetOS.equals( "windows" ) && !target.equals( "unix" ) && + !targetOS.equals( "netware" ) ) + { + throw new BuildException( "targetos must be one of 'unix', 'netware', or 'windows'" ); + } + + // Currently, we deal with only two path formats: Unix and Windows + // And Unix is everything that is not Windows + + // for NetWare, piggy-back on Windows, since in the validateSetup code, + // the same assumptions can be made as with windows - + // that ; is the path separator + + targetWindows = ( targetOS.equals( "windows" ) || targetOS.equals( "netware" ) ); + } + + /** + * Has the refid attribute of this element been set? + * + * @return The Reference value + */ + public boolean isReference() + { + return refid != null; + } + + /** + * Create a nested MAP element + * + * @return Description of the Returned Value + */ + public MapEntry createMap() + { + + MapEntry entry = new MapEntry(); + prefixMap.addElement( entry ); + return entry; + } + + /** + * Create a nested PATH element + * + * @return Description of the Returned Value + */ + public Path createPath() + { + + if( isReference() ) + throw noChildrenAllowed(); + + if( path == null ) + { + path = new Path( getProject() ); + } + return path.createPath(); + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // If we are a reference, the create a Path from the reference + if( isReference() ) + { + path = new Path( getProject() ).createPath(); + + Object obj = refid.getReferencedObject( getProject() ); + + if( obj instanceof Path ) + { + path.setRefid( refid ); + } + else if( obj instanceof FileSet ) + { + FileSet fs = ( FileSet )obj; + path.addFileset( fs ); + } + else + { + throw new BuildException( "'refid' does not refer to a path or fileset" ); + } + } + + validateSetup();// validate our setup + + // Currently, we deal with only two path formats: Unix and Windows + // And Unix is everything that is not Windows + // (with the exception for NetWare below) + + String osname = System.getProperty( "os.name" ).toLowerCase(); + + // for NetWare, piggy-back on Windows, since here and in the + // apply code, the same assumptions can be made as with windows - + // that \\ is an OK separator, and do comparisons case-insensitive. + onWindows = ( ( osname.indexOf( "windows" ) >= 0 ) || + ( osname.indexOf( "netware" ) >= 0 ) ); + + // Determine the from/to char mappings for dir sep + char fromDirSep = onWindows ? '\\' : '/'; + char toDirSep = dirSep.charAt( 0 ); + + StringBuffer rslt = new StringBuffer( 100 ); + + // Get the list of path components in canonical form + String[] elems = path.list(); + + for( int i = 0; i < elems.length; i++ ) + { + String elem = elems[i]; + + elem = mapElement( elem );// Apply the path prefix map + + // Now convert the path and file separator characters from the + // current os to the target os. + + elem = elem.replace( fromDirSep, toDirSep ); + + if( i != 0 ) + rslt.append( pathSep ); + rslt.append( elem ); + } + + // Place the result into the specified property + String value = rslt.toString(); + + log( "Set property " + property + " = " + value, Project.MSG_VERBOSE ); + + getProject().setNewProperty( property, value ); + } + + /** + * Apply the configured map to a path element. The map is used to convert + * between Windows drive letters and Unix paths. If no map is configured, + * then the input string is returned unchanged. + * + * @param elem The path element to apply the map to + * @return String Updated element + */ + private String mapElement( String elem ) + { + + int size = prefixMap.size(); + + if( size != 0 ) + { + + // Iterate over the map entries and apply each one. Stop when one of the + // entries actually changes the element + + for( int i = 0; i < size; i++ ) + { + MapEntry entry = ( MapEntry )prefixMap.elementAt( i ); + String newElem = entry.apply( elem ); + + // Note I'm using "!=" to see if we got a new object back from + // the apply method. + + if( newElem != elem ) + { + elem = newElem; + break;// We applied one, so we're done + } + } + } + + return elem; + } + + /** + * Creates an exception that indicates that this XML element must not have + * child elements if the refid attribute is set. + * + * @return Description of the Returned Value + */ + private BuildException noChildrenAllowed() + { + return new BuildException( "You must not specify nested PATH elements when using refid" ); + } + + /** + * Validate that all our parameters have been properly initialized. + * + * @throws BuildException if something is not setup properly + */ + private void validateSetup() + throws BuildException + { + + if( path == null ) + throw new BuildException( "You must specify a path to convert" ); + + if( property == null ) + throw new BuildException( "You must specify a property" ); + + // Must either have a target OS or both a dirSep and pathSep + + if( targetOS == null && pathSep == null && dirSep == null ) + throw new BuildException( "You must specify at least one of targetOS, dirSep, or pathSep" ); + + // Determine the separator strings. The dirsep and pathsep attributes + // override the targetOS settings. + String dsep = File.separator; + String psep = File.pathSeparator; + + if( targetOS != null ) + { + psep = targetWindows ? ";" : ":"; + dsep = targetWindows ? "\\" : "/"; + } + + if( pathSep != null ) + {// override with pathsep= + psep = pathSep; + } + + if( dirSep != null ) + {// override with dirsep= + dsep = dirSep; + } + + pathSep = psep; + dirSep = dsep; + } + + /** + * Helper class, holds the nested values. Elements will look like + * this: <map from="d:" to="/foo"/>

              + * + * When running on windows, the prefix comparison will be case insensitive. + * + * @author RT + */ + public class MapEntry + { + + // Members + private String from = null; + private String to = null; + + /** + * Set the "from" attribute of the map entry + * + * @param from The new From value + */ + public void setFrom( String from ) + { + this.from = from; + } + + /** + * Set the "to" attribute of the map entry + * + * @param to The new To value + */ + public void setTo( String to ) + { + this.to = to; + } + + /** + * Apply this map entry to a given path element + * + * @param elem Path element to process + * @return String Updated path element after mapping + */ + public String apply( String elem ) + { + if( from == null || to == null ) + { + throw new BuildException( "Both 'from' and 'to' must be set in a map entry" ); + } + + // If we're on windows, then do the comparison ignoring case + String cmpElem = onWindows ? elem.toLowerCase() : elem; + String cmpFrom = onWindows ? from.toLowerCase() : from; + + // If the element starts with the configured prefix, then convert the prefix + // to the configured 'to' value. + + if( cmpElem.startsWith( cmpFrom ) ) + { + int len = from.length(); + + if( len >= elem.length() ) + { + elem = to; + } + else + { + elem = to + elem.substring( len ); + } + } + + return elem; + } + }// User override on directory sep char +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ProcessDestroyer.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ProcessDestroyer.java new file mode 100644 index 000000000..590e86fd1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ProcessDestroyer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Vector; + +/** + * Destroys all registered Processes when the VM exits. + * + * @author Michael Newcomb + */ +class ProcessDestroyer + extends Thread +{ + + private Vector processes = new Vector(); + + /** + * Constructs a ProcessDestroyer and registers it as a shutdown + * hook. + */ + public ProcessDestroyer() + { + try + { + // check to see if the method exists (support pre-JDK 1.3 VMs) + // + Class[] paramTypes = {Thread.class}; + Method addShutdownHook = + Runtime.class.getMethod( "addShutdownHook", paramTypes ); + + // add the hook + // + Object[] args = {this}; + addShutdownHook.invoke( Runtime.getRuntime(), args ); + } + catch( Exception e ) + { + // it just won't be added as a shutdown hook... :( + } + } + + /** + * Returns true if the specified Process was + * successfully added to the list of processes to destroy upon VM exit. + * + * @param process the process to add + * @return true if the specified Process was + * successfully added + */ + public boolean add( Process process ) + { + processes.addElement( process ); + return processes.contains( process ); + } + + /** + * Returns true if the specified Process was + * successfully removed from the list of processes to destroy upon VM exit. + * + * @param process the process to remove + * @return true if the specified Process was + * successfully removed + */ + public boolean remove( Process process ) + { + return processes.removeElement( process ); + } + + /** + * Invoked by the VM when it is exiting. + */ + public void run() + { + synchronized( processes ) + { + Enumeration e = processes.elements(); + while( e.hasMoreElements() ) + { + ( ( Process )e.nextElement() ).destroy(); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Property.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Property.java new file mode 100644 index 000000000..15b182ecf --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Property.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Will set a Project property. Used to be a hack in ProjectHelper Will not + * override values set by the command line or parent projects. + * + * @author costin@dnt.ro + * @author Sam Ruby + * @author Glenn McAllister + */ +public class Property extends Task +{ + protected Path classpath; + protected String env; + protected File file; + + protected String name; + protected Reference ref; + protected String resource; + + protected boolean userProperty; + protected String value;// set read-only properties + + public Property() + { + super(); + } + + protected Property( boolean userProperty ) + { + this.userProperty = userProperty; + } + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setEnvironment( String env ) + { + this.env = env; + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setLocation( File location ) + { + setValue( location.getAbsolutePath() ); + } + + public void setName( String name ) + { + this.name = name; + } + + public void setRefid( Reference ref ) + { + this.ref = ref; + } + + public void setResource( String resource ) + { + this.resource = resource; + } + + /** + * @param userProperty The new UserProperty value + * @deprecated This was never a supported feature and has been deprecated + * without replacement + */ + public void setUserProperty( boolean userProperty ) + { + log( "DEPRECATED: Ignoring request to set user property in Property task.", + Project.MSG_WARN ); + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getEnvironment() + { + return env; + } + + public File getFile() + { + return file; + } + + public String getName() + { + return name; + } + + public Reference getRefid() + { + return ref; + } + + public String getResource() + { + return resource; + } + + public String getValue() + { + return value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public void execute() + throws BuildException + { + if( name != null ) + { + if( value == null && ref == null ) + { + throw new BuildException( "You must specify value, location or refid with the name attribute", + location ); + } + } + else + { + if( file == null && resource == null && env == null ) + { + throw new BuildException( "You must specify file, resource or environment when not using the name attribute", + location ); + } + } + + if( ( name != null ) && ( value != null ) ) + { + addProperty( name, value ); + } + + if( file != null ) + loadFile( file ); + + if( resource != null ) + loadResource( resource ); + + if( env != null ) + loadEnvironment( env ); + + if( ( name != null ) && ( ref != null ) ) + { + Object obj = ref.getReferencedObject( getProject() ); + if( obj != null ) + { + addProperty( name, obj.toString() ); + } + } + } + + public String toString() + { + return value == null ? "" : value; + } + + protected void addProperties( Properties props ) + { + resolveAllProperties( props ); + Enumeration e = props.keys(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + String value = ( String )props.getProperty( name ); + + String v = project.replaceProperties( value ); + addProperty( name, v ); + } + } + + protected void addProperty( String n, String v ) + { + if( userProperty ) + { + if( project.getUserProperty( n ) == null ) + { + project.setUserProperty( n, v ); + } + else + { + log( "Override ignored for " + n, Project.MSG_VERBOSE ); + } + } + else + { + project.setNewProperty( n, v ); + } + } + + protected void loadEnvironment( String prefix ) + { + Properties props = new Properties(); + if( !prefix.endsWith( "." ) ) + prefix += "."; + log( "Loading Environment " + prefix, Project.MSG_VERBOSE ); + Vector osEnv = Execute.getProcEnvironment(); + for( Enumeration e = osEnv.elements(); e.hasMoreElements(); ) + { + String entry = ( String )e.nextElement(); + int pos = entry.indexOf( '=' ); + if( pos == -1 ) + { + log( "Ignoring: " + entry, Project.MSG_WARN ); + } + else + { + props.put( prefix + entry.substring( 0, pos ), + entry.substring( pos + 1 ) ); + } + } + addProperties( props ); + } + + protected void loadFile( File file ) + throws BuildException + { + Properties props = new Properties(); + log( "Loading " + file.getAbsolutePath(), Project.MSG_VERBOSE ); + try + { + if( file.exists() ) + { + FileInputStream fis = new FileInputStream( file ); + try + { + props.load( fis ); + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + addProperties( props ); + } + else + { + log( "Unable to find property file: " + file.getAbsolutePath(), + Project.MSG_VERBOSE ); + } + } + catch( IOException ex ) + { + throw new BuildException( ex ); + } + } + + protected void loadResource( String name ) + { + Properties props = new Properties(); + log( "Resource Loading " + name, Project.MSG_VERBOSE ); + try + { + ClassLoader cL = null; + InputStream is = null; + + if( classpath != null ) + { + cL = new AntClassLoader( project, classpath ); + } + else + { + cL = this.getClass().getClassLoader(); + } + + if( cL == null ) + { + is = ClassLoader.getSystemResourceAsStream( name ); + } + else + { + is = cL.getResourceAsStream( name ); + } + + if( is != null ) + { + props.load( is ); + addProperties( props ); + } + else + { + log( "Unable to find resource " + name, Project.MSG_WARN ); + } + } + catch( IOException ex ) + { + throw new BuildException( ex ); + } + } + + private void resolveAllProperties( Properties props ) + throws BuildException + { + for( Enumeration e = props.keys(); e.hasMoreElements(); ) + { + String name = ( String )e.nextElement(); + String value = props.getProperty( name ); + + boolean resolved = false; + while( !resolved ) + { + Vector fragments = new Vector(); + Vector propertyRefs = new Vector(); + ProjectHelper.parsePropertyString( value, fragments, propertyRefs ); + + resolved = true; + if( propertyRefs.size() != 0 ) + { + StringBuffer sb = new StringBuffer(); + Enumeration i = fragments.elements(); + Enumeration j = propertyRefs.elements(); + while( i.hasMoreElements() ) + { + String fragment = ( String )i.nextElement(); + if( fragment == null ) + { + String propertyName = ( String )j.nextElement(); + if( propertyName.equals( name ) ) + { + throw new BuildException( "Property " + name + " was circularly defined." ); + } + fragment = getProject().getProperty( propertyName ); + if( fragment == null ) + { + if( props.containsKey( propertyName ) ) + { + fragment = props.getProperty( propertyName ); + resolved = false; + } + else + { + fragment = "${" + propertyName + "}"; + } + } + } + sb.append( fragment ); + } + value = sb.toString(); + props.put( name, value ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PumpStreamHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PumpStreamHandler.java new file mode 100644 index 000000000..ab5109052 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PumpStreamHandler.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copies standard output and error of subprocesses to standard output and error + * of the parent process. TODO: standard input of the subprocess is not + * implemented. + * + * @author thomas.haas@softwired-inc.com + */ +public class PumpStreamHandler implements ExecuteStreamHandler +{ + private Thread errorThread; + + private Thread inputThread; + + private OutputStream out, err; + + public PumpStreamHandler( OutputStream out, OutputStream err ) + { + this.out = out; + this.err = err; + } + + public PumpStreamHandler( OutputStream outAndErr ) + { + this( outAndErr, outAndErr ); + } + + public PumpStreamHandler() + { + this( System.out, System.err ); + } + + + public void setProcessErrorStream( InputStream is ) + { + createProcessErrorPump( is, err ); + } + + + public void setProcessInputStream( OutputStream os ) { } + + public void setProcessOutputStream( InputStream is ) + { + createProcessOutputPump( is, out ); + } + + + public void start() + { + inputThread.start(); + errorThread.start(); + } + + + public void stop() + { + try + { + inputThread.join(); + } + catch( InterruptedException e ) + {} + try + { + errorThread.join(); + } + catch( InterruptedException e ) + {} + try + { + err.flush(); + } + catch( IOException e ) + {} + try + { + out.flush(); + } + catch( IOException e ) + {} + } + + protected OutputStream getErr() + { + return err; + } + + protected OutputStream getOut() + { + return out; + } + + protected void createProcessErrorPump( InputStream is, OutputStream os ) + { + errorThread = createPump( is, os ); + } + + protected void createProcessOutputPump( InputStream is, OutputStream os ) + { + inputThread = createPump( is, os ); + } + + + /** + * Creates a stream pumper to copy the given input stream to the given + * output stream. + * + * @param is Description of Parameter + * @param os Description of Parameter + * @return Description of the Returned Value + */ + protected Thread createPump( InputStream is, OutputStream os ) + { + final Thread result = new Thread( new StreamPumper( is, os ) ); + result.setDaemon( true ); + return result; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Recorder.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Recorder.java new file mode 100644 index 000000000..ebe3fcc92 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Recorder.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * This task is the manager for RecorderEntry's. It is this class that holds all + * entries, modifies them every time the <recorder> task is called, and + * addes them to the build listener process. + * + * @author J D Glanville + * @version 0.5 + * @see RecorderEntry + */ +public class Recorder extends Task +{ + /** + * The list of recorder entries. + */ + private static Hashtable recorderEntries = new Hashtable(); + + ////////////////////////////////////////////////////////////////////// + // ATTRIBUTES + + /** + * The name of the file to record to. + */ + private String filename = null; + /** + * Whether or not to append. Need Boolean to record an unset state (null). + */ + private Boolean append = null; + /** + * Whether to start or stop recording. Need Boolean to record an unset state + * (null). + */ + private Boolean start = null; + /** + * What level to log? -1 means not initialized yet. + */ + private int loglevel = -1; + + /** + * Sets the action for the associated recorder entry. + * + * @param action The action for the entry to take: start or stop. + */ + public void setAction( ActionChoices action ) + { + if( action.getValue().equalsIgnoreCase( "start" ) ) + { + start = Boolean.TRUE; + } + else + { + start = Boolean.FALSE; + } + } + + /** + * Whether or not the logger should append to a previous file. + * + * @param append The new Append value + */ + public void setAppend( boolean append ) + { + this.append = new Boolean( append ); + } + + /** + * Sets the level to which this recorder entry should log to. + * + * @param level The new Loglevel value + * @see VerbosityLevelChoices + */ + public void setLoglevel( VerbosityLevelChoices level ) + { + //I hate cascading if/elseif clauses !!! + String lev = level.getValue(); + if( lev.equalsIgnoreCase( "error" ) ) + { + loglevel = Project.MSG_ERR; + } + else if( lev.equalsIgnoreCase( "warn" ) ) + { + loglevel = Project.MSG_WARN; + } + else if( lev.equalsIgnoreCase( "info" ) ) + { + loglevel = Project.MSG_INFO; + } + else if( lev.equalsIgnoreCase( "verbose" ) ) + { + loglevel = Project.MSG_VERBOSE; + } + else if( lev.equalsIgnoreCase( "debug" ) ) + { + loglevel = Project.MSG_DEBUG; + } + } + + ////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS / INITIALIZERS + + ////////////////////////////////////////////////////////////////////// + // ACCESSOR METHODS + + /** + * Sets the name of the file to log to, and the name of the recorder entry. + * + * @param fname File name of logfile. + */ + public void setName( String fname ) + { + filename = fname; + } + + ////////////////////////////////////////////////////////////////////// + // CORE / MAIN BODY + + /** + * The main execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( filename == null ) + throw new BuildException( "No filename specified" ); + + getProject().log( "setting a recorder for name " + filename, + Project.MSG_DEBUG ); + + // get the recorder entry + RecorderEntry recorder = getRecorder( filename, getProject() ); + // set the values on the recorder + recorder.setMessageOutputLevel( loglevel ); + recorder.setRecordState( start ); + } + + /** + * Gets the recorder that's associated with the passed in name. If the + * recorder doesn't exist, then a new one is created. + * + * @param name Description of Parameter + * @param proj Description of Parameter + * @return The Recorder value + * @exception BuildException Description of Exception + */ + protected RecorderEntry getRecorder( String name, Project proj ) + throws BuildException + { + Object o = recorderEntries.get( name ); + RecorderEntry entry; + if( o == null ) + { + // create a recorder entry + try + { + entry = new RecorderEntry( name ); + PrintStream out = null; + if( append == null ) + { + out = new PrintStream( + new FileOutputStream( name ) ); + } + else + { + out = new PrintStream( + new FileOutputStream( name, append.booleanValue() ) ); + } + entry.setErrorPrintStream( out ); + entry.setOutputPrintStream( out ); + } + catch( IOException ioe ) + { + throw new BuildException( "Problems creating a recorder entry", + ioe ); + } + proj.addBuildListener( entry ); + recorderEntries.put( name, entry ); + } + else + { + entry = ( RecorderEntry )o; + } + return entry; + } + + ////////////////////////////////////////////////////////////////////// + // INNER CLASSES + + /** + * A list of possible values for the setAction() method. + * Possible values include: start and stop. + * + * @author RT + */ + public static class ActionChoices extends EnumeratedAttribute + { + private final static String[] values = {"start", "stop"}; + + public String[] getValues() + { + return values; + } + } + + /** + * A list of possible values for the setLoglevel() method. + * Possible values include: error, warn, info, verbose, debug. + * + * @author RT + */ + public static class VerbosityLevelChoices extends EnumeratedAttribute + { + private final static String[] values = {"error", "warn", "info", + "verbose", "debug"}; + + public String[] getValues() + { + return values; + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/RecorderEntry.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/RecorderEntry.java new file mode 100644 index 000000000..4dad6229e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/RecorderEntry.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.PrintStream; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildLogger; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + + +/** + * This is a class that represents a recorder. This is the listener to the build + * process. + * + * @author J D Glanville + * @version 0.5 + */ +public class RecorderEntry implements BuildLogger +{ + + ////////////////////////////////////////////////////////////////////// + // ATTRIBUTES + + /** + * The name of the file associated with this recorder entry. + */ + private String filename = null; + /** + * The state of the recorder (recorder on or off). + */ + private boolean record = true; + /** + * The current verbosity level to record at. + */ + private int loglevel = Project.MSG_INFO; + /** + * The output PrintStream to record to. + */ + private PrintStream out = null; + /** + * The start time of the last know target. + */ + private long targetStartTime = 0l; + + ////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS / INITIALIZERS + + /** + * @param name The name of this recorder (used as the filename). + */ + protected RecorderEntry( String name ) + { + filename = name; + } + + private static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + + } + + public void setEmacsMode( boolean emacsMode ) + { + throw new java.lang.RuntimeException( "Method setEmacsMode() not yet implemented." ); + } + + public void setErrorPrintStream( PrintStream err ) + { + out = err; + } + + public void setMessageOutputLevel( int level ) + { + if( level >= Project.MSG_ERR && level <= Project.MSG_DEBUG ) + loglevel = level; + } + + public void setOutputPrintStream( PrintStream output ) + { + out = output; + } + + /** + * Turns off or on this recorder. + * + * @param state true for on, false for off, null for no change. + */ + public void setRecordState( Boolean state ) + { + if( state != null ) + record = state.booleanValue(); + } + + ////////////////////////////////////////////////////////////////////// + // ACCESSOR METHODS + + /** + * @return the name of the file the output is sent to. + */ + public String getFilename() + { + return filename; + } + + public void buildFinished( BuildEvent event ) + { + log( "< BUILD FINISHED", Project.MSG_DEBUG ); + + Throwable error = event.getException(); + if( error == null ) + { + out.println( StringUtils.LINE_SEP + "BUILD SUCCESSFUL" ); + } + else + { + out.println( StringUtils.LINE_SEP + "BUILD FAILED" + StringUtils.LINE_SEP ); + error.printStackTrace( out ); + } + out.flush(); + out.close(); + } + + public void buildStarted( BuildEvent event ) + { + log( "> BUILD STARTED", Project.MSG_DEBUG ); + } + + public void messageLogged( BuildEvent event ) + { + log( "--- MESSAGE LOGGED", Project.MSG_DEBUG ); + + StringBuffer buf = new StringBuffer(); + if( event.getTask() != null ) + { + String name = "[" + event.getTask().getTaskName() + "]"; + /** + * @todo replace 12 with DefaultLogger.LEFT_COLUMN_SIZE + */ + for( int i = 0; i < ( 12 - name.length() ); i++ ) + { + buf.append( " " ); + }// for + buf.append( name ); + }// if + buf.append( event.getMessage() ); + + log( buf.toString(), event.getPriority() ); + } + + public void targetFinished( BuildEvent event ) + { + log( "<< TARGET FINISHED -- " + event.getTarget(), Project.MSG_DEBUG ); + String time = formatTime( System.currentTimeMillis() - targetStartTime ); + log( event.getTarget() + ": duration " + time, Project.MSG_VERBOSE ); + out.flush(); + } + + public void targetStarted( BuildEvent event ) + { + log( ">> TARGET STARTED -- " + event.getTarget(), Project.MSG_DEBUG ); + log( StringUtils.LINE_SEP + event.getTarget().getName() + ":", Project.MSG_INFO ); + targetStartTime = System.currentTimeMillis(); + } + + public void taskFinished( BuildEvent event ) + { + log( "<<< TASK FINISHED -- " + event.getTask(), Project.MSG_DEBUG ); + out.flush(); + } + + public void taskStarted( BuildEvent event ) + { + log( ">>> TASK STARTED -- " + event.getTask(), Project.MSG_DEBUG ); + } + + /** + * The thing that actually sends the information to the output. + * + * @param mesg The message to log. + * @param level The verbosity level of the message. + */ + private void log( String mesg, int level ) + { + if( record && ( level <= loglevel ) ) + { + out.println( mesg ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rename.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rename.java new file mode 100644 index 000000000..7e1090049 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rename.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Renames a file. + * + * @author haas@softwired.ch + * @deprecated The rename task is deprecated. Use move instead. + */ +public class Rename extends Task +{ + private boolean replace = true; + private File dest; + + private File src; + + /** + * Sets the new name of the file. + * + * @param dest the new name of the file. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Sets wheter an existing file should be replaced. + * + * @param replace on, if an existing file should be replaced. + */ + public void setReplace( String replace ) + { + this.replace = project.toBoolean( replace ); + } + + + /** + * Sets the file to be renamed. + * + * @param src the file to rename + */ + public void setSrc( File src ) + { + this.src = src; + } + + + /** + * Renames the file src to dest + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + log( "DEPRECATED - The rename task is deprecated. Use move instead." ); + + if( dest == null ) + { + throw new BuildException( "dest attribute is required", location ); + } + + if( src == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( replace && dest.exists() ) + { + if( !dest.delete() ) + { + throw new BuildException( "Unable to remove existing file " + + dest ); + } + } + if( !src.renameTo( dest ) ) + { + throw new BuildException( "Unable to rename " + src + " to " + + dest ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Replace.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Replace.java new file mode 100644 index 000000000..626ac8216 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Replace.java @@ -0,0 +1,591 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.FileUtils; + +/** + * Replaces all occurrences of one or more string tokens with given values in + * the indicated files. Each value can be either a string or the value of a + * property available in a designated property file. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Erik Langenbach + */ +public class Replace extends MatchingTask +{ + + private File src = null; + private NestedString token = null; + private NestedString value = new NestedString(); + + private File propertyFile = null; + private Properties properties = null; + private Vector replacefilters = new Vector(); + + private File dir = null; + private boolean summary = false; + + /** + * The encoding used to read and write files - if null, uses default + */ + private String encoding = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + private int fileCount; + private int replaceCount; + + + /** + * Set the source files path when using matching tasks. + * + * @param dir The new Dir value + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Set the file encoding to use on the files read and written by replace + * + * @param encoding the encoding to use on the files + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + + /** + * Set the source file. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.src = file; + } + + /** + * Sets a file to be searched for property values. + * + * @param filename The new PropertyFile value + */ + public void setPropertyFile( File filename ) + { + propertyFile = filename; + } + + /** + * Request a summary + * + * @param summary true if you would like a summary logged of the replace + * operation + */ + public void setSummary( boolean summary ) + { + this.summary = summary; + } + + /** + * Set the string token to replace. + * + * @param token The new Token value + */ + public void setToken( String token ) + { + createReplaceToken().addText( token ); + } + + /** + * Set the string value to use as token replacement. + * + * @param value The new Value value + */ + public void setValue( String value ) + { + createReplaceValue().addText( value ); + } + + public Properties getProperties( File propertyFile ) + throws BuildException + { + Properties properties = new Properties(); + + try + { + properties.load( new FileInputStream( propertyFile ) ); + } + catch( FileNotFoundException e ) + { + String message = "Property file (" + propertyFile.getPath() + ") not found."; + throw new BuildException( message ); + } + catch( IOException e ) + { + String message = "Property file (" + propertyFile.getPath() + ") cannot be loaded."; + throw new BuildException( message ); + } + + return properties; + } + + /** + * Nested <replacetoken> element. + * + * @return Description of the Returned Value + */ + public NestedString createReplaceToken() + { + if( token == null ) + { + token = new NestedString(); + } + return token; + } + + /** + * Nested <replacevalue> element. + * + * @return Description of the Returned Value + */ + public NestedString createReplaceValue() + { + return value; + } + + /** + * Add nested <replacefilter> element. + * + * @return Description of the Returned Value + */ + public Replacefilter createReplacefilter() + { + Replacefilter filter = new Replacefilter(); + replacefilters.addElement( filter ); + return filter; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + validateAttributes(); + + if( propertyFile != null ) + { + properties = getProperties( propertyFile ); + } + + validateReplacefilters(); + fileCount = 0; + replaceCount = 0; + + if( src != null ) + { + processFile( src ); + } + + if( dir != null ) + { + DirectoryScanner ds = super.getDirectoryScanner( dir ); + String[] srcs = ds.getIncludedFiles(); + + for( int i = 0; i < srcs.length; i++ ) + { + File file = new File( dir, srcs[i] ); + processFile( file ); + } + } + + if( summary ) + { + log( "Replaced " + replaceCount + " occurrences in " + fileCount + " files.", Project.MSG_INFO ); + } + } + + /** + * Validate attributes provided for this task in .xml build file. + * + * @exception BuildException if any supplied attribute is invalid or any + * mandatory attribute is missing + */ + public void validateAttributes() + throws BuildException + { + if( src == null && dir == null ) + { + String message = "Either the file or the dir attribute " + "must be specified"; + throw new BuildException( message, location ); + } + if( propertyFile != null && !propertyFile.exists() ) + { + String message = "Property file " + propertyFile.getPath() + " does not exist."; + throw new BuildException( message, location ); + } + if( token == null && replacefilters.size() == 0 ) + { + String message = "Either token or a nested replacefilter " + + "must be specified"; + throw new BuildException( message, location ); + } + if( token != null && "".equals( token.getText() ) ) + { + String message = "The token attribute must not be an empty string."; + throw new BuildException( message, location ); + } + } + + /** + * Validate nested elements. + * + * @exception BuildException if any supplied attribute is invalid or any + * mandatory attribute is missing + */ + public void validateReplacefilters() + throws BuildException + { + for( int i = 0; i < replacefilters.size(); i++ ) + { + Replacefilter element = ( Replacefilter )replacefilters.elementAt( i ); + element.validate(); + } + } + + /** + * Perform the replacement on the given file. The replacement is performed + * on a temporary file which then replaces the original file. + * + * @param src the source file + * @exception BuildException Description of Exception + */ + private void processFile( File src ) + throws BuildException + { + if( !src.exists() ) + { + throw new BuildException( "Replace: source file " + src.getPath() + " doesn't exist", location ); + } + + File temp = fileUtils.createTempFile( "rep", ".tmp", + fileUtils.getParentFile( src ) ); + + Reader reader = null; + Writer writer = null; + try + { + reader = encoding == null ? new FileReader( src ) + : new InputStreamReader( new FileInputStream( src ), encoding ); + writer = encoding == null ? new FileWriter( temp ) + : new OutputStreamWriter( new FileOutputStream( temp ), encoding ); + + BufferedReader br = new BufferedReader( reader ); + BufferedWriter bw = new BufferedWriter( writer ); + + // read the entire file into a StringBuffer + // size of work buffer may be bigger than needed + // when multibyte characters exist in the source file + // but then again, it might be smaller than needed on + // platforms like Windows where length can't be trusted + int fileLengthInBytes = ( int )( src.length() ); + StringBuffer tmpBuf = new StringBuffer( fileLengthInBytes ); + int readChar = 0; + int totread = 0; + while( true ) + { + readChar = br.read(); + if( readChar < 0 ) + { + break; + } + tmpBuf.append( ( char )readChar ); + totread++; + } + + // create a String so we can use indexOf + String buf = tmpBuf.toString(); + + //Preserve original string (buf) so we can compare the result + String newString = new String( buf ); + + if( token != null ) + { + // line separators in values and tokens are "\n" + // in order to compare with the file contents, replace them + // as needed + String linesep = System.getProperty( "line.separator" ); + String val = stringReplace( value.getText(), "\n", linesep ); + String tok = stringReplace( token.getText(), "\n", linesep ); + + // for each found token, replace with value + log( "Replacing in " + src.getPath() + ": " + token.getText() + " --> " + value.getText(), Project.MSG_VERBOSE ); + newString = stringReplace( newString, tok, val ); + } + + if( replacefilters.size() > 0 ) + { + newString = processReplacefilters( newString, src.getPath() ); + } + + boolean changes = !newString.equals( buf ); + if( changes ) + { + bw.write( newString, 0, newString.length() ); + bw.flush(); + } + + // cleanup + bw.close(); + writer = null; + br.close(); + reader = null; + + // If there were changes, move the new one to the old one; + // otherwise, delete the new one + if( changes ) + { + ++fileCount; + src.delete(); + temp.renameTo( src ); + temp = null; + } + } + catch( IOException ioe ) + { + throw new BuildException( "IOException in " + src + " - " + + ioe.getClass().getName() + ":" + ioe.getMessage(), ioe, location ); + } + finally + { + if( reader != null ) + { + try + { + reader.close(); + } + catch( IOException e ) + {} + } + if( writer != null ) + { + try + { + writer.close(); + } + catch( IOException e ) + {} + } + if( temp != null ) + { + temp.delete(); + } + } + + } + + private String processReplacefilters( String buffer, String filename ) + { + String newString = new String( buffer ); + + for( int i = 0; i < replacefilters.size(); i++ ) + { + Replacefilter filter = ( Replacefilter )replacefilters.elementAt( i ); + + //for each found token, replace with value + log( "Replacing in " + filename + ": " + filter.getToken() + " --> " + filter.getReplaceValue(), Project.MSG_VERBOSE ); + newString = stringReplace( newString, filter.getToken(), filter.getReplaceValue() ); + } + + return newString; + } + + /** + * Replace occurrences of str1 in string str with str2 + * + * @param str Description of Parameter + * @param str1 Description of Parameter + * @param str2 Description of Parameter + * @return Description of the Returned Value + */ + private String stringReplace( String str, String str1, String str2 ) + { + StringBuffer ret = new StringBuffer(); + int start = 0; + int found = str.indexOf( str1 ); + while( found >= 0 ) + { + // write everything up to the found str1 + if( found > start ) + { + ret.append( str.substring( start, found ) ); + } + + // write the replacement str2 + if( str2 != null ) + { + ret.append( str2 ); + } + + // search again + start = found + str1.length(); + found = str.indexOf( str1, start ); + ++replaceCount; + } + + // write the remaining characters + if( str.length() > start ) + { + ret.append( str.substring( start, str.length() ) ); + } + + return ret.toString(); + } + + //Inner class + public class NestedString + { + + private StringBuffer buf = new StringBuffer(); + + public String getText() + { + return buf.toString(); + } + + public void addText( String val ) + { + buf.append( val ); + } + } + + //Inner class + public class Replacefilter + { + private String property; + private String token; + private String value; + + public void setProperty( String property ) + { + this.property = property; + } + + public void setToken( String token ) + { + this.token = token; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getProperty() + { + return property; + } + + public String getReplaceValue() + { + if( property != null ) + { + return ( String )properties.getProperty( property ); + } + else if( value != null ) + { + return value; + } + else if( Replace.this.value != null ) + { + return Replace.this.value.getText(); + } + else + { + //Default is empty string + return new String( "" ); + } + } + + public String getToken() + { + return token; + } + + public String getValue() + { + return value; + } + + public void validate() + throws BuildException + { + //Validate mandatory attributes + if( token == null ) + { + String message = "token is a mandatory attribute " + "of replacefilter."; + throw new BuildException( message ); + } + + if( "".equals( token ) ) + { + String message = "The token attribute must not be an empty string."; + throw new BuildException( message ); + } + + //value and property are mutually exclusive attributes + if( ( value != null ) && ( property != null ) ) + { + String message = "Either value or property " + "can be specified, but a replacefilter " + "element cannot have both."; + throw new BuildException( message ); + } + + if( ( property != null ) ) + { + //the property attribute must have access to a property file + if( propertyFile == null ) + { + String message = "The replacefilter's property attribute " + "can only be used with the replacetask's " + "propertyFile attribute."; + throw new BuildException( message ); + } + + //Make sure property exists in property file + if( properties == null || + properties.getProperty( property ) == null ) + { + String message = "property \"" + property + "\" was not found in " + propertyFile.getPath(); + throw new BuildException( message ); + } + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rmic.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rmic.java new file mode 100644 index 000000000..e37fac08e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rmic.java @@ -0,0 +1,688 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.rmi.Remote; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.rmic.RmicAdapter; +import org.apache.tools.ant.taskdefs.rmic.RmicAdapterFactory; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Task to compile RMI stubs and skeletons. This task can take the following + * arguments: + *

                + *
              • base: The base directory for the compiled stubs and skeletons + *
              • class: The name of the class to generate the stubs from + *
              • stubVersion: The version of the stub prototol to use (1.1, 1.2, + * compat) + *
              • sourceBase: The base directory for the generated stubs and skeletons + * + *
              • classpath: Additional classpath, appended before the system classpath + * + *
              • iiop: Generate IIOP compatable output + *
              • iiopopts: Include IIOP options + *
              • idl: Generate IDL output + *
              • idlopts: Include IDL options + *
              • includeantruntime + *
              • includejavaruntime + *
              • extdirs + *
              + * Of these arguments, base is required.

              + * + * If classname is specified then only that classname will be compiled. If it is + * absent, then base is traversed for classes according to patterns.

              + * + * + * + * @author duncan@x180.com + * @author ludovic.claude@websitewatchers.co.uk + * @author David Maclean david@cm.co.za + * @author Stefan Bodewig + * @author Takashi Okamoto tokamoto@rd.nttdata.co.jp + */ + +public class Rmic extends MatchingTask +{ + + private final static String FAIL_MSG + = "Rmic failed, messages should have been provided."; + private boolean verify = false; + private boolean filtering = false; + + private boolean iiop = false; + private boolean idl = false; + private boolean debug = false; + private boolean includeAntRuntime = true; + private boolean includeJavaRuntime = false; + + private Vector compileList = new Vector(); + + private ClassLoader loader = null; + + private File baseDir; + private String classname; + private Path compileClasspath; + private Path extdirs; + private String idlopts; + private String iiopopts; + private File sourceBase; + private String stubVersion; + + /** + * Sets the base directory to output generated class. + * + * @param base The new Base value + */ + public void setBase( File base ) + { + this.baseDir = base; + } + + /** + * Sets the class name to compile. + * + * @param classname The new Classname value + */ + public void setClassname( String classname ) + { + this.classname = classname; + } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Sets the debug flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Sets the extension directories that will be used during the compilation. + * + * @param extdirs The new Extdirs value + */ + public void setExtdirs( Path extdirs ) + { + if( this.extdirs == null ) + { + this.extdirs = extdirs; + } + else + { + this.extdirs.append( extdirs ); + } + } + + public void setFiltering( boolean filter ) + { + filtering = filter; + } + + /** + * Indicates that IDL output should be generated. This defaults to false if + * not set. + * + * @param idl The new Idl value + */ + public void setIdl( boolean idl ) + { + this.idl = idl; + } + + /** + * pass additional arguments for idl compile + * + * @param idlopts The new Idlopts value + */ + public void setIdlopts( String idlopts ) + { + this.idlopts = idlopts; + } + + /** + * Indicates that IIOP compatible stubs should be generated. This defaults + * to false if not set. + * + * @param iiop The new Iiop value + */ + public void setIiop( boolean iiop ) + { + this.iiop = iiop; + } + + /** + * pass additional arguments for iiop + * + * @param iiopopts The new Iiopopts value + */ + public void setIiopopts( String iiopopts ) + { + this.iiopopts = iiopopts; + } + + /** + * Include ant's own classpath in this task's classpath? + * + * @param include The new Includeantruntime value + */ + public void setIncludeantruntime( boolean include ) + { + includeAntRuntime = include; + } + + /** + * Sets whether or not to include the java runtime libraries to this task's + * classpath. + * + * @param include The new Includejavaruntime value + */ + public void setIncludejavaruntime( boolean include ) + { + includeJavaRuntime = include; + } + + /** + * Sets the source dirs to find the source java files. + * + * @param sourceBase The new SourceBase value + */ + public void setSourceBase( File sourceBase ) + { + this.sourceBase = sourceBase; + } + + /** + * Sets the stub version. + * + * @param stubVersion The new StubVersion value + */ + public void setStubVersion( String stubVersion ) + { + this.stubVersion = stubVersion; + } + + /** + * Indicates that the classes found by the directory match should be checked + * to see if they implement java.rmi.Remote. This defaults to false if not + * set. + * + * @param verify The new Verify value + */ + public void setVerify( boolean verify ) + { + this.verify = verify; + } + + /** + * Gets the base directory to output generated class. + * + * @return The Base value + */ + public File getBase() + { + return this.baseDir; + } + + /** + * Gets the class name to compile. + * + * @return The Classname value + */ + public String getClassname() + { + return classname; + } + + /** + * Gets the classpath. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return compileClasspath; + } + + public Vector getCompileList() + { + return compileList; + } + + /** + * Gets the debug flag. + * + * @return The Debug value + */ + public boolean getDebug() + { + return debug; + } + + /** + * Gets the extension directories that will be used during the compilation. + * + * @return The Extdirs value + */ + public Path getExtdirs() + { + return extdirs; + } + + /** + * Gets file list to compile. + * + * @return The FileList value + */ + public Vector getFileList() + { + return compileList; + } + + public boolean getFiltering() + { + return filtering; + } + + /* + * Gets IDL flags. + */ + public boolean getIdl() + { + return idl; + } + + /** + * Gets additional arguments for idl compile. + * + * @return The Idlopts value + */ + public String getIdlopts() + { + return idlopts; + } + + /** + * Gets iiop flags. + * + * @return The Iiop value + */ + public boolean getIiop() + { + return iiop; + } + + /** + * Gets additional arguments for iiop. + * + * @return The Iiopopts value + */ + public String getIiopopts() + { + return iiopopts; + } + + /** + * Gets whether or not the ant classpath is to be included in the task's + * classpath. + * + * @return The Includeantruntime value + */ + public boolean getIncludeantruntime() + { + return includeAntRuntime; + } + + /** + * Gets whether or not the java runtime should be included in this task's + * classpath. + * + * @return The Includejavaruntime value + */ + public boolean getIncludejavaruntime() + { + return includeJavaRuntime; + } + + /** + * Classloader for the user-specified classpath. + * + * @return The Loader value + */ + public ClassLoader getLoader() + { + return loader; + } + + /** + * Returns the topmost interface that extends Remote for a given class - if + * one exists. + * + * @param testClass Description of Parameter + * @return The RemoteInterface value + */ + public Class getRemoteInterface( Class testClass ) + { + if( Remote.class.isAssignableFrom( testClass ) ) + { + Class[] interfaces = testClass.getInterfaces(); + if( interfaces != null ) + { + for( int i = 0; i < interfaces.length; i++ ) + { + if( Remote.class.isAssignableFrom( interfaces[i] ) ) + { + return interfaces[i]; + } + } + } + } + return null; + } + + /** + * Gets the source dirs to find the source java files. + * + * @return The SourceBase value + */ + public File getSourceBase() + { + return sourceBase; + } + + public String getStubVersion() + { + return stubVersion; + } + + /** + * Get verify flag. + * + * @return The Verify value + */ + public boolean getVerify() + { + return verify; + } + + /** + * Load named class and test whether it can be rmic'ed + * + * @param classname Description of Parameter + * @return The ValidRmiRemote value + */ + public boolean isValidRmiRemote( String classname ) + { + try + { + Class testClass = loader.loadClass( classname ); + // One cannot RMIC an interface for "classic" RMI (JRMP) + if( testClass.isInterface() && !iiop && !idl ) + { + return false; + } + return isValidRmiRemote( testClass ); + } + catch( ClassNotFoundException e ) + { + log( "Unable to verify class " + classname + + ". It could not be found.", Project.MSG_WARN ); + } + catch( NoClassDefFoundError e ) + { + log( "Unable to verify class " + classname + + ". It is not defined.", Project.MSG_WARN ); + } + catch( Throwable t ) + { + log( "Unable to verify class " + classname + + ". Loading caused Exception: " + + t.getMessage(), Project.MSG_WARN ); + } + // we only get here if an exception has been thrown + return false; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath.createPath(); + } + + /** + * Maybe creates a nested extdirs element. + * + * @return Description of the Returned Value + */ + public Path createExtdirs() + { + if( extdirs == null ) + { + extdirs = new Path( project ); + } + return extdirs.createPath(); + } + + public void execute() + throws BuildException + { + if( baseDir == null ) + { + throw new BuildException( "base attribute must be set!", location ); + } + if( !baseDir.exists() ) + { + throw new BuildException( "base does not exist!", location ); + } + + if( verify ) + { + log( "Verify has been turned on.", Project.MSG_INFO ); + } + + String compiler = project.getProperty( "build.rmic" ); + RmicAdapter adapter = RmicAdapterFactory.getRmic( compiler, this ); + + // now we need to populate the compiler adapter + adapter.setRmic( this ); + + Path classpath = adapter.getClasspath(); + loader = new AntClassLoader( project, classpath ); + + // scan base dirs to build up compile lists only if a + // specific classname is not given + if( classname == null ) + { + DirectoryScanner ds = this.getDirectoryScanner( baseDir ); + String[] files = ds.getIncludedFiles(); + scanDir( baseDir, files, adapter.getMapper() ); + } + else + { + // otherwise perform a timestamp comparison - at least + scanDir( baseDir, + new String[]{classname.replace( '.', File.separatorChar ) + ".class"}, + adapter.getMapper() ); + } + + int fileCount = compileList.size(); + if( fileCount > 0 ) + { + log( "RMI Compiling " + fileCount + + " class" + ( fileCount > 1 ? "es" : "" ) + " to " + baseDir, + Project.MSG_INFO ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + throw new BuildException( FAIL_MSG, location ); + } + } + + /* + * Move the generated source file to the base directory. If + * base directory and sourcebase are the same, the generated + * sources are already in place. + */ + if( null != sourceBase && !baseDir.equals( sourceBase ) ) + { + if( idl ) + { + log( "Cannot determine sourcefiles in idl mode, ", + Project.MSG_WARN ); + log( "sourcebase attribute will be ignored.", Project.MSG_WARN ); + } + else + { + for( int j = 0; j < fileCount; j++ ) + { + moveGeneratedFile( baseDir, sourceBase, + ( String )compileList.elementAt( j ), + adapter ); + } + } + } + compileList.removeAllElements(); + } + + /** + * Scans the directory looking for class files to be compiled. The result is + * returned in the class variable compileList. + * + * @param baseDir Description of Parameter + * @param files Description of Parameter + * @param mapper Description of Parameter + */ + protected void scanDir( File baseDir, String files[], + FileNameMapper mapper ) + { + + String[] newFiles = files; + if( idl ) + { + log( "will leave uptodate test to rmic implementation in idl mode.", + Project.MSG_VERBOSE ); + } + else if( iiop + && iiopopts != null && iiopopts.indexOf( "-always" ) > -1 ) + { + log( "no uptodate test as -always option has been specified", + Project.MSG_VERBOSE ); + } + else + { + SourceFileScanner sfs = new SourceFileScanner( this ); + newFiles = sfs.restrict( files, baseDir, baseDir, mapper ); + } + + for( int i = 0; i < newFiles.length; i++ ) + { + String classname = newFiles[i].replace( File.separatorChar, '.' ); + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + compileList.addElement( classname ); + } + } + + /** + * Check to see if the class or (super)interfaces implement java.rmi.Remote. + * + * @param testClass Description of Parameter + * @return The ValidRmiRemote value + */ + private boolean isValidRmiRemote( Class testClass ) + { + return getRemoteInterface( testClass ) != null; + } + + /** + * Move the generated source file(s) to the base directory + * + * @param baseDir Description of Parameter + * @param sourceBaseFile Description of Parameter + * @param classname Description of Parameter + * @param adapter Description of Parameter + * @exception BuildException Description of Exception + */ + private void moveGeneratedFile( File baseDir, File sourceBaseFile, + String classname, + RmicAdapter adapter ) + throws BuildException + { + + String classFileName = + classname.replace( '.', File.separatorChar ) + ".class"; + String[] generatedFiles = + adapter.getMapper().mapFileName( classFileName ); + + for( int i = 0; i < generatedFiles.length; i++ ) + { + String sourceFileName = + classFileName.substring( 0, classFileName.length() - 6 ) + ".java"; + File oldFile = new File( baseDir, sourceFileName ); + File newFile = new File( sourceBaseFile, sourceFileName ); + try + { + project.copyFile( oldFile, newFile, filtering ); + oldFile.delete(); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + oldFile + " to " + + newFile + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SQLExec.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SQLExec.java new file mode 100644 index 000000000..27acf92a0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SQLExec.java @@ -0,0 +1,867 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Reader; +import java.io.StringReader; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.Enumeration; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Reads in a text file containing SQL statements seperated with semicolons and + * executes it in a given db. Comments may be created with REM -- or //. + * + * @author Jeff Martin + * @author Michael McCallum + * @author Tim Stephenson + */ +public class SQLExec extends Task +{ + + private int goodSql = 0, totalSql = 0; + + private Vector filesets = new Vector(); + + /** + * Database connection + */ + private Connection conn = null; + + /** + * Autocommit flag. Default value is false + */ + private boolean autocommit = false; + + /** + * SQL statement + */ + private Statement statement = null; + + /** + * DB driver. + */ + private String driver = null; + + /** + * DB url. + */ + private String url = null; + + /** + * User name. + */ + private String userId = null; + + /** + * Password + */ + private String password = null; + + /** + * SQL input file + */ + private File srcFile = null; + + /** + * SQL input command + */ + private String sqlCommand = ""; + + /** + * SQL transactions to perform + */ + private Vector transactions = new Vector(); + + /** + * SQL Statement delimiter + */ + private String delimiter = ";"; + + /** + * The delimiter type indicating whether the delimiter will only be + * recognized on a line by itself + */ + private String delimiterType = DelimiterType.NORMAL; + + /** + * Print SQL results. + */ + private boolean print = false; + + /** + * Print header columns. + */ + private boolean showheaders = true; + + /** + * Results Output file. + */ + private File output = null; + + /** + * RDBMS Product needed for this SQL. + */ + private String rdbms = null; + + /** + * RDBMS Version needed for this SQL. + */ + private String version = null; + + /** + * Action to perform if an error is found + */ + private String onError = "abort"; + + /** + * Encoding to use when reading SQL statements from a file + */ + private String encoding = null; + + private Path classpath; + + private AntClassLoader loader; + + /** + * Set the autocommit flag for the DB connection. + * + * @param autocommit The new Autocommit value + */ + public void setAutocommit( boolean autocommit ) + { + this.autocommit = autocommit; + } + + /** + * Set the classpath for loading the driver. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * Set the classpath for loading the driver using the classpath reference. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the statement delimiter.

              + * + * For example, set this to "go" and delimitertype to "ROW" for Sybase ASE + * or MS SQL Server.

              + * + * @param delimiter The new Delimiter value + */ + public void setDelimiter( String delimiter ) + { + this.delimiter = delimiter; + } + + /** + * Set the Delimiter type for this sql task. The delimiter type takes two + * values - normal and row. Normal means that any occurence of the delimiter + * terminate the SQL command whereas with row, only a line containing just + * the delimiter is recognized as the end of the command. + * + * @param delimiterType The new DelimiterType value + */ + public void setDelimiterType( DelimiterType delimiterType ) + { + this.delimiterType = delimiterType.getValue(); + } + + /** + * Set the JDBC driver to be used. + * + * @param driver The new Driver value + */ + public void setDriver( String driver ) + { + this.driver = driver; + } + + /** + * Set the file encoding to use on the sql files read in + * + * @param encoding the encoding to use on the files + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Set the action to perform onerror + * + * @param action The new Onerror value + */ + public void setOnerror( OnError action ) + { + this.onError = action.getValue(); + } + + /** + * Set the output file. + * + * @param output The new Output value + */ + public void setOutput( File output ) + { + this.output = output; + } + + + /** + * Set the password for the DB connection. + * + * @param password The new Password value + */ + public void setPassword( String password ) + { + this.password = password; + } + + /** + * Set the print flag. + * + * @param print The new Print value + */ + public void setPrint( boolean print ) + { + this.print = print; + } + + /** + * Set the rdbms required + * + * @param vendor The new Rdbms value + */ + public void setRdbms( String vendor ) + { + this.rdbms = vendor.toLowerCase(); + } + + /** + * Set the showheaders flag. + * + * @param showheaders The new Showheaders value + */ + public void setShowheaders( boolean showheaders ) + { + this.showheaders = showheaders; + } + + /** + * Set the name of the sql file to be run. + * + * @param srcFile The new Src value + */ + public void setSrc( File srcFile ) + { + this.srcFile = srcFile; + } + + /** + * Set the DB connection url. + * + * @param url The new Url value + */ + public void setUrl( String url ) + { + this.url = url; + } + + /** + * Set the user name for the DB connection. + * + * @param userId The new Userid value + */ + public void setUserid( String userId ) + { + this.userId = userId; + } + + /** + * Set the version required + * + * @param version The new Version value + */ + public void setVersion( String version ) + { + this.version = version.toLowerCase(); + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Set the sql command to execute + * + * @param sql The feature to be added to the Text attribute + */ + public void addText( String sql ) + { + this.sqlCommand += sql; + } + + /** + * Create the classpath for loading the driver. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + + /** + * Set the sql command to execute + * + * @return Description of the Returned Value + */ + public Transaction createTransaction() + { + Transaction t = new Transaction(); + transactions.addElement( t ); + return t; + } + + /** + * Load the sql file and then execute it + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + sqlCommand = sqlCommand.trim(); + + if( srcFile == null && sqlCommand.length() == 0 && filesets.isEmpty() ) + { + if( transactions.size() == 0 ) + { + throw new BuildException( "Source file or fileset, transactions or sql statement must be set!", location ); + } + } + else + { + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File srcDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + + // Make a transaction for each file + for( int j = 0; j < srcFiles.length; j++ ) + { + Transaction t = createTransaction(); + t.setSrc( new File( srcDir, srcFiles[j] ) ); + } + } + + // Make a transaction group for the outer command + Transaction t = createTransaction(); + t.setSrc( srcFile ); + t.addText( sqlCommand ); + } + + if( driver == null ) + { + throw new BuildException( "Driver attribute must be set!", location ); + } + if( userId == null ) + { + throw new BuildException( "User Id attribute must be set!", location ); + } + if( password == null ) + { + throw new BuildException( "Password attribute must be set!", location ); + } + if( url == null ) + { + throw new BuildException( "Url attribute must be set!", location ); + } + if( srcFile != null && !srcFile.exists() ) + { + throw new BuildException( "Source file does not exist!", location ); + } + Driver driverInstance = null; + // Load the driver using the + try + { + Class dc; + if( classpath != null ) + { + log( "Loading " + driver + " using AntClassLoader with classpath " + classpath, + Project.MSG_VERBOSE ); + + loader = new AntClassLoader( project, classpath ); + dc = loader.loadClass( driver ); + } + else + { + log( "Loading " + driver + " using system loader.", Project.MSG_VERBOSE ); + dc = Class.forName( driver ); + } + driverInstance = ( Driver )dc.newInstance(); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( "Class Not Found: JDBC driver " + driver + " could not be loaded", location ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( "Illegal Access: JDBC driver " + driver + " could not be loaded", location ); + } + catch( InstantiationException e ) + { + throw new BuildException( "Instantiation Exception: JDBC driver " + driver + " could not be loaded", location ); + } + + try + { + log( "connecting to " + url, Project.MSG_VERBOSE ); + Properties info = new Properties(); + info.put( "user", userId ); + info.put( "password", password ); + conn = driverInstance.connect( url, info ); + + if( conn == null ) + { + // Driver doesn't understand the URL + throw new SQLException( "No suitable Driver for " + url ); + } + + if( !isValidRdbms( conn ) ) + return; + + conn.setAutoCommit( autocommit ); + + statement = conn.createStatement(); + + PrintStream out = System.out; + try + { + if( output != null ) + { + log( "Opening PrintStream to output file " + output, Project.MSG_VERBOSE ); + out = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + + // Process all transactions + for( Enumeration e = transactions.elements(); + e.hasMoreElements(); ) + { + + ( ( Transaction )e.nextElement() ).runTransaction( out ); + if( !autocommit ) + { + log( "Commiting transaction", Project.MSG_VERBOSE ); + conn.commit(); + } + } + } + finally + { + if( out != null && out != System.out ) + { + out.close(); + } + } + } + catch( IOException e ) + { + if( !autocommit && conn != null && onError.equals( "abort" ) ) + { + try + { + conn.rollback(); + } + catch( SQLException ex ) + {} + } + throw new BuildException( e ); + } + catch( SQLException e ) + { + if( !autocommit && conn != null && onError.equals( "abort" ) ) + { + try + { + conn.rollback(); + } + catch( SQLException ex ) + {} + } + throw new BuildException( e ); + } + finally + { + try + { + if( statement != null ) + { + statement.close(); + } + if( conn != null ) + { + conn.close(); + } + } + catch( SQLException e ) + {} + } + + log( goodSql + " of " + totalSql + + " SQL statements executed successfully" ); + } + + /** + * Verify if connected to the correct RDBMS + * + * @param conn Description of Parameter + * @return The ValidRdbms value + */ + protected boolean isValidRdbms( Connection conn ) + { + if( rdbms == null && version == null ) + return true; + + try + { + DatabaseMetaData dmd = conn.getMetaData(); + + if( rdbms != null ) + { + String theVendor = dmd.getDatabaseProductName().toLowerCase(); + + log( "RDBMS = " + theVendor, Project.MSG_VERBOSE ); + if( theVendor == null || theVendor.indexOf( rdbms ) < 0 ) + { + log( "Not the required RDBMS: " + rdbms, Project.MSG_VERBOSE ); + return false; + } + } + + if( version != null ) + { + String theVersion = dmd.getDatabaseProductVersion().toLowerCase(); + + log( "Version = " + theVersion, Project.MSG_VERBOSE ); + if( theVersion == null || + !( theVersion.startsWith( version ) || + theVersion.indexOf( " " + version ) >= 0 ) ) + { + log( "Not the required version: \"" + version + "\"", Project.MSG_VERBOSE ); + return false; + } + } + } + catch( SQLException e ) + { + // Could not get the required information + log( "Failed to obtain required RDBMS information", Project.MSG_ERR ); + return false; + } + + return true; + } + + /** + * Exec the sql statement. + * + * @param sql Description of Parameter + * @param out Description of Parameter + * @exception SQLException Description of Exception + */ + protected void execSQL( String sql, PrintStream out ) + throws SQLException + { + // Check and ignore empty statements + if( "".equals( sql.trim() ) ) + return; + + try + { + totalSql++; + if( !statement.execute( sql ) ) + { + log( statement.getUpdateCount() + " rows affected", + Project.MSG_VERBOSE ); + } + else + { + if( print ) + { + printResults( out ); + } + } + + SQLWarning warning = conn.getWarnings(); + while( warning != null ) + { + log( warning + " sql warning", Project.MSG_VERBOSE ); + warning = warning.getNextWarning(); + } + conn.clearWarnings(); + goodSql++; + } + catch( SQLException e ) + { + log( "Failed to execute: " + sql, Project.MSG_ERR ); + if( !onError.equals( "continue" ) ) + throw e; + log( e.toString(), Project.MSG_ERR ); + } + } + + /** + * print any results in the statement. + * + * @param out Description of Parameter + * @exception java.sql.SQLException Description of Exception + */ + protected void printResults( PrintStream out ) + throws java.sql.SQLException + { + ResultSet rs = null; + do + { + rs = statement.getResultSet(); + if( rs != null ) + { + log( "Processing new result set.", Project.MSG_VERBOSE ); + ResultSetMetaData md = rs.getMetaData(); + int columnCount = md.getColumnCount(); + StringBuffer line = new StringBuffer(); + if( showheaders ) + { + for( int col = 1; col < columnCount; col++ ) + { + line.append( md.getColumnName( col ) ); + line.append( "," ); + } + line.append( md.getColumnName( columnCount ) ); + out.println( line ); + line.setLength( 0 ); + } + while( rs.next() ) + { + boolean first = true; + for( int col = 1; col <= columnCount; col++ ) + { + String columnValue = rs.getString( col ); + if( columnValue != null ) + { + columnValue = columnValue.trim(); + } + + if( first ) + { + first = false; + } + else + { + line.append( "," ); + } + line.append( columnValue ); + } + out.println( line ); + line.setLength( 0 ); + } + } + }while ( statement.getMoreResults() ); + out.println(); + } + + protected void runStatements( Reader reader, PrintStream out ) + throws SQLException, IOException + { + String sql = ""; + String line = ""; + + BufferedReader in = new BufferedReader( reader ); + + try + { + while( ( line = in.readLine() ) != null ) + { + line = line.trim(); + line = project.replaceProperties( line ); + if( line.startsWith( "//" ) ) + continue; + if( line.startsWith( "--" ) ) + continue; + StringTokenizer st = new StringTokenizer( line ); + if( st.hasMoreTokens() ) + { + String token = st.nextToken(); + if( "REM".equalsIgnoreCase( token ) ) + { + continue; + } + } + + sql += " " + line; + sql = sql.trim(); + + // SQL defines "--" as a comment to EOL + // and in Oracle it may contain a hint + // so we cannot just remove it, instead we must end it + if( line.indexOf( "--" ) >= 0 ) + sql += "\n"; + + if( delimiterType.equals( DelimiterType.NORMAL ) && sql.endsWith( delimiter ) || + delimiterType.equals( DelimiterType.ROW ) && line.equals( delimiter ) ) + { + log( "SQL: " + sql, Project.MSG_VERBOSE ); + execSQL( sql.substring( 0, sql.length() - delimiter.length() ), out ); + sql = ""; + } + } + + // Catch any statements not followed by ; + if( !sql.equals( "" ) ) + { + execSQL( sql, out ); + } + } + catch( SQLException e ) + { + throw e; + } + + } + + public static class DelimiterType extends EnumeratedAttribute + { + public final static String NORMAL = "normal"; + public final static String ROW = "row"; + + public String[] getValues() + { + return new String[]{NORMAL, ROW}; + } + } + + /** + * Enumerated attribute with the values "continue", "stop" and "abort" for + * the onerror attribute. + * + * @author RT + */ + public static class OnError extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"continue", "stop", "abort"}; + } + } + + /** + * Contains the definition of a new transaction element. Transactions allow + * several files or blocks of statements to be executed using the same JDBC + * connection and commit operation in between. + * + * @author RT + */ + public class Transaction + { + private File tSrcFile = null; + private String tSqlCommand = ""; + + public void setSrc( File src ) + { + this.tSrcFile = src; + } + + public void addText( String sql ) + { + this.tSqlCommand += sql; + } + + private void runTransaction( PrintStream out ) + throws IOException, SQLException + { + if( tSqlCommand.length() != 0 ) + { + log( "Executing commands", Project.MSG_INFO ); + runStatements( new StringReader( tSqlCommand ), out ); + } + + if( tSrcFile != null ) + { + log( "Executing file: " + tSrcFile.getAbsolutePath(), + Project.MSG_INFO ); + Reader reader = ( encoding == null ) ? new FileReader( tSrcFile ) + : new InputStreamReader( new FileInputStream( tSrcFile ), encoding ); + runStatements( reader, out ); + reader.close(); + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SendEmail.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SendEmail.java new file mode 100644 index 000000000..052ed4940 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SendEmail.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.mail.MailMessage; + +/** + * A task to send SMTP email.

              + * + * + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * from + * + * + * + * Email address of sender. + * + * + * + * Yes + * + * + * + * + * + * + * + * mailhost + * + * + * + * Host name of the mail server. + * + * + * + * No, default to "localhost" + * + * + * + * + * + * + * + * toList + * + * + * + * Comma-separated list of recipients. + * + * + * + * Yes + * + * + * + * + * + * + * + * subject + * + * + * + * Email subject line. + * + * + * + * No + * + * + * + * + * + * + * + * files + * + * + * + * Filename(s) of text to send in the body of the email. Multiple files + * are comma-separated. + * + * + * + * One of these two attributes + * + * + * + * + * + * + * + * message + * + * + * + * Message to send inthe body of the email. + * + * + * + * + * + * + * + * + * + * includefilenames + * + * + * + * Includes filenames before file contents when set to true. + * + * + * + * No, default is false + * + * + * + *

              + * + * + * + * @author glenn_twiggs@bmc.com + * @author Magesh Umasankar + */ +public class SendEmail extends Task +{ + private String mailhost = "localhost"; + private int mailport = MailMessage.DEFAULT_PORT; + private Vector files = new Vector(); + /** + * failure flag + */ + private boolean failOnError = true; + private String from; + private boolean includefilenames; + private String message; + private String subject; + private String toList; + + + /** + * Creates new SendEmail + */ + public SendEmail() { } + + /** + * Sets the FailOnError attribute of the MimeMail object + * + * @param failOnError The new FailOnError value + * @since 1.5 + */ + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + /** + * Sets the file parameter of this build task. + * + * @param filenames Filenames to include as the message body of this email. + */ + public void setFiles( String filenames ) + { + StringTokenizer t = new StringTokenizer( filenames, ", " ); + + while( t.hasMoreTokens() ) + { + files.addElement( project.resolveFile( t.nextToken() ) ); + } + } + + /** + * Sets the from parameter of this build task. + * + * @param from Email address of sender. + */ + public void setFrom( String from ) + { + this.from = from; + } + + /** + * Sets Includefilenames attribute + * + * @param includefilenames Set to true if file names are to be included. + * @since 1.5 + */ + public void setIncludefilenames( boolean includefilenames ) + { + this.includefilenames = includefilenames; + } + + /** + * Sets the mailhost parameter of this build task. + * + * @param mailhost Mail host name. + */ + public void setMailhost( String mailhost ) + { + this.mailhost = mailhost; + } + + /** + * Sets the mailport parameter of this build task. + * + * @param value mail port name. + */ + public void setMailport( Integer value ) + { + this.mailport = value.intValue(); + } + + /** + * Sets the message parameter of this build task. + * + * @param message Message body of this email. + */ + public void setMessage( String message ) + { + this.message = message; + } + + /** + * Sets the subject parameter of this build task. + * + * @param subject Subject of this email. + */ + public void setSubject( String subject ) + { + this.subject = subject; + } + + /** + * Sets the toList parameter of this build task. + * + * @param toList Comma-separated list of email recipient addreses. + */ + public void setToList( String toList ) + { + this.toList = toList; + } + + /** + * Executes this build task. + * + * @throws BuildException if there is an error during task execution. + */ + public void execute() + throws BuildException + { + try + { + MailMessage mailMessage = new MailMessage( mailhost ); + mailMessage.setPort( mailport ); + + if( from != null ) + { + mailMessage.from( from ); + } + else + { + throw new BuildException( "Attribute \"from\" is required." ); + } + + if( toList != null ) + { + StringTokenizer t = new StringTokenizer( toList, ", ", false ); + + while( t.hasMoreTokens() ) + { + mailMessage.to( t.nextToken() ); + } + } + else + { + throw new BuildException( "Attribute \"toList\" is required." ); + } + + if( subject != null ) + { + mailMessage.setSubject( subject ); + } + + if( !files.isEmpty() ) + { + PrintStream out = mailMessage.getPrintStream(); + + for( Enumeration e = files.elements(); e.hasMoreElements(); ) + { + File file = ( File )e.nextElement(); + + if( file.exists() && file.canRead() ) + { + int bufsize = 1024; + int length; + byte[] buf = new byte[bufsize]; + if( includefilenames ) + { + String filename = file.getName(); + int filenamelength = filename.length(); + out.println( filename ); + for( int star = 0; star < filenamelength; star++ ) + { + out.print( '=' ); + } + out.println(); + } + BufferedInputStream in = null; + try + { + in = new BufferedInputStream( + new FileInputStream( file ), bufsize ); + while( ( length = in.read( buf, 0, bufsize ) ) != -1 ) + { + out.write( buf, 0, length ); + } + if( includefilenames ) + { + out.println(); + } + } + finally + { + if( in != null ) + { + try + { + in.close(); + } + catch( IOException ioe ) + {} + } + } + + } + else + { + throw new BuildException( "File \"" + file.getName() + + "\" does not exist or is not readable." ); + } + } + } + else if( message != null ) + { + PrintStream out = mailMessage.getPrintStream(); + out.print( message ); + } + else + { + throw new BuildException( "Attribute \"file\" or \"message\" is required." ); + } + + log( "Sending email" ); + mailMessage.sendAndClose(); + } + catch( IOException ioe ) + { + String err = "IO error sending mail " + ioe.toString(); + if( failOnError ) + { + throw new BuildException( err, ioe, location ); + } + else + { + log( err, Project.MSG_ERR ); + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sequential.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sequential.java new file mode 100644 index 000000000..59fa575be --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sequential.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; + + +/** + * Implements a single threaded task execution.

              + * + * + * + * @author Thomas Christen chr@active.ch + */ +public class Sequential extends Task + implements TaskContainer +{ + + /** + * Optional Vector holding the nested tasks + */ + private Vector nestedTasks = new Vector(); + + /** + * Add a nested task to Sequential.

              + * + * + * + * @param nestedTask Nested task to execute Sequential

              + * + * + */ + public void addTask( Task nestedTask ) + { + nestedTasks.addElement( nestedTask ); + } + + /** + * Execute all nestedTasks. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + for( Enumeration e = nestedTasks.elements(); e.hasMoreElements(); ) + { + Task nestedTask = ( Task )e.nextElement(); + nestedTask.perform(); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SignJar.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SignJar.java new file mode 100644 index 000000000..29ffddd1a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SignJar.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; + +/** + * Sign a archive. + * + * @author Peter Donald donaldp@apache.org + * + * @author Nick Fortescue + * nick@ox.compsoc.net + */ +public class SignJar extends Task +{ + + /** + * the filesets of the jars to sign + */ + protected Vector filesets = new Vector(); + + /** + * The alias of signer. + */ + protected String alias; + protected boolean internalsf; + + /** + * The name of the jar file. + */ + protected File jar; + protected String keypass; + + /** + * The name of keystore file. + */ + protected File keystore; + /** + * Whether to assume a jar which has an appropriate .SF file in is already + * signed. + */ + protected boolean lazy; + protected boolean sectionsonly; + protected File sigfile; + protected File signedjar; + + protected String storepass; + protected String storetype; + protected boolean verbose; + + public void setAlias( final String alias ) + { + this.alias = alias; + } + + public void setInternalsf( final boolean internalsf ) + { + this.internalsf = internalsf; + } + + public void setJar( final File jar ) + { + this.jar = jar; + } + + public void setKeypass( final String keypass ) + { + this.keypass = keypass; + } + + public void setKeystore( final File keystore ) + { + this.keystore = keystore; + } + + public void setLazy( final boolean lazy ) + { + this.lazy = lazy; + } + + public void setSectionsonly( final boolean sectionsonly ) + { + this.sectionsonly = sectionsonly; + } + + public void setSigfile( final File sigfile ) + { + this.sigfile = sigfile; + } + + public void setSignedjar( final File signedjar ) + { + this.signedjar = signedjar; + } + + public void setStorepass( final String storepass ) + { + this.storepass = storepass; + } + + public void setStoretype( final String storetype ) + { + this.storetype = storetype; + } + + public void setVerbose( final boolean verbose ) + { + this.verbose = verbose; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( final FileSet set ) + { + filesets.addElement( set ); + } + + + public void execute() + throws BuildException + { + if( null == jar && null == filesets ) + { + throw new BuildException( "jar must be set through jar attribute or nested filesets" ); + } + if( null != jar ) + { + doOneJar( jar, signedjar ); + return; + } + else + { + //Assume null != filesets + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] jarFiles = ds.getIncludedFiles(); + for( int j = 0; j < jarFiles.length; j++ ) + { + doOneJar( new File( fs.getDir( project ), jarFiles[j] ), null ); + } + } + } + } + + protected boolean isSigned( File file ) + { + final String SIG_START = "META-INF/"; + final String SIG_END = ".SF"; + + if( !file.exists() ) + { + return false; + } + ZipFile jarFile = null; + try + { + jarFile = new ZipFile( file ); + if( null == alias ) + { + Enumeration entries = jarFile.entries(); + while( entries.hasMoreElements() ) + { + String name = ( ( ZipEntry )entries.nextElement() ).getName(); + if( name.startsWith( SIG_START ) && name.endsWith( SIG_END ) ) + { + return true; + } + } + return false; + } + else + { + return jarFile.getEntry( SIG_START + alias.toUpperCase() + + SIG_END ) != null; + } + } + catch( IOException e ) + { + return false; + } + finally + { + if( jarFile != null ) + { + try + { + jarFile.close(); + } + catch( IOException e ) + {} + } + } + } + + protected boolean isUpToDate( File jarFile, File signedjarFile ) + { + if( null == jarFile ) + { + return false; + } + + if( null != signedjarFile ) + { + + if( !jarFile.exists() ) + return false; + if( !signedjarFile.exists() ) + return false; + if( jarFile.equals( signedjarFile ) ) + return false; + if( signedjarFile.lastModified() > jarFile.lastModified() ) + return true; + } + else + { + if( lazy ) + { + return isSigned( jarFile ); + } + } + + return false; + } + + private void doOneJar( File jarSource, File jarTarget ) + throws BuildException + { + if( project.getJavaVersion().equals( Project.JAVA_1_1 ) ) + { + throw new BuildException( "The signjar task is only available on JDK versions 1.2 or greater" ); + } + + if( null == alias ) + { + throw new BuildException( "alias attribute must be set" ); + } + + if( null == storepass ) + { + throw new BuildException( "storepass attribute must be set" ); + } + + if( isUpToDate( jarSource, jarTarget ) ) + return; + + final StringBuffer sb = new StringBuffer(); + + final ExecTask cmd = ( ExecTask )project.createTask( "exec" ); + cmd.setExecutable( "jarsigner" ); + + if( null != keystore ) + { + cmd.createArg().setValue( "-keystore" ); + cmd.createArg().setValue( keystore.toString() ); + } + + if( null != storepass ) + { + cmd.createArg().setValue( "-storepass" ); + cmd.createArg().setValue( storepass ); + } + + if( null != storetype ) + { + cmd.createArg().setValue( "-storetype" ); + cmd.createArg().setValue( storetype ); + } + + if( null != keypass ) + { + cmd.createArg().setValue( "-keypass" ); + cmd.createArg().setValue( keypass ); + } + + if( null != sigfile ) + { + cmd.createArg().setValue( "-sigfile" ); + cmd.createArg().setValue( sigfile.toString() ); + } + + if( null != jarTarget ) + { + cmd.createArg().setValue( "-signedjar" ); + cmd.createArg().setValue( jarTarget.toString() ); + } + + if( verbose ) + { + cmd.createArg().setValue( "-verbose" ); + } + + if( internalsf ) + { + cmd.createArg().setValue( "-internalsf" ); + } + + if( sectionsonly ) + { + cmd.createArg().setValue( "-sectionsonly" ); + } + + cmd.createArg().setValue( jarSource.toString() ); + + cmd.createArg().setValue( alias ); + + log( "Signing Jar : " + jarSource.getAbsolutePath() ); + cmd.setFailonerror( true ); + cmd.setTaskName( getTaskName() ); + cmd.execute(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sleep.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sleep.java new file mode 100644 index 000000000..2c8d81c86 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sleep.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * A task to sleep for a period of time + * + * @author steve_l@iseran.com steve loughran + * @created 01 May 2001 + */ + +public class Sleep extends Task +{ + /** + * failure flag + */ + private boolean failOnError = true; + + /** + * Description of the Field + */ + private int seconds = 0; + /** + * Description of the Field + */ + private int hours = 0; + /** + * Description of the Field + */ + private int minutes = 0; + /** + * Description of the Field + */ + private int milliseconds = 0; + + + /** + * Creates new instance + */ + public Sleep() { } + + + /** + * Sets the FailOnError attribute of the MimeMail object + * + * @param failOnError The new FailOnError value + */ + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + + /** + * Sets the Hours attribute of the Sleep object + * + * @param hours The new Hours value + */ + public void setHours( int hours ) + { + this.hours = hours; + } + + + /** + * Sets the Milliseconds attribute of the Sleep object + * + * @param milliseconds The new Milliseconds value + */ + public void setMilliseconds( int milliseconds ) + { + this.milliseconds = milliseconds; + } + + + /** + * Sets the Minutes attribute of the Sleep object + * + * @param minutes The new Minutes value + */ + public void setMinutes( int minutes ) + { + this.minutes = minutes; + } + + + /** + * Sets the Seconds attribute of the Sleep object + * + * @param seconds The new Seconds value + */ + public void setSeconds( int seconds ) + { + this.seconds = seconds; + } + + + /** + * sleep for a period of time + * + * @param millis time to sleep + */ + public void doSleep( long millis ) + { + try + { + Thread.currentThread().sleep( millis ); + } + catch( InterruptedException ie ) + { + } + } + + + /** + * Executes this build task. throws org.apache.tools.ant.BuildException if + * there is an error during task execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + validate(); + long sleepTime = getSleepTime(); + log( "sleeping for " + sleepTime + " milliseconds", + Project.MSG_VERBOSE ); + doSleep( sleepTime ); + } + catch( Exception e ) + { + if( failOnError ) + { + throw new BuildException( e ); + } + else + { + String text = e.toString(); + log( text, Project.MSG_ERR ); + } + } + } + + + /** + * verify parameters + * + * @throws BuildException if something is invalid + */ + public void validate() + throws BuildException + { + long sleepTime = getSleepTime(); + if( getSleepTime() < 0 ) + { + throw new BuildException( "Negative sleep periods are not supported" ); + } + } + + + /** + * return time to sleep + * + * @return sleep time. if below 0 then there is an error + */ + + private long getSleepTime() + { + return ( ( ( ( long )hours * 60 ) + minutes ) * 60 + seconds ) * 1000 + milliseconds; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/StreamPumper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/StreamPumper.java new file mode 100644 index 000000000..8e8a8e9ef --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/StreamPumper.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copies all data from an input stream to an output stream. + * + * @author thomas.haas@softwired-inc.com + */ +public class StreamPumper implements Runnable +{ + + // TODO: make SIZE and SLEEP instance variables. + // TODO: add a status flag to note if an error occured in run. + + private final static int SLEEP = 5; + private final static int SIZE = 128; + private InputStream is; + private OutputStream os; + + + /** + * Create a new stream pumper. + * + * @param is input stream to read data from + * @param os output stream to write data to. + */ + public StreamPumper( InputStream is, OutputStream os ) + { + this.is = is; + this.os = os; + } + + + /** + * Copies data from the input stream to the output stream. Terminates as + * soon as the input stream is closed or an error occurs. + */ + public void run() + { + final byte[] buf = new byte[SIZE]; + + int length; + try + { + while( ( length = is.read( buf ) ) > 0 ) + { + os.write( buf, 0, length ); + try + { + Thread.sleep( SLEEP ); + } + catch( InterruptedException e ) + {} + } + } + catch( IOException e ) + {} + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tar.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tar.java new file mode 100644 index 000000000..2f3fe3cdc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tar.java @@ -0,0 +1,481 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; +import org.apache.tools.tar.TarConstants; +import org.apache.tools.tar.TarEntry; +import org.apache.tools.tar.TarOutputStream; + +/** + * Creates a TAR archive. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Stefan Bodewig + * @author Magesh Umasankar + */ + +public class Tar extends MatchingTask +{ + + /** + * @deprecated Tar.WARN is deprecated and is replaced with + * Tar.TarLongFileMode.WARN + */ + public final static String WARN = "warn"; + /** + * @deprecated Tar.FAIL is deprecated and is replaced with + * Tar.TarLongFileMode.FAIL + */ + public final static String FAIL = "fail"; + /** + * @deprecated Tar.TRUNCATE is deprecated and is replaced with + * Tar.TarLongFileMode.TRUNCATE + */ + public final static String TRUNCATE = "truncate"; + /** + * @deprecated Tar.GNU is deprecated and is replaced with + * Tar.TarLongFileMode.GNU + */ + public final static String GNU = "gnu"; + /** + * @deprecated Tar.OMIT is deprecated and is replaced with + * Tar.TarLongFileMode.OMIT + */ + public final static String OMIT = "omit"; + + private TarLongFileMode longFileMode = new TarLongFileMode(); + + Vector filesets = new Vector(); + Vector fileSetFiles = new Vector(); + + /** + * Indicates whether the user has been warned about long files already. + */ + private boolean longWarningGiven = false; + File baseDir; + + File tarFile; + + /** + * This is the base directory to look in for things to tar. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * Set how to handle long files. Allowable values are truncate - paths are + * truncated to the maximum length fail - paths greater than the maximim + * cause a build exception warn - paths greater than the maximum cause a + * warning and GNU is used gnu - GNU extensions are used for any paths + * greater than the maximum. omit - paths greater than the maximum are + * omitted from the archive + * + * @param mode The new Longfile value + * @deprecated setLongFile(String) is deprecated and is replaced with + * setLongFile(Tar.TarLongFileMode) to make Ant's Introspection + * mechanism do the work and also to encapsulate operations on the mode + * in its own class. + */ + public void setLongfile( String mode ) + { + log( "DEPRECATED - The setLongfile(String) method has been deprecated." + + " Use setLongfile(Tar.TarLongFileMode) instead." ); + this.longFileMode = new TarLongFileMode(); + longFileMode.setValue( mode ); + } + + /** + * Set how to handle long files. Allowable values are truncate - paths are + * truncated to the maximum length fail - paths greater than the maximim + * cause a build exception warn - paths greater than the maximum cause a + * warning and GNU is used gnu - GNU extensions are used for any paths + * greater than the maximum. omit - paths greater than the maximum are + * omitted from the archive + * + * @param mode The new Longfile value + */ + public void setLongfile( TarLongFileMode mode ) + { + this.longFileMode = mode; + } + + + /** + * This is the name/location of where to create the tar file. + * + * @param tarFile The new Tarfile value + */ + public void setTarfile( File tarFile ) + { + this.tarFile = tarFile; + } + + public TarFileSet createTarFileSet() + { + TarFileSet fileset = new TarFileSet(); + filesets.addElement( fileset ); + return fileset; + } + + public void execute() + throws BuildException + { + if( tarFile == null ) + { + throw new BuildException( "tarfile attribute must be set!", + location ); + } + + if( tarFile.exists() && tarFile.isDirectory() ) + { + throw new BuildException( "tarfile is a directory!", + location ); + } + + if( tarFile.exists() && !tarFile.canWrite() ) + { + throw new BuildException( "Can not write to the specified tarfile!", + location ); + } + + if( baseDir != null ) + { + if( !baseDir.exists() ) + { + throw new BuildException( "basedir does not exist!", location ); + } + + // add the main fileset to the list of filesets to process. + TarFileSet mainFileSet = new TarFileSet( fileset ); + mainFileSet.setDir( baseDir ); + filesets.addElement( mainFileSet ); + } + + if( filesets.size() == 0 ) + { + throw new BuildException( "You must supply either a basdir attribute or some nested filesets.", + location ); + } + + // check if tr is out of date with respect to each + // fileset + boolean upToDate = true; + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + TarFileSet fs = ( TarFileSet )e.nextElement(); + String[] files = fs.getFiles( project ); + + if( !archiveIsUpToDate( files ) ) + { + upToDate = false; + } + + for( int i = 0; i < files.length; ++i ) + { + if( tarFile.equals( new File( fs.getDir( project ), files[i] ) ) ) + { + throw new BuildException( "A tar file cannot include itself", location ); + } + } + } + + if( upToDate ) + { + log( "Nothing to do: " + tarFile.getAbsolutePath() + " is up to date.", + Project.MSG_INFO ); + return; + } + + log( "Building tar: " + tarFile.getAbsolutePath(), Project.MSG_INFO ); + + TarOutputStream tOut = null; + try + { + tOut = new TarOutputStream( new FileOutputStream( tarFile ) ); + tOut.setDebug( true ); + if( longFileMode.isTruncateMode() ) + { + tOut.setLongFileMode( TarOutputStream.LONGFILE_TRUNCATE ); + } + else if( longFileMode.isFailMode() || + longFileMode.isOmitMode() ) + { + tOut.setLongFileMode( TarOutputStream.LONGFILE_ERROR ); + } + else + { + // warn or GNU + tOut.setLongFileMode( TarOutputStream.LONGFILE_GNU ); + } + + longWarningGiven = false; + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + TarFileSet fs = ( TarFileSet )e.nextElement(); + String[] files = fs.getFiles( project ); + for( int i = 0; i < files.length; i++ ) + { + File f = new File( fs.getDir( project ), files[i] ); + String name = files[i].replace( File.separatorChar, '/' ); + tarFile( f, tOut, name, fs ); + } + } + } + catch( IOException ioe ) + { + String msg = "Problem creating TAR: " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( tOut != null ) + { + try + { + // close up + tOut.close(); + } + catch( IOException e ) + {} + } + } + } + + protected boolean archiveIsUpToDate( String[] files ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + MergingMapper mm = new MergingMapper(); + mm.setTo( tarFile.getAbsolutePath() ); + return sfs.restrict( files, baseDir, null, mm ).length == 0; + } + + protected void tarFile( File file, TarOutputStream tOut, String vPath, + TarFileSet tarFileSet ) + throws IOException + { + FileInputStream fIn = null; + + // don't add "" to the archive + if( vPath.length() <= 0 ) + { + return; + } + + if( file.isDirectory() && !vPath.endsWith( "/" ) ) + { + vPath += "/"; + } + + try + { + if( vPath.length() >= TarConstants.NAMELEN ) + { + if( longFileMode.isOmitMode() ) + { + log( "Omitting: " + vPath, Project.MSG_INFO ); + return; + } + else if( longFileMode.isWarnMode() ) + { + log( "Entry: " + vPath + " longer than " + + TarConstants.NAMELEN + " characters.", Project.MSG_WARN ); + if( !longWarningGiven ) + { + log( "Resulting tar file can only be processed successfully" + + " by GNU compatible tar commands", Project.MSG_WARN ); + longWarningGiven = true; + } + } + else if( longFileMode.isFailMode() ) + { + throw new BuildException( + "Entry: " + vPath + " longer than " + + TarConstants.NAMELEN + "characters.", location ); + } + } + + TarEntry te = new TarEntry( vPath ); + te.setModTime( file.lastModified() ); + if( !file.isDirectory() ) + { + te.setSize( file.length() ); + te.setMode( tarFileSet.getMode() ); + } + te.setUserName( tarFileSet.getUserName() ); + te.setGroupName( tarFileSet.getGroup() ); + + tOut.putNextEntry( te ); + + if( !file.isDirectory() ) + { + fIn = new FileInputStream( file ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + tOut.write( buffer, 0, count ); + count = fIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + + tOut.closeEntry(); + } + finally + { + if( fIn != null ) + fIn.close(); + } + } + + public static class TarFileSet extends FileSet + { + private String[] files = null; + + private int mode = 0100644; + + private String userName = ""; + private String groupName = ""; + + + public TarFileSet( FileSet fileset ) + { + super( fileset ); + } + + public TarFileSet() + { + super(); + } + + public void setGroup( String groupName ) + { + this.groupName = groupName; + } + + public void setMode( String octalString ) + { + this.mode = 0100000 | Integer.parseInt( octalString, 8 ); + } + + public void setUserName( String userName ) + { + this.userName = userName; + } + + /** + * Get a list of files and directories specified in the fileset. + * + * @param p Description of Parameter + * @return a list of file and directory names, relative to the baseDir + * for the project. + */ + public String[] getFiles( Project p ) + { + if( files == null ) + { + DirectoryScanner ds = getDirectoryScanner( p ); + String[] directories = ds.getIncludedDirectories(); + String[] filesPerSe = ds.getIncludedFiles(); + files = new String[directories.length + filesPerSe.length]; + System.arraycopy( directories, 0, files, 0, directories.length ); + System.arraycopy( filesPerSe, 0, files, directories.length, + filesPerSe.length ); + } + + return files; + } + + public String getGroup() + { + return groupName; + } + + public int getMode() + { + return mode; + } + + public String getUserName() + { + return userName; + } + + } + + /** + * Valid Modes for LongFile attribute to Tar Task + * + * @author Magesh Umasankar + */ + public static class TarLongFileMode extends EnumeratedAttribute + { + + // permissable values for longfile attribute + public final static String WARN = "warn"; + public final static String FAIL = "fail"; + public final static String TRUNCATE = "truncate"; + public final static String GNU = "gnu"; + public final static String OMIT = "omit"; + + private final String[] validModes = {WARN, FAIL, TRUNCATE, GNU, OMIT}; + + public TarLongFileMode() + { + super(); + setValue( WARN ); + } + + public String[] getValues() + { + return validModes; + } + + public boolean isFailMode() + { + return FAIL.equalsIgnoreCase( getValue() ); + } + + public boolean isGnuMode() + { + return GNU.equalsIgnoreCase( getValue() ); + } + + public boolean isOmitMode() + { + return OMIT.equalsIgnoreCase( getValue() ); + } + + public boolean isTruncateMode() + { + return TRUNCATE.equalsIgnoreCase( getValue() ); + } + + public boolean isWarnMode() + { + return WARN.equalsIgnoreCase( getValue() ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/TaskOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/TaskOutputStream.java new file mode 100644 index 000000000..5af5c9934 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/TaskOutputStream.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Task; + +/** + * Redirects text written to a stream thru the standard ant logging mechanism. + * This class is useful for integrating with tools that write to System.out and + * System.err. For example, the following will cause all text written to + * System.out to be logged with "info" priority:

              System.setOut(new PrintStream(new TaskOutputStream(project, Project.MSG_INFO)));
              + * + * @author James Duncan Davidson (duncan@x180.com) + * @deprecated use LogOutputStream instead. + */ + +public class TaskOutputStream extends OutputStream +{ + private StringBuffer line; + private int msgOutputLevel; + + private Task task; + + /** + * Constructs a new JavacOutputStream with the given project as the output + * source for messages. + * + * @param task Description of Parameter + * @param msgOutputLevel Description of Parameter + */ + + TaskOutputStream( Task task, int msgOutputLevel ) + { + this.task = task; + this.msgOutputLevel = msgOutputLevel; + + line = new StringBuffer(); + } + + /** + * Write a character to the output stream. This method looks to make sure + * that there isn't an error being reported and will flush each line of + * input out to the project's log stream. + * + * @param c Description of Parameter + * @exception IOException Description of Exception + */ + + public void write( int c ) + throws IOException + { + char cc = ( char )c; + if( cc == '\r' || cc == '\n' ) + { + // line feed + if( line.length() > 0 ) + { + processLine(); + } + } + else + { + line.append( cc ); + } + } + + /** + * Processes a line of input and determines if an error occured. + */ + + private void processLine() + { + String s = line.toString(); + task.log( s, msgOutputLevel ); + line = new StringBuffer(); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Taskdef.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Taskdef.java new file mode 100644 index 000000000..609a2771d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Taskdef.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Define a new task. + * + * @author Stefan Bodewig + */ +public class Taskdef extends Definer +{ + protected void addDefinition( String name, Class c ) + throws BuildException + { + project.addTaskDefinition( name, c ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Touch.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Touch.java new file mode 100644 index 000000000..358597118 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Touch.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Locale; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.FileUtils; + +/** + * Touch a file and/or fileset(s) -- corresponds to the Unix touch command.

              + * + * If the file to touch doesn't exist, an empty one is created.

              + * + * Note: Setting the modification time of files is not supported in JDK 1.1.

              + * + * @author Stefan Bodewig + * @author Michael J. Sikorsky + * @author Robert Shaw + */ +public class Touch extends Task +{// required + private long millis = -1; + private Vector filesets = new Vector(); + private String dateTime; + + private File file; + private FileUtils fileUtils; + + public Touch() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Date in the format MM/DD/YYYY HH:MM AM_PM. + * + * @param dateTime The new Datetime value + */ + public void setDatetime( String dateTime ) + { + this.dateTime = dateTime; + } + + /** + * Sets a single source file to touch. If the file does not exist an empty + * file will be created. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Milliseconds since 01/01/1970 00:00 am. + * + * @param millis The new Millis value + */ + public void setMillis( long millis ) + { + this.millis = millis; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Execute the touch operation. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( file == null && filesets.size() == 0 ) + { + throw + new BuildException( "Specify at least one source - a file or a fileset." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( "Use a fileset to touch directories." ); + } + + if( dateTime != null ) + { + DateFormat df = DateFormat.getDateTimeInstance( DateFormat.SHORT, + DateFormat.SHORT, + Locale.US ); + try + { + setMillis( df.parse( dateTime ).getTime() ); + if( millis < 0 ) + { + throw new BuildException( "Date of " + dateTime + + " results in negative milliseconds value relative to epoch (January 1, 1970, 00:00:00 GMT)." ); + } + } + catch( ParseException pe ) + { + throw new BuildException( pe.getMessage(), pe, location ); + } + } + + touch(); + } + + /** + * Does the actual work. Entry point for Untar and Expand as well. + * + * @exception BuildException Description of Exception + */ + protected void touch() + throws BuildException + { + if( file != null ) + { + if( !file.exists() ) + { + log( "Creating " + file, Project.MSG_INFO ); + try + { + FileOutputStream fos = new FileOutputStream( file ); + fos.write( new byte[0] ); + fos.close(); + } + catch( IOException ioe ) + { + throw new BuildException( "Could not create " + file, ioe, + location ); + } + } + } + + if( millis >= 0 && project.getJavaVersion() == Project.JAVA_1_1 ) + { + log( "modification time of files cannot be set in JDK 1.1", + Project.MSG_WARN ); + return; + } + + boolean resetMillis = false; + if( millis < 0 ) + { + resetMillis = true; + millis = System.currentTimeMillis(); + } + + if( file != null ) + { + touch( file ); + } + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + String[] srcDirs = ds.getIncludedDirectories(); + + for( int j = 0; j < srcFiles.length; j++ ) + { + touch( new File( fromDir, srcFiles[j] ) ); + } + + for( int j = 0; j < srcDirs.length; j++ ) + { + touch( new File( fromDir, srcDirs[j] ) ); + } + } + + if( resetMillis ) + { + millis = -1; + } + } + + protected void touch( File file ) + throws BuildException + { + if( !file.canWrite() ) + { + throw new BuildException( "Can not change modification date of read-only file " + file ); + } + + if( project.getJavaVersion() == Project.JAVA_1_1 ) + { + return; + } + + fileUtils.setFileLastModified( file, millis ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Transform.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Transform.java new file mode 100644 index 000000000..02ecfae96 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Transform.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +/** + * Has been merged into ExecuteOn, empty class for backwards compatibility. + * + * @author Stefan Bodewig + */ +public class Transform extends ExecuteOn +{ +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tstamp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tstamp.java new file mode 100644 index 000000000..276d964e0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tstamp.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; +import java.util.TimeZone; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Sets TSTAMP, DSTAMP and TODAY + * + * @author costin@dnt.ro + * @author stefano@apache.org + * @author roxspring@yahoo.com + * @author conor@cognet.com.au + * @author Magesh Umasankar + */ +public class Tstamp extends Task +{ + + private Vector customFormats = new Vector(); + private String prefix = ""; + + public void setPrefix( String prefix ) + { + this.prefix = prefix; + if( !this.prefix.endsWith( "." ) ) + { + this.prefix += "."; + } + } + + public CustomFormat createFormat() + { + CustomFormat cts = new CustomFormat( prefix ); + customFormats.addElement( cts ); + return cts; + } + + public void execute() + throws BuildException + { + try + { + Date d = new Date(); + + SimpleDateFormat dstamp = new SimpleDateFormat( "yyyyMMdd" ); + project.setNewProperty( prefix + "DSTAMP", dstamp.format( d ) ); + + SimpleDateFormat tstamp = new SimpleDateFormat( "HHmm" ); + project.setNewProperty( prefix + "TSTAMP", tstamp.format( d ) ); + + SimpleDateFormat today = new SimpleDateFormat( "MMMM d yyyy", Locale.US ); + project.setNewProperty( prefix + "TODAY", today.format( d ) ); + + Enumeration i = customFormats.elements(); + while( i.hasMoreElements() ) + { + CustomFormat cts = ( CustomFormat )i.nextElement(); + cts.execute( project, d, location ); + } + + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + public static class Unit extends EnumeratedAttribute + { + + private final static String MILLISECOND = "millisecond"; + private final static String SECOND = "second"; + private final static String MINUTE = "minute"; + private final static String HOUR = "hour"; + private final static String DAY = "day"; + private final static String WEEK = "week"; + private final static String MONTH = "month"; + private final static String YEAR = "year"; + + private final static String[] units = { + MILLISECOND, + SECOND, + MINUTE, + HOUR, + DAY, + WEEK, + MONTH, + YEAR + }; + + private Hashtable calendarFields = new Hashtable(); + + public Unit() + { + calendarFields.put( MILLISECOND, + new Integer( Calendar.MILLISECOND ) ); + calendarFields.put( SECOND, new Integer( Calendar.SECOND ) ); + calendarFields.put( MINUTE, new Integer( Calendar.MINUTE ) ); + calendarFields.put( HOUR, new Integer( Calendar.HOUR_OF_DAY ) ); + calendarFields.put( DAY, new Integer( Calendar.DATE ) ); + calendarFields.put( WEEK, new Integer( Calendar.WEEK_OF_YEAR ) ); + calendarFields.put( MONTH, new Integer( Calendar.MONTH ) ); + calendarFields.put( YEAR, new Integer( Calendar.YEAR ) ); + } + + public int getCalendarField() + { + String key = getValue().toLowerCase(); + Integer i = ( Integer )calendarFields.get( key ); + return i.intValue(); + } + + public String[] getValues() + { + return units; + } + } + + public class CustomFormat + { + private int offset = 0; + private int field = Calendar.DATE; + private String prefix = ""; + private String country; + private String language; + private String pattern; + private String propertyName; + private TimeZone timeZone; + private String variant; + + public CustomFormat( String prefix ) + { + this.prefix = prefix; + } + + public void setLocale( String locale ) + { + StringTokenizer st = new StringTokenizer( locale, " \t\n\r\f," ); + try + { + language = st.nextToken(); + if( st.hasMoreElements() ) + { + country = st.nextToken(); + if( st.hasMoreElements() ) + { + country = st.nextToken(); + if( st.hasMoreElements() ) + { + throw new BuildException( "bad locale format", getLocation() ); + } + } + } + else + { + country = ""; + } + } + catch( NoSuchElementException e ) + { + throw new BuildException( "bad locale format", e, getLocation() ); + } + } + + public void setOffset( int offset ) + { + this.offset = offset; + } + + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + public void setProperty( String propertyName ) + { + this.propertyName = prefix + propertyName; + } + + public void setTimezone( String id ) + { + timeZone = TimeZone.getTimeZone( id ); + } + + /** + * @param unit The new Unit value + * @deprecated setUnit(String) is deprecated and is replaced with + * setUnit(Tstamp.Unit) to make Ant's Introspection mechanism do + * the work and also to encapsulate operations on the unit in its + * own class. + */ + public void setUnit( String unit ) + { + log( "DEPRECATED - The setUnit(String) method has been deprecated." + + " Use setUnit(Tstamp.Unit) instead." ); + Unit u = new Unit(); + u.setValue( unit ); + field = u.getCalendarField(); + } + + public void setUnit( Unit unit ) + { + field = unit.getCalendarField(); + } + + public void execute( Project project, Date date, Location location ) + { + if( propertyName == null ) + { + throw new BuildException( "property attribute must be provided", location ); + } + + if( pattern == null ) + { + throw new BuildException( "pattern attribute must be provided", location ); + } + + SimpleDateFormat sdf; + if( language == null ) + { + sdf = new SimpleDateFormat( pattern ); + } + else if( variant == null ) + { + sdf = new SimpleDateFormat( pattern, new Locale( language, country ) ); + } + else + { + sdf = new SimpleDateFormat( pattern, new Locale( language, country, variant ) ); + } + if( offset != 0 ) + { + Calendar calendar = Calendar.getInstance(); + calendar.setTime( date ); + calendar.add( field, offset ); + date = calendar.getTime(); + } + if( timeZone != null ) + { + sdf.setTimeZone( timeZone ); + } + project.setNewProperty( propertyName, sdf.format( date ) ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Typedef.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Typedef.java new file mode 100644 index 000000000..2bb6ac880 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Typedef.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Define a new data type. + * + * @author Stefan Bodewig + */ +public class Typedef extends Definer +{ + protected void addDefinition( String name, Class c ) + throws BuildException + { + project.addDataTypeDefinition( name, c ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Unpack.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Unpack.java new file mode 100644 index 000000000..1e641b251 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Unpack.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Abstract Base class for unpack tasks. + * + * @author Magesh Umasankar + */ + +public abstract class Unpack extends Task +{ + protected File dest; + + protected File source; + + public void setDest( String dest ) + { + this.dest = project.resolveFile( dest ); + } + + public void setSrc( String src ) + { + source = project.resolveFile( src ); + } + + public void execute() + throws BuildException + { + validate(); + extract(); + } + + protected abstract String getDefaultExtension(); + + protected abstract void extract(); + + private void createDestFile( String defaultExtension ) + { + String sourceName = source.getName(); + int len = sourceName.length(); + if( defaultExtension != null + && len > defaultExtension.length() + && defaultExtension.equalsIgnoreCase( sourceName.substring( len - defaultExtension.length() ) ) ) + { + dest = new File( dest, sourceName.substring( 0, + len - defaultExtension.length() ) ); + } + else + { + dest = new File( dest, sourceName ); + } + } + + private void validate() + throws BuildException + { + if( source == null ) + { + throw new BuildException( "No Src for gunzip specified", location ); + } + + if( !source.exists() ) + { + throw new BuildException( "Src doesn't exist", location ); + } + + if( source.isDirectory() ) + { + throw new BuildException( "Cannot expand a directory", location ); + } + + if( dest == null ) + { + dest = new File( source.getParent() ); + } + + if( dest.isDirectory() ) + { + String defaultExtension = getDefaultExtension(); + createDestFile( defaultExtension ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Untar.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Untar.java new file mode 100644 index 000000000..99fba8759 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Untar.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Date; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.tar.TarEntry; +import org.apache.tools.tar.TarInputStream; + +/** + * Untar a file. Heavily based on the Expand task. + * + * @author Stefan Bodewig + * @author Magesh Umasankar + */ +public class Untar extends Expand +{ + + protected void expandFile( FileUtils fileUtils, File srcF, File dir ) + { + TarInputStream tis = null; + try + { + log( "Expanding: " + srcF + " into " + dir, Project.MSG_INFO ); + + tis = new TarInputStream( new FileInputStream( srcF ) ); + TarEntry te = null; + + while( ( te = tis.getNextEntry() ) != null ) + { + extractFile( fileUtils, srcF, dir, tis, + te.getName(), + te.getModTime(), te.isDirectory() ); + } + log( "expand complete", Project.MSG_VERBOSE ); + + } + catch( IOException ioe ) + { + throw new BuildException( "Error while expanding " + srcF.getPath(), + ioe, location ); + } + finally + { + if( tis != null ) + { + try + { + tis.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/UpToDate.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/UpToDate.java new file mode 100644 index 000000000..968131b0a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/UpToDate.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Will set the given property if the specified target has a timestamp greater + * than all of the source files. + * + * @author William Ferguson + * williamf@mincom.com + * @author Hiroaki Nakamura + * hnakamur@mc.neweb.ne.jp + * @author Stefan Bodewig + */ + +public class UpToDate extends MatchingTask implements Condition +{ + private Vector sourceFileSets = new Vector(); + + protected Mapper mapperElement = null; + + private String _property; + private File _targetFile; + private String _value; + + /** + * The property to set if the target file is more up to date than each of + * the source files. + * + * @param property the name of the property to set if Target is up to date. + */ + public void setProperty( String property ) + { + _property = property; + } + + /** + * The file which must be more up to date than each of the source files if + * the property is to be set. + * + * @param file the file which we are checking against. + */ + public void setTargetFile( File file ) + { + _targetFile = file; + } + + /** + * The value to set the named property to if the target file is more up to + * date than each of the source files. Defaults to 'true'. + * + * @param value the value to set the property to if Target is up to date + */ + public void setValue( String value ) + { + _value = value; + } + + /** + * Nested <srcfiles> element. + * + * @param fs The feature to be added to the Srcfiles attribute + */ + public void addSrcfiles( FileSet fs ) + { + sourceFileSets.addElement( fs ); + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Evaluate all target and source files, see if the targets are up-to-date. + * + * @return Description of the Returned Value + */ + public boolean eval() + { + if( sourceFileSets.size() == 0 ) + { + throw new BuildException( "At least one element must be set" ); + } + + if( _targetFile == null && mapperElement == null ) + { + throw new BuildException( "The targetfile attribute or a nested mapper element must be set" ); + } + + // if not there then it can't be up to date + if( _targetFile != null && !_targetFile.exists() ) + return false; + + Enumeration enum = sourceFileSets.elements(); + boolean upToDate = true; + while( upToDate && enum.hasMoreElements() ) + { + FileSet fs = ( FileSet )enum.nextElement(); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + upToDate = upToDate && scanDir( fs.getDir( project ), + ds.getIncludedFiles() ); + } + return upToDate; + } + + + /** + * Sets property to true if target files have a more recent timestamp than + * each of the corresponding source files. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + boolean upToDate = eval(); + if( upToDate ) + { + this.project.setProperty( _property, this.getValue() ); + if( mapperElement == null ) + { + log( "File \"" + _targetFile.getAbsolutePath() + "\" is up to date.", + Project.MSG_VERBOSE ); + } + else + { + log( "All target files have been up to date.", + Project.MSG_VERBOSE ); + } + } + } + + protected boolean scanDir( File srcDir, String files[] ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + FileNameMapper mapper = null; + File dir = srcDir; + if( mapperElement == null ) + { + MergingMapper mm = new MergingMapper(); + mm.setTo( _targetFile.getAbsolutePath() ); + mapper = mm; + dir = null; + } + else + { + mapper = mapperElement.getImplementation(); + } + return sfs.restrict( files, srcDir, dir, mapper ).length == 0; + } + + /** + * Returns the value, or "true" if a specific value wasn't provided. + * + * @return The Value value + */ + private String getValue() + { + return ( _value != null ) ? _value : "true"; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/WaitFor.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/WaitFor.java new file mode 100644 index 000000000..9f87abb1d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/WaitFor.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.taskdefs.condition.ConditionBase; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Wait for an external event to occur. Wait for an external process to start or + * to complete some task. This is useful with the parallel task to + * syncronize the execution of tests with server startup. The following + * attributes can be specified on a waitfor task: + *
                + *
              • maxwait - maximum length of time to wait before giving up
              • + *
              • maxwaitunit - The unit to be used to interpret maxwait attribute
              • + * + *
              • checkevery - amount of time to sleep between each check
              • + *
              • checkeveryunit - The unit to be used to interpret checkevery attribute + *
              • + *
              • timeoutproperty - name of a property to set if maxwait has been + * exceeded.
              • + *
              + * The maxwaitunit and checkeveryunit are allowed to have the following values: + * millesond, second, minute, hour, day and week. The default is millisecond. + * + * @author Denis Hennessy + * @author Magesh Umasankar + */ + +public class WaitFor extends ConditionBase +{ + private long maxWaitMillis = 1000l * 60l * 3l;// default max wait time + private long maxWaitMultiplier = 1l; + private long checkEveryMillis = 500l; + private long checkEveryMultiplier = 1l; + private String timeoutProperty; + + /** + * Set the time between each check + * + * @param time The new CheckEvery value + */ + public void setCheckEvery( long time ) + { + checkEveryMillis = time; + } + + /** + * Set the check every time unit + * + * @param unit The new CheckEveryUnit value + */ + public void setCheckEveryUnit( Unit unit ) + { + checkEveryMultiplier = unit.getMultiplier(); + } + + /** + * Set the maximum length of time to wait + * + * @param time The new MaxWait value + */ + public void setMaxWait( long time ) + { + maxWaitMillis = time; + } + + /** + * Set the max wait time unit + * + * @param unit The new MaxWaitUnit value + */ + public void setMaxWaitUnit( Unit unit ) + { + maxWaitMultiplier = unit.getMultiplier(); + } + + /** + * Set the timeout property. + * + * @param p The new TimeoutProperty value + */ + public void setTimeoutProperty( String p ) + { + timeoutProperty = p; + } + + /** + * Check repeatedly for the specified conditions until they become true or + * the timeout expires. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + Condition c = ( Condition )getConditions().nextElement(); + + maxWaitMillis *= maxWaitMultiplier; + checkEveryMillis *= checkEveryMultiplier; + long start = System.currentTimeMillis(); + long end = start + maxWaitMillis; + + while( System.currentTimeMillis() < end ) + { + if( c.eval() ) + { + return; + } + try + { + Thread.sleep( checkEveryMillis ); + } + catch( InterruptedException e ) + { + } + } + + if( timeoutProperty != null ) + { + project.setNewProperty( timeoutProperty, "true" ); + } + } + + public static class Unit extends EnumeratedAttribute + { + + private final static String MILLISECOND = "millisecond"; + private final static String SECOND = "second"; + private final static String MINUTE = "minute"; + private final static String HOUR = "hour"; + private final static String DAY = "day"; + private final static String WEEK = "week"; + + private final static String[] units = { + MILLISECOND, SECOND, MINUTE, HOUR, DAY, WEEK + }; + + private Hashtable timeTable = new Hashtable(); + + public Unit() + { + timeTable.put( MILLISECOND, new Long( 1l ) ); + timeTable.put( SECOND, new Long( 1000l ) ); + timeTable.put( MINUTE, new Long( 1000l * 60l ) ); + timeTable.put( HOUR, new Long( 1000l * 60l * 60l ) ); + timeTable.put( DAY, new Long( 1000l * 60l * 60l * 24l ) ); + timeTable.put( WEEK, new Long( 1000l * 60l * 60l * 24l * 7l ) ); + } + + public long getMultiplier() + { + String key = getValue().toLowerCase(); + Long l = ( Long )timeTable.get( key ); + return l.longValue(); + } + + public String[] getValues() + { + return units; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/War.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/War.java new file mode 100644 index 000000000..ff2bf9c48 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/War.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + + +/** + * Creates a WAR archive. + * + * @author Stefan Bodewig + */ +public class War extends Jar +{ + + private File deploymentDescriptor; + private boolean descriptorAdded; + + public War() + { + super(); + archiveType = "war"; + emptyBehavior = "create"; + } + + public void setWarfile( File warFile ) + { + log( "DEPRECATED - The warfile attribute is deprecated. Use file attribute instead." ); + setFile( warFile ); + } + + public void setWebxml( File descr ) + { + deploymentDescriptor = descr; + if( !deploymentDescriptor.exists() ) + throw new BuildException( "Deployment descriptor: " + deploymentDescriptor + " does not exist." ); + + // Create a ZipFileSet for this file, and pass it up. + ZipFileSet fs = new ZipFileSet(); + fs.setDir( new File( deploymentDescriptor.getParent() ) ); + fs.setIncludes( deploymentDescriptor.getName() ); + fs.setFullpath( "WEB-INF/web.xml" ); + super.addFileset( fs ); + } + + public void addClasses( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/classes/" ); + super.addFileset( fs ); + } + + public void addLib( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/lib/" ); + super.addFileset( fs ); + } + + public void addWebinf( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/" ); + super.addFileset( fs ); + } + + /** + * Make sure we don't think we already have a web.xml next time this task + * gets executed. + */ + protected void cleanUp() + { + descriptorAdded = false; + super.cleanUp(); + } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + // If no webxml file is specified, it's an error. + if( deploymentDescriptor == null && !isInUpdateMode() ) + { + throw new BuildException( "webxml attribute is required", location ); + } + + super.initZipOutputStream( zOut ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is WEB-INF/web.xml, we warn if it's not the + // one specified in the "webxml" attribute - or if it's being added twice, + // meaning the same file is specified by the "webxml" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "WEB-INF/web.xml" ) ) + { + if( deploymentDescriptor == null || !deploymentDescriptor.equals( file ) || descriptorAdded ) + { + log( "Warning: selected " + archiveType + " files include a WEB-INF/web.xml which will be ignored " + + "(please use webxml attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + descriptorAdded = true; + } + } + else + { + super.zipFile( file, zOut, vPath ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLiaison.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLiaison.java new file mode 100644 index 000000000..a4d01bd8e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLiaison.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; + +/** + * Proxy interface for XSLT processors. + * + * @author Sam Ruby + * @author Stephane Bailliez + * @see XSLTProcess + */ +public interface XSLTLiaison +{ + + /** + * the file protocol prefix for systemid. This file protocol must be + * appended to an absolute path. Typically: FILE_PROTOCOL_PREFIX + + * file.getAbsolutePath() This is not correct in specification terms + * since an absolute url in Unix is file:// + file.getAbsolutePath() while + * it is file:/// + file.getAbsolutePath() under Windows. Whatever, it + * should not be a problem to put file:/// in every case since most parsers + * for now incorrectly makes no difference between it.. and users also have + * problem with that :) + */ + String FILE_PROTOCOL_PREFIX = "file:///"; + + /** + * set the stylesheet to use for the transformation. + * + * @param stylesheet the stylesheet to be used for transformation. + * @exception Exception Description of Exception + */ + void setStylesheet( File stylesheet ) + throws Exception; + + /** + * Add a parameter to be set during the XSL transformation. + * + * @param name the parameter name. + * @param expression the parameter value as an expression string. + * @throws Exception thrown if any problems happens. + */ + void addParam( String name, String expression ) + throws Exception; + + /** + * set the output type to use for the transformation. Only "xml" (the + * default) is guaranteed to work for all parsers. Xalan2 also supports + * "html" and "text". + * + * @param type the output method to use + * @exception Exception Description of Exception + */ + void setOutputtype( String type ) + throws Exception; + + /** + * Perform the transformation of a file into another. + * + * @param infile the input file, probably an XML one. :-) + * @param outfile the output file resulting from the transformation + * @see #setStylesheet(File) + * @throws Exception thrown if any problems happens. + */ + void transform( File infile, File outfile ) + throws Exception; + +}//-- XSLTLiaison diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLogger.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLogger.java new file mode 100644 index 000000000..4579d4085 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLogger.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +public interface XSLTLogger +{ + /** + * Log a message. + * + * @param msg Description of Parameter + */ + void log( String msg ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java new file mode 100644 index 000000000..ae2051ae0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java @@ -0,0 +1,13 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +public interface XSLTLoggerAware +{ + void setLogger( XSLTLogger l ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTProcess.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTProcess.java new file mode 100644 index 000000000..13a17d2b2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTProcess.java @@ -0,0 +1,581 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + + +/** + * A Task to process via XSLT a set of XML documents. This is useful for + * building views of XML based documentation. arguments: + *
                + *
              • basedir + *
              • destdir + *
              • style + *
              • includes + *
              • excludes + *
              + * Of these arguments, the sourcedir and destdir are required.

              + * + * This task will recursively scan the sourcedir and destdir looking for XML + * documents to process via XSLT. Any other files, such as images, or html files + * in the source directory will be copied into the destination directory. + * + * @author Keith Visco + * @author Sam Ruby + * @author Russell Gold + * @author Stefan Bodewig + */ + +public class XSLTProcess extends MatchingTask implements XSLTLogger +{ + + private File destDir = null; + + private File baseDir = null; + + private String xslFile = null; + + private String targetExtension = ".html"; + private Vector params = new Vector(); + + private File inFile = null; + + private File outFile = null; + private Path classpath = null; + private boolean stylesheetLoaded = false; + + private boolean force = false; + + private String outputtype = null; + + private FileUtils fileUtils; + private XSLTLiaison liaison; + + private String processor; + + /** + * Creates a new XSLTProcess Task. + */ + public XSLTProcess() + { + fileUtils = FileUtils.newFileUtils(); + }//-- setForce + + /** + * Set the base directory. + * + * @param dir The new Basedir value + */ + public void setBasedir( File dir ) + { + baseDir = dir; + } + + /** + * Set the classpath to load the Processor through (attribute). + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + createClasspath().append( classpath ); + } + + /** + * Set the classpath to load the Processor through via reference + * (attribute). + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + }//-- setSourceDir + + /** + * Set the destination directory into which the XSL result files should be + * copied to + * + * @param dir The new Destdir value + */ + public void setDestdir( File dir ) + { + destDir = dir; + }//-- setDestDir + + /** + * Set the desired file extension to be used for the target + * + * @param name the extension to use + */ + public void setExtension( String name ) + { + targetExtension = name; + }//-- execute + + /** + * Set whether to check dependencies, or always generate. + * + * @param force The new Force value + */ + public void setForce( boolean force ) + { + this.force = force; + } + + /** + * Sets an input xml file to be styled + * + * @param inFile The new In value + */ + public void setIn( File inFile ) + { + this.inFile = inFile; + } + + /** + * Sets an out file + * + * @param outFile The new Out value + */ + public void setOut( File outFile ) + { + this.outFile = outFile; + } + + /** + * Set the output type to use for the transformation. Only "xml" (the + * default) is guaranteed to work for all parsers. Xalan2 also supports + * "html" and "text". + * + * @param type the output method to use + */ + public void setOutputtype( String type ) + { + this.outputtype = type; + } + + + public void setProcessor( String processor ) + { + this.processor = processor; + }//-- setDestDir + + /** + * Sets the file to use for styling relative to the base directory of this + * task. + * + * @param xslFile The new Style value + */ + public void setStyle( String xslFile ) + { + this.xslFile = xslFile; + } + + /** + * Set the classpath to load the Processor through (nested element). + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + public Param createParam() + { + Param p = new Param(); + params.addElement( p ); + return p; + }//-- XSLTProcess + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + + public void execute() + throws BuildException + { + DirectoryScanner scanner; + String[] list; + String[] dirs; + + if( xslFile == null ) + { + throw new BuildException( "no stylesheet specified", location ); + } + + if( baseDir == null ) + { + baseDir = project.resolveFile( "." ); + } + + liaison = getLiaison(); + + // check if liaison wants to log errors using us as logger + if( liaison instanceof XSLTLoggerAware ) + { + ( ( XSLTLoggerAware )liaison ).setLogger( this ); + } + + log( "Using " + liaison.getClass().toString(), Project.MSG_VERBOSE ); + + File stylesheet = project.resolveFile( xslFile ); + if( !stylesheet.exists() ) + { + stylesheet = fileUtils.resolveFile( baseDir, xslFile ); + /* + * shouldn't throw out deprecation warnings before we know, + * the wrong version has been used. + */ + if( stylesheet.exists() ) + { + log( "DEPRECATED - the style attribute should be relative to the project\'s" ); + log( " basedir, not the tasks\'s basedir." ); + } + } + + // if we have an in file and out then process them + if( inFile != null && outFile != null ) + { + process( inFile, outFile, stylesheet ); + return; + } + + /* + * if we get here, in and out have not been specified, we are + * in batch processing mode. + */ + //-- make sure Source directory exists... + if( destDir == null ) + { + String msg = "destdir attributes must be set!"; + throw new BuildException( msg ); + } + scanner = getDirectoryScanner( baseDir ); + log( "Transforming into " + destDir, Project.MSG_INFO ); + + // Process all the files marked for styling + list = scanner.getIncludedFiles(); + for( int i = 0; i < list.length; ++i ) + { + process( baseDir, list[i], destDir, stylesheet ); + } + + // Process all the directoried marked for styling + dirs = scanner.getIncludedDirectories(); + for( int j = 0; j < dirs.length; ++j ) + { + list = new File( baseDir, dirs[j] ).list(); + for( int i = 0; i < list.length; ++i ) + process( baseDir, list[i], destDir, stylesheet ); + } + } + + protected XSLTLiaison getLiaison() + { + // if processor wasn't specified, see if TraX is available. If not, + // default it to xslp or xalan, depending on which is in the classpath + if( liaison == null ) + { + if( processor != null ) + { + try + { + resolveProcessor( processor ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + else + { + try + { + resolveProcessor( "trax" ); + } + catch( Throwable e1 ) + { + try + { + resolveProcessor( "xalan" ); + } + catch( Throwable e2 ) + { + try + { + resolveProcessor( "adaptx" ); + } + catch( Throwable e3 ) + { + try + { + resolveProcessor( "xslp" ); + } + catch( Throwable e4 ) + { + e4.printStackTrace(); + e3.printStackTrace(); + e2.printStackTrace(); + throw new BuildException( e1 ); + } + } + } + } + } + } + return liaison; + } + + /** + * Loads the stylesheet and set xsl:param parameters. + * + * @param stylesheet Description of Parameter + * @exception BuildException Description of Exception + */ + protected void configureLiaison( File stylesheet ) + throws BuildException + { + if( stylesheetLoaded ) + { + return; + } + stylesheetLoaded = true; + + try + { + log( "Loading stylesheet " + stylesheet, Project.MSG_INFO ); + liaison.setStylesheet( stylesheet ); + for( Enumeration e = params.elements(); e.hasMoreElements(); ) + { + Param p = ( Param )e.nextElement(); + liaison.addParam( p.getName(), p.getExpression() ); + } + } + catch( Exception ex ) + { + log( "Failed to read stylesheet " + stylesheet, Project.MSG_INFO ); + throw new BuildException( ex ); + } + } + + private void ensureDirectoryFor( File targetFile ) + throws BuildException + { + File directory = new File( targetFile.getParent() ); + if( !directory.exists() ) + { + if( !directory.mkdirs() ) + { + throw new BuildException( "Unable to create directory: " + + directory.getAbsolutePath() ); + } + } + } + + /** + * Load named class either via the system classloader or a given custom + * classloader. + * + * @param classname Description of Parameter + * @return Description of the Returned Value + * @exception Exception Description of Exception + */ + private Class loadClass( String classname ) + throws Exception + { + if( classpath == null ) + { + return Class.forName( classname ); + } + else + { + AntClassLoader al = new AntClassLoader( project, classpath ); + Class c = al.loadClass( classname ); + AntClassLoader.initializeClass( c ); + return c; + } + } + + /** + * Processes the given input XML file and stores the result in the given + * resultFile. + * + * @param baseDir Description of Parameter + * @param xmlFile Description of Parameter + * @param destDir Description of Parameter + * @param stylesheet Description of Parameter + * @exception BuildException Description of Exception + */ + private void process( File baseDir, String xmlFile, File destDir, + File stylesheet ) + throws BuildException + { + + String fileExt = targetExtension; + File outFile = null; + File inFile = null; + + try + { + long styleSheetLastModified = stylesheet.lastModified(); + inFile = new File( baseDir, xmlFile ); + int dotPos = xmlFile.lastIndexOf( '.' ); + if( dotPos > 0 ) + { + outFile = new File( destDir, xmlFile.substring( 0, xmlFile.lastIndexOf( '.' ) ) + fileExt ); + } + else + { + outFile = new File( destDir, xmlFile + fileExt ); + } + if( force || + inFile.lastModified() > outFile.lastModified() || + styleSheetLastModified > outFile.lastModified() ) + { + ensureDirectoryFor( outFile ); + log( "Processing " + inFile + " to " + outFile ); + + configureLiaison( stylesheet ); + liaison.transform( inFile, outFile ); + } + } + catch( Exception ex ) + { + // If failed to process document, must delete target document, + // or it will not attempt to process it the second time + log( "Failed to process " + inFile, Project.MSG_INFO ); + if( outFile != null ) + { + outFile.delete(); + } + + throw new BuildException( ex ); + } + + }//-- processXML + + private void process( File inFile, File outFile, File stylesheet ) + throws BuildException + { + try + { + long styleSheetLastModified = stylesheet.lastModified(); + log( "In file " + inFile + " time: " + inFile.lastModified(), Project.MSG_DEBUG ); + log( "Out file " + outFile + " time: " + outFile.lastModified(), Project.MSG_DEBUG ); + log( "Style file " + xslFile + " time: " + styleSheetLastModified, Project.MSG_DEBUG ); + if( force || + inFile.lastModified() > outFile.lastModified() || + styleSheetLastModified > outFile.lastModified() ) + { + ensureDirectoryFor( outFile ); + log( "Processing " + inFile + " to " + outFile, Project.MSG_INFO ); + configureLiaison( stylesheet ); + liaison.transform( inFile, outFile ); + } + } + catch( Exception ex ) + { + log( "Failed to process " + inFile, Project.MSG_INFO ); + if( outFile != null ) + outFile.delete(); + throw new BuildException( ex ); + } + } + + /** + * Load processor here instead of in setProcessor - this will be called from + * within execute, so we have access to the latest classpath. + * + * @param proc Description of Parameter + * @exception Exception Description of Exception + */ + private void resolveProcessor( String proc ) + throws Exception + { + if( proc.equals( "trax" ) ) + { + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.TraXLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "xslp" ) ) + { + log( "DEPRECATED - xslp processor is deprecated. Use trax or xalan instead." ); + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.XslpLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "xalan" ) ) + { + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.XalanLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "adaptx" ) ) + { + log( "DEPRECATED - adaptx processor is deprecated. Use trax or xalan instead." ); + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.AdaptxLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else + { + liaison = ( XSLTLiaison )loadClass( proc ).newInstance(); + } + } + + public class Param + { + private String name = null; + private String expression = null; + + public void setExpression( String expression ) + { + this.expression = expression; + } + + public void setName( String name ) + { + this.name = name; + } + + public String getExpression() + throws BuildException + { + if( expression == null ) + throw new BuildException( "Expression attribute is missing." ); + return expression; + } + + public String getName() + throws BuildException + { + if( name == null ) + throw new BuildException( "Name attribute is missing." ); + return name; + } + } + +}//-- XSLTProcess diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Zip.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Zip.java new file mode 100644 index 000000000..802ec0476 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Zip.java @@ -0,0 +1,888 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.Stack; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.ZipInputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.FileScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.ant.types.ZipScanner; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; +import org.apache.tools.zip.ZipEntry; +import org.apache.tools.zip.ZipOutputStream; + +/** + * Create a ZIP archive. + * + * @author James Davidson duncan@x180.com + * @author Jon S. Stevens jon@clearink.com + * @author Stefan Bodewig + */ +public class Zip extends MatchingTask +{ + + // For directories: + private final static long EMPTY_CRC = new CRC32().getValue(); + private boolean doCompress = true; + private boolean doUpdate = false; + private boolean doFilesonly = false; + protected String archiveType = "zip"; + protected String emptyBehavior = "skip"; + private Vector filesets = new Vector(); + protected Hashtable addedDirs = new Hashtable(); + private Vector addedFiles = new Vector(); + + protected File zipFile; + + /** + * true when we are adding new files into the Zip file, as opposed to adding + * back the unchanged files + */ + private boolean addingNewFiles; + private File baseDir; + + /** + * Encoding to use for filenames, defaults to the platform's default + * encoding. + */ + private String encoding; + + protected static String[][] grabFileNames( FileScanner[] scanners ) + { + String[][] result = new String[scanners.length][]; + for( int i = 0; i < scanners.length; i++ ) + { + String[] files = scanners[i].getIncludedFiles(); + String[] dirs = scanners[i].getIncludedDirectories(); + result[i] = new String[files.length + dirs.length]; + System.arraycopy( files, 0, result[i], 0, files.length ); + System.arraycopy( dirs, 0, result[i], files.length, dirs.length ); + } + return result; + } + + protected static File[] grabFiles( FileScanner[] scanners ) + { + return grabFiles( scanners, grabFileNames( scanners ) ); + } + + protected static File[] grabFiles( FileScanner[] scanners, + String[][] fileNames ) + { + Vector files = new Vector(); + for( int i = 0; i < fileNames.length; i++ ) + { + File thisBaseDir = scanners[i].getBasedir(); + for( int j = 0; j < fileNames[i].length; j++ ) + files.addElement( new File( thisBaseDir, fileNames[i][j] ) ); + } + File[] toret = new File[files.size()]; + files.copyInto( toret ); + return toret; + } + + /** + * This is the base directory to look in for things to zip. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * Sets whether we want to compress the files or only store them. + * + * @param c The new Compress value + */ + public void setCompress( boolean c ) + { + doCompress = c; + } + + /** + * Encoding to use for filenames, defaults to the platform's default + * encoding.

              + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * .

              + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * This is the name/location of where to create the .zip file. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.zipFile = file; + } + + /** + * Emulate Sun's jar utility by not adding parent dirs + * + * @param f The new Filesonly value + */ + public void setFilesonly( boolean f ) + { + doFilesonly = f; + } + + /** + * Sets whether we want to update the file (if it exists) or create a new + * one. + * + * @param c The new Update value + */ + public void setUpdate( boolean c ) + { + doUpdate = c; + } + + /** + * Sets behavior of the task when no files match. Possible values are: + * fail (throw an exception and halt the build); skip + * (do not create any archive, but issue a warning); create + * (make an archive with no entries). Default for zip tasks is skip + * ; for jar tasks, create. + * + * @param we The new Whenempty value + */ + public void setWhenempty( WhenEmpty we ) + { + emptyBehavior = we.getValue(); + } + + /** + * This is the name/location of where to create the .zip file. + * + * @param zipFile The new Zipfile value + * @deprecated Use setFile() instead + */ + public void setZipfile( File zipFile ) + { + log( "DEPRECATED - The zipfile attribute is deprecated. Use file attribute instead." ); + setFile( zipFile ); + } + + /** + * Are we updating an existing archive? + * + * @return The InUpdateMode value + */ + public boolean isInUpdateMode() + { + return doUpdate; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Adds a set of files (nested zipfileset attribute) that can be read from + * an archive and be given a prefix/fullpath. + * + * @param set The feature to be added to the Zipfileset attribute + */ + public void addZipfileset( ZipFileSet set ) + { + filesets.addElement( set ); + } + + public void execute() + throws BuildException + { + if( baseDir == null && filesets.size() == 0 && "zip".equals( archiveType ) ) + { + throw new BuildException( "basedir attribute must be set, or at least " + + "one fileset must be given!" ); + } + + if( zipFile == null ) + { + throw new BuildException( "You must specify the " + archiveType + " file to create!" ); + } + + // Renamed version of original file, if it exists + File renamedFile = null; + // Whether or not an actual update is required - + // we don't need to update if the original file doesn't exist + + addingNewFiles = true; + doUpdate = doUpdate && zipFile.exists(); + if( doUpdate ) + { + FileUtils fileUtils = FileUtils.newFileUtils(); + renamedFile = fileUtils.createTempFile( "zip", ".tmp", + fileUtils.getParentFile( zipFile ) ); + + try + { + if( !zipFile.renameTo( renamedFile ) ) + { + throw new BuildException( "Unable to rename old file to temporary file" ); + } + } + catch( SecurityException e ) + { + throw new BuildException( "Not allowed to rename old file to temporary file" ); + } + } + + // Create the scanners to pass to isUpToDate(). + Vector dss = new Vector(); + if( baseDir != null ) + { + dss.addElement( getDirectoryScanner( baseDir ) ); + } + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + dss.addElement( fs.getDirectoryScanner( project ) ); + } + int dssSize = dss.size(); + FileScanner[] scanners = new FileScanner[dssSize]; + dss.copyInto( scanners ); + + // quick exit if the target is up to date + // can also handle empty archives + if( isUpToDate( scanners, zipFile ) ) + { + return; + } + + String action = doUpdate ? "Updating " : "Building "; + + log( action + archiveType + ": " + zipFile.getAbsolutePath() ); + + boolean success = false; + try + { + ZipOutputStream zOut = + new ZipOutputStream( new FileOutputStream( zipFile ) ); + zOut.setEncoding( encoding ); + try + { + if( doCompress ) + { + zOut.setMethod( ZipOutputStream.DEFLATED ); + } + else + { + zOut.setMethod( ZipOutputStream.STORED ); + } + initZipOutputStream( zOut ); + + // Add the implicit fileset to the archive. + if( baseDir != null ) + { + addFiles( getDirectoryScanner( baseDir ), zOut, "", "" ); + } + // Add the explicit filesets to the archive. + addFiles( filesets, zOut ); + if( doUpdate ) + { + addingNewFiles = false; + ZipFileSet oldFiles = new ZipFileSet(); + oldFiles.setSrc( renamedFile ); + + StringBuffer exclusionPattern = new StringBuffer(); + for( int i = 0; i < addedFiles.size(); i++ ) + { + if( i != 0 ) + { + exclusionPattern.append( "," ); + } + exclusionPattern.append( ( String )addedFiles.elementAt( i ) ); + } + oldFiles.setExcludes( exclusionPattern.toString() ); + Vector tmp = new Vector(); + tmp.addElement( oldFiles ); + addFiles( tmp, zOut ); + } + finalizeZipOutputStream( zOut ); + success = true; + } + finally + { + // Close the output stream. + try + { + if( zOut != null ) + { + zOut.close(); + } + } + catch( IOException ex ) + { + // If we're in this finally clause because of an exception, we don't + // really care if there's an exception when closing the stream. E.g. if it + // throws "ZIP file must have at least one entry", because an exception happened + // before we added any files, then we must swallow this exception. Otherwise, + // the error that's reported will be the close() error, which is not the real + // cause of the problem. + if( success ) + throw ex; + } + } + } + catch( IOException ioe ) + { + String msg = "Problem creating " + archiveType + ": " + ioe.getMessage(); + + // delete a bogus ZIP file + if( !zipFile.delete() ) + { + msg += " (and the archive is probably corrupt but I could not delete it)"; + } + + if( doUpdate ) + { + if( !renamedFile.renameTo( zipFile ) ) + { + msg += " (and I couldn't rename the temporary file " + + renamedFile.getName() + " back)"; + } + } + + throw new BuildException( msg, ioe, location ); + } + finally + { + cleanUp(); + } + + // If we've been successful on an update, delete the temporary file + if( success && doUpdate ) + { + if( !renamedFile.delete() ) + { + log( "Warning: unable to delete temporary file " + + renamedFile.getName(), Project.MSG_WARN ); + } + } + } + + /** + * Indicates if the task is adding new files into the archive as opposed to + * copying back unchanged files from the backup copy + * + * @return The AddingNewFiles value + */ + protected boolean isAddingNewFiles() + { + return addingNewFiles; + } + + + /** + * Check whether the archive is up-to-date; and handle behavior for empty + * archives. + * + * @param scanners list of prepared scanners containing files to archive + * @param zipFile intended archive file (may or may not exist) + * @return true if nothing need be done (may have done something already); + * false if archive creation should proceed + * @exception BuildException if it likes + */ + protected boolean isUpToDate( FileScanner[] scanners, File zipFile ) + throws BuildException + { + String[][] fileNames = grabFileNames( scanners ); + File[] files = grabFiles( scanners, fileNames ); + if( files.length == 0 ) + { + if( emptyBehavior.equals( "skip" ) ) + { + log( "Warning: skipping " + archiveType + " archive " + zipFile + + " because no files were included.", Project.MSG_WARN ); + return true; + } + else if( emptyBehavior.equals( "fail" ) ) + { + throw new BuildException( "Cannot create " + archiveType + " archive " + zipFile + + ": no files were included.", location ); + } + else + { + // Create. + return createEmptyZip( zipFile ); + } + } + else + { + for( int i = 0; i < files.length; ++i ) + { + if( files[i].equals( zipFile ) ) + { + throw new BuildException( "A zip file cannot include itself", location ); + } + } + + if( !zipFile.exists() ) + return false; + + SourceFileScanner sfs = new SourceFileScanner( this ); + MergingMapper mm = new MergingMapper(); + mm.setTo( zipFile.getAbsolutePath() ); + for( int i = 0; i < scanners.length; i++ ) + { + if( sfs.restrict( fileNames[i], scanners[i].getBasedir(), null, + mm ).length > 0 ) + { + return false; + } + } + return true; + } + } + + /** + * Add all files of the given FileScanner to the ZipOutputStream prependig + * the given prefix to each filename.

              + * + * Ensure parent directories have been added as well. + * + * @param scanner The feature to be added to the Files attribute + * @param zOut The feature to be added to the Files attribute + * @param prefix The feature to be added to the Files attribute + * @param fullpath The feature to be added to the Files attribute + * @exception IOException Description of Exception + */ + protected void addFiles( FileScanner scanner, ZipOutputStream zOut, + String prefix, String fullpath ) + throws IOException + { + if( prefix.length() > 0 && fullpath.length() > 0 ) + throw new BuildException( "Both prefix and fullpath attributes may not be set on the same fileset." ); + + File thisBaseDir = scanner.getBasedir(); + + // directories that matched include patterns + String[] dirs = scanner.getIncludedDirectories(); + if( dirs.length > 0 && fullpath.length() > 0 ) + throw new BuildException( "fullpath attribute may only be specified for filesets that specify a single file." ); + for( int i = 0; i < dirs.length; i++ ) + { + if( "".equals( dirs[i] ) ) + { + continue; + } + String name = dirs[i].replace( File.separatorChar, '/' ); + if( !name.endsWith( "/" ) ) + { + name += "/"; + } + addParentDirs( thisBaseDir, name, zOut, prefix ); + } + + // files that matched include patterns + String[] files = scanner.getIncludedFiles(); + if( files.length > 1 && fullpath.length() > 0 ) + throw new BuildException( "fullpath attribute may only be specified for filesets that specify a single file." ); + for( int i = 0; i < files.length; i++ ) + { + File f = new File( thisBaseDir, files[i] ); + if( fullpath.length() > 0 ) + { + // Add this file at the specified location. + addParentDirs( null, fullpath, zOut, "" ); + zipFile( f, zOut, fullpath ); + } + else + { + // Add this file with the specified prefix. + String name = files[i].replace( File.separatorChar, '/' ); + addParentDirs( thisBaseDir, name, zOut, prefix ); + zipFile( f, zOut, prefix + name ); + } + } + } + + /** + * Iterate over the given Vector of (zip)filesets and add all files to the + * ZipOutputStream using the given prefix or fullpath. + * + * @param filesets The feature to be added to the Files attribute + * @param zOut The feature to be added to the Files attribute + * @exception IOException Description of Exception + */ + protected void addFiles( Vector filesets, ZipOutputStream zOut ) + throws IOException + { + // Add each fileset in the Vector. + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + + String prefix = ""; + String fullpath = ""; + if( fs instanceof ZipFileSet ) + { + ZipFileSet zfs = ( ZipFileSet )fs; + prefix = zfs.getPrefix(); + fullpath = zfs.getFullpath(); + } + + if( prefix.length() > 0 + && !prefix.endsWith( "/" ) + && !prefix.endsWith( "\\" ) ) + { + prefix += "/"; + } + + // Need to manually add either fullpath's parent directory, or + // the prefix directory, to the archive. + if( prefix.length() > 0 ) + { + addParentDirs( null, prefix, zOut, "" ); + zipDir( null, zOut, prefix ); + } + else if( fullpath.length() > 0 ) + { + addParentDirs( null, fullpath, zOut, "" ); + } + + if( fs instanceof ZipFileSet + && ( ( ZipFileSet )fs ).getSrc() != null ) + { + addZipEntries( ( ZipFileSet )fs, ds, zOut, prefix, fullpath ); + } + else + { + // Add the fileset. + addFiles( ds, zOut, prefix, fullpath ); + } + } + } + + /** + * Ensure all parent dirs of a given entry have been added. + * + * @param baseDir The feature to be added to the ParentDirs attribute + * @param entry The feature to be added to the ParentDirs attribute + * @param zOut The feature to be added to the ParentDirs attribute + * @param prefix The feature to be added to the ParentDirs attribute + * @exception IOException Description of Exception + */ + protected void addParentDirs( File baseDir, String entry, + ZipOutputStream zOut, String prefix ) + throws IOException + { + if( !doFilesonly ) + { + Stack directories = new Stack(); + int slashPos = entry.length(); + + while( ( slashPos = entry.lastIndexOf( ( int )'/', slashPos - 1 ) ) != -1 ) + { + String dir = entry.substring( 0, slashPos + 1 ); + if( addedDirs.get( prefix + dir ) != null ) + { + break; + } + directories.push( dir ); + } + + while( !directories.isEmpty() ) + { + String dir = ( String )directories.pop(); + File f = null; + if( baseDir != null ) + { + f = new File( baseDir, dir ); + } + else + { + f = new File( dir ); + } + zipDir( f, zOut, prefix + dir ); + } + } + } + + protected void addZipEntries( ZipFileSet fs, DirectoryScanner ds, + ZipOutputStream zOut, String prefix, String fullpath ) + throws IOException + { + if( prefix.length() > 0 && fullpath.length() > 0 ) + throw new BuildException( "Both prefix and fullpath attributes may not be set on the same fileset." ); + + ZipScanner zipScanner = ( ZipScanner )ds; + File zipSrc = fs.getSrc(); + + ZipEntry entry; + java.util.zip.ZipEntry origEntry; + ZipInputStream in = null; + try + { + in = new ZipInputStream( new FileInputStream( zipSrc ) ); + + while( ( origEntry = in.getNextEntry() ) != null ) + { + entry = new ZipEntry( origEntry ); + String vPath = entry.getName(); + if( zipScanner.match( vPath ) ) + { + if( fullpath.length() > 0 ) + { + addParentDirs( null, fullpath, zOut, "" ); + zipFile( in, zOut, fullpath, entry.getTime() ); + } + else + { + addParentDirs( null, vPath, zOut, prefix ); + if( !entry.isDirectory() ) + { + zipFile( in, zOut, prefix + vPath, entry.getTime() ); + } + } + } + } + } + finally + { + if( in != null ) + { + in.close(); + } + } + } + + /** + * Do any clean up necessary to allow this instance to be used again.

              + * + * When we get here, the Zip file has been closed and all we need to do is + * to reset some globals.

              + */ + protected void cleanUp() + { + addedDirs = new Hashtable(); + addedFiles = new Vector(); + filesets = new Vector(); + zipFile = null; + baseDir = null; + doCompress = true; + doUpdate = false; + doFilesonly = false; + addingNewFiles = false; + encoding = null; + } + + /** + * Create an empty zip file + * + * @param zipFile Description of Parameter + * @return true if the file is then considered up to date. + */ + protected boolean createEmptyZip( File zipFile ) + { + // In this case using java.util.zip will not work + // because it does not permit a zero-entry archive. + // Must create it manually. + log( "Note: creating empty " + archiveType + " archive " + zipFile, Project.MSG_INFO ); + try + { + OutputStream os = new FileOutputStream( zipFile ); + try + { + // Cf. PKZIP specification. + byte[] empty = new byte[22]; + empty[0] = 80;// P + empty[1] = 75;// K + empty[2] = 5; + empty[3] = 6; + // remainder zeros + os.write( empty ); + } + finally + { + os.close(); + } + } + catch( IOException ioe ) + { + throw new BuildException( "Could not create empty ZIP archive", ioe, location ); + } + return true; + } + + protected void finalizeZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException { } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException { } + + protected void zipDir( File dir, ZipOutputStream zOut, String vPath ) + throws IOException + { + if( addedDirs.get( vPath ) != null ) + { + // don't add directories we've already added. + // no warning if we try, it is harmless in and of itself + return; + } + addedDirs.put( vPath, vPath ); + + ZipEntry ze = new ZipEntry( vPath ); + if( dir != null && dir.exists() ) + { + ze.setTime( dir.lastModified() ); + } + else + { + ze.setTime( System.currentTimeMillis() ); + } + ze.setSize( 0 ); + ze.setMethod( ZipEntry.STORED ); + // This is faintly ridiculous: + ze.setCrc( EMPTY_CRC ); + + // this is 040775 | MS-DOS directory flag in reverse byte order + ze.setExternalAttributes( 0x41FD0010L ); + + zOut.putNextEntry( ze ); + } + + protected void zipFile( InputStream in, ZipOutputStream zOut, String vPath, + long lastModified ) + throws IOException + { + ZipEntry ze = new ZipEntry( vPath ); + ze.setTime( lastModified ); + + /* + * XXX ZipOutputStream.putEntry expects the ZipEntry to know its + * size and the CRC sum before you start writing the data when using + * STORED mode. + * + * This forces us to process the data twice. + * + * I couldn't find any documentation on this, just found out by try + * and error. + */ + if( !doCompress ) + { + long size = 0; + CRC32 cal = new CRC32(); + if( !in.markSupported() ) + { + // Store data into a byte[] + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + size += count; + cal.update( buffer, 0, count ); + bos.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + in = new ByteArrayInputStream( bos.toByteArray() ); + + } + else + { + in.mark( Integer.MAX_VALUE ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + size += count; + cal.update( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + in.reset(); + } + ze.setSize( size ); + ze.setCrc( cal.getValue() ); + } + + zOut.putNextEntry( ze ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + if( count != 0 ) + { + zOut.write( buffer, 0, count ); + } + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + addedFiles.addElement( vPath ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + if( file.equals( zipFile ) ) + { + throw new BuildException( "A zip file cannot include itself", location ); + } + + FileInputStream fIn = new FileInputStream( file ); + try + { + zipFile( fIn, zOut, vPath, file.lastModified() ); + } + finally + { + fIn.close(); + } + } + + + /** + * Possible behaviors when there are no matching files for the task. + * + * @author RT + */ + public static class WhenEmpty extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"fail", "skip", "create"}; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java new file mode 100644 index 000000000..f82338b01 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Javac; + +/** + * The interface that all compiler adapters must adher to.

              + * + * A compiler adapter is an adapter that interprets the javac's parameters in + * preperation to be passed off to the compier this adapter represents. As all + * the necessary values are stored in the Javac task itself, the only thing all + * adapters need is the javac task, the execute command and a parameterless + * constructor (for reflection).

              + * + * @author Jay Dickon Glanville + * jayglanville@home.com + */ + +public interface CompilerAdapter +{ + + /** + * Sets the compiler attributes, which are stored in the Javac task. + * + * @param attributes The new Javac value + */ + void setJavac( Javac attributes ); + + /** + * Executes the task. + * + * @return has the compilation been successful + * @exception BuildException Description of Exception + */ + boolean execute() + throws BuildException; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java new file mode 100644 index 000000000..5e0e91536 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Creates the necessary compiler adapter, given basic criteria. + * + * @author J D Glanville + */ +public class CompilerAdapterFactory +{ + + /** + * This is a singlton -- can't create instances!! + */ + private CompilerAdapterFactory() { } + + /** + * Based on the parameter passed in, this method creates the necessary + * factory desired. The current mapping for compiler names are as follows: + * + *
                + *
              • jikes = jikes compiler + *
              • classic, javac1.1, javac1.2 = the standard compiler from JDK + * 1.1/1.2 + *
              • modern, javac1.3 = the new compiler of JDK 1.3 + *
              • jvc, microsoft = the command line compiler from Microsoft's SDK + * for Java / Visual J++ + *
              • kjc = the kopi compiler
              • + *
              • gcj = the gcj compiler from gcc
              • + *
              • a fully quallified classname = the name of a compiler + * adapter + *
              + * + * + * @param compilerType either the name of the desired compiler, or the full + * classname of the compiler's adapter. + * @param task a task to log through. + * @return The Compiler value + * @throws BuildException if the compiler type could not be resolved into a + * compiler adapter. + */ + public static CompilerAdapter getCompiler( String compilerType, Task task ) + throws BuildException + { + /* + * If I've done things right, this should be the extent of the + * conditional statements required. + */ + if( compilerType.equalsIgnoreCase( "jikes" ) ) + { + return new Jikes(); + } + if( compilerType.equalsIgnoreCase( "extJavac" ) ) + { + return new JavacExternal(); + } + if( compilerType.equalsIgnoreCase( "classic" ) || + compilerType.equalsIgnoreCase( "javac1.1" ) || + compilerType.equalsIgnoreCase( "javac1.2" ) ) + { + return new Javac12(); + } + if( compilerType.equalsIgnoreCase( "modern" ) || + compilerType.equalsIgnoreCase( "javac1.3" ) || + compilerType.equalsIgnoreCase( "javac1.4" ) ) + { + // does the modern compiler exist? + try + { + Class.forName( "com.sun.tools.javac.Main" ); + } + catch( ClassNotFoundException cnfe ) + { + task.log( "Modern compiler is not available - using " + + "classic compiler", Project.MSG_WARN ); + return new Javac12(); + } + return new Javac13(); + } + if( compilerType.equalsIgnoreCase( "jvc" ) || + compilerType.equalsIgnoreCase( "microsoft" ) ) + { + return new Jvc(); + } + if( compilerType.equalsIgnoreCase( "kjc" ) ) + { + return new Kjc(); + } + if( compilerType.equalsIgnoreCase( "gcj" ) ) + { + return new Gcj(); + } + if( compilerType.equalsIgnoreCase( "sj" ) || + compilerType.equalsIgnoreCase( "symantec" ) ) + { + return new Sj(); + } + return resolveClassName( compilerType ); + } + + /** + * Tries to resolve the given classname into a compiler adapter. Throws a + * fit if it can't. + * + * @param className The fully qualified classname to be created. + * @return Description of the Returned Value + * @throws BuildException This is the fit that is thrown if className isn't + * an instance of CompilerAdapter. + */ + private static CompilerAdapter resolveClassName( String className ) + throws BuildException + { + try + { + Class c = Class.forName( className ); + Object o = c.newInstance(); + return ( CompilerAdapter )o; + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( className + " can\'t be found.", cnfe ); + } + catch( ClassCastException cce ) + { + throw new BuildException( className + " isn\'t the classname of " + + "a compiler adapter.", cce ); + } + catch( Throwable t ) + { + // for all other possibilities + throw new BuildException( className + " caused an interesting " + + "exception.", t ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java new file mode 100644 index 000000000..a4675b48d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java @@ -0,0 +1,494 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.Javac; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.FileUtils; + +/** + * This is the default implementation for the CompilerAdapter interface. + * Currently, this is a cut-and-paste of the original javac task. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public abstract class DefaultCompilerAdapter implements CompilerAdapter +{ + protected static String lSep = System.getProperty( "line.separator" ); + protected boolean debug = false; + protected boolean optimize = false; + protected boolean deprecation = false; + protected boolean depend = false; + protected boolean verbose = false; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + protected Javac attributes; + protected Path bootclasspath; + protected Path compileClasspath; + + protected File[] compileList; + protected File destDir; + protected String encoding; + protected Path extdirs; + protected boolean includeAntRuntime; + protected boolean includeJavaRuntime; + protected Location location; + protected String memoryInitialSize; + protected String memoryMaximumSize; + protected Project project; + + /* + * jdg - TODO - all these attributes are currently protected, but they + * should probably be private in the near future. + */ + protected Path src; + protected String target; + + public void setJavac( Javac attributes ) + { + this.attributes = attributes; + src = attributes.getSrcdir(); + destDir = attributes.getDestdir(); + encoding = attributes.getEncoding(); + debug = attributes.getDebug(); + optimize = attributes.getOptimize(); + deprecation = attributes.getDeprecation(); + depend = attributes.getDepend(); + verbose = attributes.getVerbose(); + target = attributes.getTarget(); + bootclasspath = attributes.getBootclasspath(); + extdirs = attributes.getExtdirs(); + compileList = attributes.getFileList(); + compileClasspath = attributes.getClasspath(); + project = attributes.getProject(); + location = attributes.getLocation(); + includeAntRuntime = attributes.getIncludeantruntime(); + includeJavaRuntime = attributes.getIncludejavaruntime(); + memoryInitialSize = attributes.getMemoryInitialSize(); + memoryMaximumSize = attributes.getMemoryMaximumSize(); + } + + public Javac getJavac() + { + return attributes; + } + + protected Commandline setupJavacCommand() + { + return setupJavacCommand( false ); + } + + /** + * Does the command line argument processing for classic and adds the files + * to compile as well. + * + * @param debugLevelCheck Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupJavacCommand( boolean debugLevelCheck ) + { + Commandline cmd = new Commandline(); + setupJavacCommandlineSwitches( cmd, debugLevelCheck ); + logAndAddFilesToCompile( cmd ); + return cmd; + } + + protected Commandline setupJavacCommandlineSwitches( Commandline cmd ) + { + return setupJavacCommandlineSwitches( cmd, false ); + } + + /** + * Does the command line argument processing common to classic and modern. + * Doesn't add the files to compile. + * + * @param cmd Description of Parameter + * @param useDebugLevel Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupJavacCommandlineSwitches( Commandline cmd, + boolean useDebugLevel ) + { + Path classpath = getCompileClasspath(); + + // we cannot be using Java 1.0 when forking, so we only have to + // distinguish between Java 1.1, and Java 1.2 and higher, as Java 1.1 + // has its own parameter format + boolean usingJava1_1 = Project.getJavaVersion().equals( Project.JAVA_1_1 ); + String memoryParameterPrefix = usingJava1_1 ? "-J-" : "-J-X"; + if( memoryInitialSize != null ) + { + if( !attributes.isForkedJavac() ) + { + attributes.log( "Since fork is false, ignoring memoryInitialSize setting.", + Project.MSG_WARN ); + } + else + { + cmd.createArgument().setValue( memoryParameterPrefix + "ms" + memoryInitialSize ); + } + } + + if( memoryMaximumSize != null ) + { + if( !attributes.isForkedJavac() ) + { + attributes.log( "Since fork is false, ignoring memoryMaximumSize setting.", + Project.MSG_WARN ); + } + else + { + cmd.createArgument().setValue( memoryParameterPrefix + "mx" + memoryMaximumSize ); + } + } + + if( attributes.getNowarn() ) + { + cmd.createArgument().setValue( "-nowarn" ); + } + + if( deprecation == true ) + { + cmd.createArgument().setValue( "-deprecation" ); + } + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + cmd.createArgument().setValue( "-classpath" ); + + // Just add "sourcepath" to classpath ( for JDK1.1 ) + // as well as "bootclasspath" and "extdirs" + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + Path cp = new Path( project ); + /* + * XXX - This doesn't mix very well with build.systemclasspath, + */ + if( bootclasspath != null ) + { + cp.append( bootclasspath ); + } + if( extdirs != null ) + { + cp.addExtdirs( extdirs ); + } + cp.append( classpath ); + cp.append( src ); + cmd.createArgument().setPath( cp ); + } + else + { + cmd.createArgument().setPath( classpath ); + cmd.createArgument().setValue( "-sourcepath" ); + cmd.createArgument().setPath( src ); + if( target != null ) + { + cmd.createArgument().setValue( "-target" ); + cmd.createArgument().setValue( target ); + } + if( bootclasspath != null ) + { + cmd.createArgument().setValue( "-bootclasspath" ); + cmd.createArgument().setPath( bootclasspath ); + } + if( extdirs != null ) + { + cmd.createArgument().setValue( "-extdirs" ); + cmd.createArgument().setPath( extdirs ); + } + } + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + if( debug ) + { + if( useDebugLevel + && Project.getJavaVersion() != Project.JAVA_1_0 + && Project.getJavaVersion() != Project.JAVA_1_1 ) + { + + String debugLevel = attributes.getDebugLevel(); + if( debugLevel != null ) + { + cmd.createArgument().setValue( "-g:" + debugLevel ); + } + else + { + cmd.createArgument().setValue( "-g" ); + } + } + else + { + cmd.createArgument().setValue( "-g" ); + } + } + else if( Project.getJavaVersion() != Project.JAVA_1_0 && + Project.getJavaVersion() != Project.JAVA_1_1 ) + { + cmd.createArgument().setValue( "-g:none" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + + if( depend ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + cmd.createArgument().setValue( "-depend" ); + } + else if( Project.getJavaVersion().startsWith( "1.2" ) ) + { + cmd.createArgument().setValue( "-Xdepend" ); + } + else + { + attributes.log( "depend attribute is not supported by the modern compiler", + Project.MSG_WARN ); + } + } + + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + return cmd; + } + + /** + * Does the command line argument processing for modern and adds the files + * to compile as well. + * + * @return Description of the Returned Value + */ + protected Commandline setupModernJavacCommand() + { + Commandline cmd = new Commandline(); + setupModernJavacCommandlineSwitches( cmd ); + + logAndAddFilesToCompile( cmd ); + return cmd; + } + + /** + * Does the command line argument processing for modern. Doesn't add the + * files to compile. + * + * @param cmd Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupModernJavacCommandlineSwitches( Commandline cmd ) + { + setupJavacCommandlineSwitches( cmd, true ); + if( attributes.getSource() != null ) + { + cmd.createArgument().setValue( "-source" ); + cmd.createArgument().setValue( attributes.getSource() ); + } + return cmd; + } + + /** + * Builds the compilation classpath. + * + * @return The CompileClasspath value + */ + protected Path getCompileClasspath() + { + Path classpath = new Path( project ); + + // add dest dir to classpath so that previously compiled and + // untouched classes are on classpath + + if( destDir != null ) + { + classpath.setLocation( destDir ); + } + + // Combine the build classpath with the system classpath, in an + // order determined by the value of build.classpath + + if( compileClasspath == null ) + { + if( includeAntRuntime ) + { + classpath.addExisting( Path.systemClasspath ); + } + } + else + { + if( includeAntRuntime ) + { + classpath.addExisting( compileClasspath.concatSystemClasspath( "last" ) ); + } + else + { + classpath.addExisting( compileClasspath.concatSystemClasspath( "ignore" ) ); + } + } + + if( includeJavaRuntime ) + { + classpath.addJavaRuntime(); + } + + return classpath; + } + + /** + * Adds the command line arguments specifc to the current implementation. + * + * @param cmd The feature to be added to the CurrentCompilerArgs attribute + */ + protected void addCurrentCompilerArgs( Commandline cmd ) + { + cmd.addArguments( getJavac().getCurrentCompilerArgs() ); + } + + /** + * Do the compile with the specified arguments. + * + * @param args - arguments to pass to process on command line + * @param firstFileName - index of the first source file in args + * @return Description of the Returned Value + */ + protected int executeExternalCompile( String[] args, int firstFileName ) + { + String[] commandArray = null; + File tmpFile = null; + + try + { + /* + * Many system have been reported to get into trouble with + * long command lines - no, not only Windows ;-). + * + * POSIX seems to define a lower limit of 4k, so use a temporary + * file if the total length of the command line exceeds this limit. + */ + if( Commandline.toString( args ).length() > 4096 ) + { + PrintWriter out = null; + try + { + tmpFile = fileUtils.createTempFile( "jikes", "", null ); + out = new PrintWriter( new FileWriter( tmpFile ) ); + for( int i = firstFileName; i < args.length; i++ ) + { + out.println( args[i] ); + } + out.flush(); + commandArray = new String[firstFileName + 1]; + System.arraycopy( args, 0, commandArray, 0, firstFileName ); + commandArray[firstFileName] = "@" + tmpFile.getAbsolutePath(); + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", e, location ); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( Throwable t ) + {} + } + } + } + else + { + commandArray = args; + } + + try + { + Execute exe = new Execute( new LogStreamHandler( attributes, + Project.MSG_INFO, + Project.MSG_WARN ) ); + exe.setAntRun( project ); + exe.setWorkingDirectory( project.getBaseDir() ); + exe.setCommandline( commandArray ); + exe.execute(); + return exe.getExitValue(); + } + catch( IOException e ) + { + throw new BuildException( "Error running " + args[0] + + " compiler", e, location ); + } + } + finally + { + if( tmpFile != null ) + { + tmpFile.delete(); + } + } + } + + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( Commandline cmd ) + { + attributes.log( "Compilation args: " + cmd.toString(), + Project.MSG_VERBOSE ); + + StringBuffer niceSourceList = new StringBuffer( "File" ); + if( compileList.length != 1 ) + { + niceSourceList.append( "s" ); + } + niceSourceList.append( " to be compiled:" ); + + niceSourceList.append( lSep ); + + for( int i = 0; i < compileList.length; i++ ) + { + String arg = compileList[i].getAbsolutePath(); + cmd.createArgument().setValue( arg ); + niceSourceList.append( " " + arg + lSep ); + } + + attributes.log( niceSourceList.toString(), Project.MSG_VERBOSE ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Gcj.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Gcj.java new file mode 100644 index 000000000..ee0918e10 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Gcj.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the gcj compiler. This is primarily a cut-and-paste + * from the jikes. + * + * @author Takashi Okamoto + */ +public class Gcj extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the gcj compiler. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author tora@debian.org + */ + public boolean execute() + throws BuildException + { + Commandline cmd; + attributes.log( "Using gcj compiler", Project.MSG_VERBOSE ); + cmd = setupGCJCommand(); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + + protected Commandline setupGCJCommand() + { + Commandline cmd = new Commandline(); + Path classpath = new Path( project ); + + // gcj doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // gcj doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + classpath.append( getCompileClasspath() ); + + // Gcj has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + cmd.setExecutable( "gcj" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + + if( destDir.mkdirs() ) + { + throw new BuildException( "Can't make output directories. Maybe permission is wrong. " ); + } + ; + } + + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + + if( encoding != null ) + { + cmd.createArgument().setValue( "--encoding=" + encoding ); + } + if( debug ) + { + cmd.createArgument().setValue( "-g1" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + + /** + * gcj should be set for generate class. + */ + cmd.createArgument().setValue( "-C" ); + + addCurrentCompilerArgs( cmd ); + + return cmd; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac12.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac12.java new file mode 100644 index 000000000..9eee2523a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac12.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the javac compiler for JDK 1.2 This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Javac12 extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using classic compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupJavacCommand( true ); + + OutputStream logstr = new LogOutputStream( attributes, Project.MSG_WARN ); + try + { + // Create an instance of the compiler, redirecting output to + // the project log + Class c = Class.forName( "sun.tools.javac.Main" ); + Constructor cons = c.getConstructor( new Class[]{OutputStream.class, String.class} ); + Object compiler = cons.newInstance( new Object[]{logstr, "javac"} ); + + // Call the compile() method + Method compile = c.getMethod( "compile", new Class[]{String[].class} ); + Boolean ok = ( Boolean )compile.invoke( compiler, new Object[]{cmd.getArguments()} ); + return ok.booleanValue(); + } + catch( ClassNotFoundException ex ) + { + throw new BuildException( "Cannot use classic compiler, as it is not available" + + " A common solution is to set the environment variable" + + " JAVA_HOME to your jdk directory.", location ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting classic compiler: ", ex, location ); + } + } + finally + { + try + { + logstr.close(); + } + catch( IOException e ) + { + // plain impossible + throw new BuildException( e ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac13.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac13.java new file mode 100644 index 000000000..7516d43c9 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac13.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + +/** + * The implementation of the javac compiler for JDK 1.3 This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Javac13 extends DefaultCompilerAdapter +{ + + /** + * Integer returned by the "Modern" jdk1.3 compiler to indicate success. + */ + private final static int MODERN_COMPILER_SUCCESS = 0; + + public boolean execute() + throws BuildException + { + attributes.log( "Using modern compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupModernJavacCommand(); + + // Use reflection to be able to build on all JDKs >= 1.1: + try + { + Class c = Class.forName( "com.sun.tools.javac.Main" ); + Object compiler = c.newInstance(); + Method compile = c.getMethod( "compile", + new Class[]{( new String[]{} ).getClass()} ); + int result = ( ( Integer )compile.invoke + ( compiler, new Object[]{cmd.getArguments()} ) ).intValue(); + return ( result == MODERN_COMPILER_SUCCESS ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting modern compiler", ex, location ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java new file mode 100644 index 000000000..528b7cd43 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * Performs a compile using javac externally. + * + * @author Brian Deitte + */ +public class JavacExternal extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the Javac externally. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using external javac compiler", Project.MSG_VERBOSE ); + + Commandline cmd = new Commandline(); + cmd.setExecutable( getJavac().getJavacExecutable() ); + setupModernJavacCommandlineSwitches( cmd ); + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jikes.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jikes.java new file mode 100644 index 000000000..a45f64c3c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jikes.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the jikes compiler. This is primarily a cut-and-paste + * from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Jikes extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the Jikes compiler from IBM.. Mostly of this + * code is identical to doClassicCompile() However, it does not support all + * options like bootclasspath, extdirs, deprecation and so on, because there + * is no option in jikes and I don't understand what they should do. It has + * been successfully tested with jikes >1.10 + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author skanthak@muehlheim.de + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using jikes compiler", Project.MSG_VERBOSE ); + + Path classpath = new Path( project ); + + // Jikes doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // Jikes doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + else + { + // there is a bootclasspath stated. By default, the + // includeJavaRuntime is false. If the user has stated a + // bootclasspath and said to include the java runtime, it's on + // their head! + } + classpath.append( getCompileClasspath() ); + + // Jikes has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + // if the user has set JIKESPATH we should add the contents as well + String jikesPath = System.getProperty( "jikes.class.path" ); + if( jikesPath != null ) + { + classpath.append( new Path( project, jikesPath ) ); + } + + Commandline cmd = new Commandline(); + cmd.setExecutable( "jikes" ); + + if( deprecation == true ) + cmd.createArgument().setValue( "-deprecation" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + if( debug ) + { + cmd.createArgument().setValue( "-g" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + if( depend ) + { + cmd.createArgument().setValue( "-depend" ); + } + /** + * XXX Perhaps we shouldn't use properties for these three options + * (emacs mode, warnings and pedantic), but include it in the javac + * directive? + */ + + /** + * Jikes has the nice feature to print error messages in a form readable + * by emacs, so that emacs can directly set the cursor to the place, + * where the error occured. + */ + String emacsProperty = project.getProperty( "build.compiler.emacs" ); + if( emacsProperty != null && Project.toBoolean( emacsProperty ) ) + { + cmd.createArgument().setValue( "+E" ); + } + + /** + * Jikes issues more warnings that javac, for example, when you have + * files in your classpath that don't exist. As this is often the case, + * these warning can be pretty annoying. + */ + String warningsProperty = project.getProperty( "build.compiler.warnings" ); + if( warningsProperty != null ) + { + attributes.log( "!! the build.compiler.warnings property is deprecated. !!", + Project.MSG_WARN ); + attributes.log( "!! Use the nowarn attribute instead. !!", + Project.MSG_WARN ); + if( !Project.toBoolean( warningsProperty ) ) + { + cmd.createArgument().setValue( "-nowarn" ); + } + } + if( attributes.getNowarn() ) + { + /* + * FIXME later + * + * let the magic property win over the attribute for backwards + * compatibility + */ + cmd.createArgument().setValue( "-nowarn" ); + } + + /** + * Jikes can issue pedantic warnings. + */ + String pedanticProperty = project.getProperty( "build.compiler.pedantic" ); + if( pedanticProperty != null && Project.toBoolean( pedanticProperty ) ) + { + cmd.createArgument().setValue( "+P" ); + } + + /** + * Jikes supports something it calls "full dependency checking", see the + * jikes documentation for differences between -depend and +F. + */ + String fullDependProperty = project.getProperty( "build.compiler.fulldepend" ); + if( fullDependProperty != null && Project.toBoolean( fullDependProperty ) ) + { + cmd.createArgument().setValue( "+F" ); + } + + addCurrentCompilerArgs( cmd ); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jvc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jvc.java new file mode 100644 index 000000000..ccb0b0f2a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jvc.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the jvc compiler from microsoft. This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Jvc extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using jvc compiler", Project.MSG_VERBOSE ); + + Path classpath = new Path( project ); + + // jvc doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // jvc doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + else + { + // there is a bootclasspath stated. By default, the + // includeJavaRuntime is false. If the user has stated a + // bootclasspath and said to include the java runtime, it's on + // their head! + } + classpath.append( getCompileClasspath() ); + + // jvc has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + Commandline cmd = new Commandline(); + cmd.setExecutable( "jvc" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "/d" ); + cmd.createArgument().setFile( destDir ); + } + + // Add the Classpath before the "internal" one. + cmd.createArgument().setValue( "/cp:p" ); + cmd.createArgument().setPath( classpath ); + + // Enable MS-Extensions and ... + cmd.createArgument().setValue( "/x-" ); + // ... do not display a Message about this. + cmd.createArgument().setValue( "/nomessage" ); + // Do not display Logo + cmd.createArgument().setValue( "/nologo" ); + + if( debug ) + { + cmd.createArgument().setValue( "/g" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "/O" ); + } + if( verbose ) + { + cmd.createArgument().setValue( "/verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Kjc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Kjc.java new file mode 100644 index 000000000..2cb2bdc48 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Kjc.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the Java compiler for KJC. This is primarily a + * cut-and-paste from Jikes.java and DefaultCompilerAdapter. + * + * @author Takashi Okamoto + + */ +public class Kjc extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using kjc compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupKjcCommand(); + + try + { + Class c = Class.forName( "at.dms.kjc.Main" ); + + // Call the compile() method + Method compile = c.getMethod( "compile", + new Class[]{String[].class} ); + Boolean ok = ( Boolean )compile.invoke( null, + new Object[]{cmd.getArguments()} ); + return ok.booleanValue(); + } + catch( ClassNotFoundException ex ) + { + throw new BuildException( "Cannot use kjc compiler, as it is not available" + + " A common solution is to set the environment variable" + + " CLASSPATH to your kjc archive (kjc.jar).", location ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting kjc compiler: ", ex, location ); + } + } + } + + /** + * setup kjc command arguments. + * + * @return Description of the Returned Value + */ + protected Commandline setupKjcCommand() + { + Commandline cmd = new Commandline(); + + // generate classpath, because kjc does't support sourcepath. + Path classpath = getCompileClasspath(); + + if( deprecation == true ) + { + cmd.createArgument().setValue( "-deprecation" ); + } + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + // generate the clsspath + cmd.createArgument().setValue( "-classpath" ); + + Path cp = new Path( project ); + + // kjc don't have bootclasspath option. + if( bootclasspath != null ) + { + cp.append( bootclasspath ); + } + + if( extdirs != null ) + { + cp.addExtdirs( extdirs ); + } + + cp.append( classpath ); + cp.append( src ); + + cmd.createArgument().setPath( cp ); + + // kjc-1.5A doesn't support -encoding option now. + // but it will be supported near the feature. + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + + if( debug ) + { + cmd.createArgument().setValue( "-g" ); + } + + if( optimize ) + { + cmd.createArgument().setValue( "-O2" ); + } + + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + logAndAddFilesToCompile( cmd ); + return cmd; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Sj.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Sj.java new file mode 100644 index 000000000..3ab63f48b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Sj.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the sj compiler. Uses the defaults for + * DefaultCompilerAdapter + * + * @author Don Ferguson + */ +public class Sj extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the sj compiler from Symantec. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author don@bea.com + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using symantec java compiler", Project.MSG_VERBOSE ); + + Commandline cmd = setupJavacCommand(); + cmd.setExecutable( "sj" ); + + int firstFileName = cmd.size() - compileList.length; + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/And.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/And.java new file mode 100644 index 000000000..44b9810ac --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/And.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; + +/** + * <and> condition container.

              + * + * Iterates over all conditions and returns false as soon as one evaluates to + * false.

              + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class And extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + Enumeration enum = getConditions(); + while( enum.hasMoreElements() ) + { + Condition c = ( Condition )enum.nextElement(); + if( !c.eval() ) + { + return false; + } + } + return true; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Condition.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Condition.java new file mode 100644 index 000000000..fc589867c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Condition.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; + +import org.apache.myrmidon.api.TaskException; + +/** + * Interface for conditions to use inside the <condition> task. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface Condition +{ + /** + * Is this condition true? + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean eval() + throws TaskException; +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/ConditionBase.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/ConditionBase.java new file mode 100644 index 000000000..a8a13820d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/ConditionBase.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import java.util.NoSuchElementException; +import java.util.Vector; +import org.apache.tools.ant.ProjectComponent; +import org.apache.tools.ant.taskdefs.Available; +import org.apache.tools.ant.taskdefs.Checksum; +import org.apache.tools.ant.taskdefs.UpToDate; +import org.apache.myrmidon.framework.Os; + +/** + * Baseclass for the <condition> task as well as several conditions - + * ensures that the types of conditions inside the task and the "container" + * conditions are in sync. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public abstract class ConditionBase extends ProjectComponent +{ + private Vector conditions = new Vector(); + + /** + * Add an <and> condition "container". + * + * @param a The feature to be added to the And attribute + * @since 1.1 + */ + public void addAnd( And a ) + { + conditions.addElement( a ); + } + + /** + * Add an <available> condition. + * + * @param a The feature to be added to the Available attribute + * @since 1.1 + */ + public void addAvailable( Available a ) + { + conditions.addElement( a ); + } + + /** + * Add an <checksum> condition. + * + * @param c The feature to be added to the Checksum attribute + * @since 1.4 + */ + public void addChecksum( Checksum c ) + { + conditions.addElement( c ); + } + + /** + * Add an <equals> condition. + * + * @param e The feature to be added to the Equals attribute + * @since 1.1 + */ + public void addEquals( Equals e ) + { + conditions.addElement( e ); + } + + /** + * Add an <http> condition. + * + * @param h The feature to be added to the Http attribute + * @since 1.7 + */ + public void addHttp( Http h ) + { + conditions.addElement( h ); + } + + /** + * Add an <isset> condition. + * + * @param i The feature to be added to the IsSet attribute + * @since 1.1 + */ + public void addIsSet( IsSet i ) + { + conditions.addElement( i ); + } + + /** + * Add an <not> condition "container". + * + * @param n The feature to be added to the Not attribute + * @since 1.1 + */ + public void addNot( Not n ) + { + conditions.addElement( n ); + } + + /** + * Add an <or> condition "container". + * + * @param o The feature to be added to the Or attribute + * @since 1.1 + */ + public void addOr( Or o ) + { + conditions.addElement( o ); + } + + /** + * Add an <os> condition. + * + * @param o The feature to be added to the Os attribute + * @since 1.1 + */ + public void addOs( Os o ) + { + conditions.addElement( o ); + } + + /** + * Add a <socket> condition. + * + * @param s The feature to be added to the Socket attribute + * @since 1.7 + */ + public void addSocket( Socket s ) + { + conditions.addElement( s ); + } + + /** + * Add an <uptodate> condition. + * + * @param u The feature to be added to the Uptodate attribute + * @since 1.1 + */ + public void addUptodate( UpToDate u ) + { + conditions.addElement( u ); + } + + /** + * Iterate through all conditions. + * + * @return The Conditions value + * @since 1.1 + */ + protected final Enumeration getConditions() + { + return new ConditionEnumeration(); + } + + /** + * Count the conditions. + * + * @return Description of the Returned Value + * @since 1.1 + */ + protected int countConditions() + { + return conditions.size(); + } + + /** + * Inner class that configures those conditions with a project instance that + * need it. + * + * @author RT + * @since 1.1 + */ + private class ConditionEnumeration implements Enumeration + { + private int currentElement = 0; + + public boolean hasMoreElements() + { + return countConditions() > currentElement; + } + + public Object nextElement() + throws NoSuchElementException + { + Object o = null; + try + { + o = conditions.elementAt( currentElement++ ); + } + catch( ArrayIndexOutOfBoundsException e ) + { + throw new NoSuchElementException(); + } + + if( o instanceof ProjectComponent ) + { + ( ( ProjectComponent )o ).setProject( getProject() ); + } + return o; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Equals.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Equals.java new file mode 100644 index 000000000..306f7928f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Equals.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; + +/** + * Simple String comparison condition. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Equals implements Condition +{ + + private String arg1, arg2; + + public void setArg1( String a1 ) + { + arg1 = a1; + } + + public void setArg2( String a2 ) + { + arg2 = a2; + } + + public boolean eval() + throws BuildException + { + if( arg1 == null || arg2 == null ) + { + throw new BuildException( "both arg1 and arg2 are required in equals" ); + } + return arg1.equals( arg2 ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Http.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Http.java new file mode 100644 index 000000000..b70aa228a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Http.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition to wait for a HTTP request to succeed. Its attribute(s) are: url - + * the URL of the request. + * + * @author Denis Hennessy + */ +public class Http extends ProjectComponent implements Condition +{ + String spec = null; + + public void setUrl( String url ) + { + spec = url; + } + + public boolean eval() + throws BuildException + { + if( spec == null ) + { + throw new BuildException( "No url specified in HTTP task" ); + } + log( "Checking for " + spec, Project.MSG_VERBOSE ); + try + { + URL url = new URL( spec ); + try + { + URLConnection conn = url.openConnection(); + if( conn instanceof HttpURLConnection ) + { + HttpURLConnection http = ( HttpURLConnection )conn; + int code = http.getResponseCode(); + log( "Result code for " + spec + " was " + code, Project.MSG_VERBOSE ); + if( code > 0 && code < 500 ) + { + return true; + } + else + { + return false; + } + } + } + catch( java.io.IOException e ) + { + return false; + } + } + catch( MalformedURLException e ) + { + throw new BuildException( "Badly formed URL: " + spec, e ); + } + return true; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/IsSet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/IsSet.java new file mode 100644 index 000000000..e7a6a11be --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/IsSet.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition that tests whether a given property has been set. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class IsSet extends ProjectComponent implements Condition +{ + private String property; + + public void setProperty( String p ) + { + property = p; + } + + public boolean eval() + throws BuildException + { + if( property == null ) + { + throw new BuildException( "No property specified for isset condition" ); + } + + return getProject().getProperty( property ) != null; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Not.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Not.java new file mode 100644 index 000000000..1af3b0bdb --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Not.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; + +/** + * <not> condition. Evaluates to true if the single condition nested into + * it is false and vice versa. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Not extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + return !( ( Condition )getConditions().nextElement() ).eval(); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Or.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Or.java new file mode 100644 index 000000000..62ca4641c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Or.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; + +/** + * <or> condition container.

              + * + * Iterates over all conditions and returns true as soon as one evaluates to + * true.

              + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Or extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + Enumeration enum = getConditions(); + while( enum.hasMoreElements() ) + { + Condition c = ( Condition )enum.nextElement(); + if( c.eval() ) + { + return true; + } + } + return false; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Socket.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Socket.java new file mode 100644 index 000000000..b1e47526a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Socket.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition to wait for a TCP/IP socket to have a listener. Its attribute(s) + * are: server - the name of the server. port - the port number of the socket. + * + * @author Denis Hennessy + */ +public class Socket extends ProjectComponent implements Condition +{ + String server = null; + int port = 0; + + public void setPort( int port ) + { + this.port = port; + } + + public void setServer( String server ) + { + this.server = server; + } + + public boolean eval() + throws BuildException + { + if( server == null ) + { + throw new BuildException( "No server specified in Socket task" ); + } + if( port == 0 ) + { + throw new BuildException( "No port specified in Socket task" ); + } + log( "Checking for listener at " + server + ":" + port, Project.MSG_VERBOSE ); + try + { + java.net.Socket socket = new java.net.Socket( server, port ); + } + catch( IOException e ) + { + return false; + } + return true; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/defaults.properties b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/defaults.properties new file mode 100644 index 000000000..c680c8f92 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/defaults.properties @@ -0,0 +1,141 @@ +# standard ant tasks +mkdir=org.apache.tools.ant.taskdefs.Mkdir +javac=org.apache.tools.ant.taskdefs.Javac +chmod=org.apache.tools.ant.taskdefs.Chmod +delete=org.apache.tools.ant.taskdefs.Delete +copy=org.apache.tools.ant.taskdefs.Copy +move=org.apache.tools.ant.taskdefs.Move +jar=org.apache.tools.ant.taskdefs.Jar +rmic=org.apache.tools.ant.taskdefs.Rmic +cvs=org.apache.tools.ant.taskdefs.Cvs +get=org.apache.tools.ant.taskdefs.Get +unzip=org.apache.tools.ant.taskdefs.Expand +unjar=org.apache.tools.ant.taskdefs.Expand +unwar=org.apache.tools.ant.taskdefs.Expand +echo=org.apache.tools.ant.taskdefs.Echo +javadoc=org.apache.tools.ant.taskdefs.Javadoc +zip=org.apache.tools.ant.taskdefs.Zip +gzip=org.apache.tools.ant.taskdefs.GZip +gunzip=org.apache.tools.ant.taskdefs.GUnzip +replace=org.apache.tools.ant.taskdefs.Replace +java=org.apache.tools.ant.taskdefs.Java +tstamp=org.apache.tools.ant.taskdefs.Tstamp +property=org.apache.tools.ant.taskdefs.Property +taskdef=org.apache.tools.ant.taskdefs.Taskdef +ant=org.apache.tools.ant.taskdefs.Ant +exec=org.apache.tools.ant.taskdefs.ExecTask +tar=org.apache.tools.ant.taskdefs.Tar +untar=org.apache.tools.ant.taskdefs.Untar +available=org.apache.tools.ant.taskdefs.Available +filter=org.apache.tools.ant.taskdefs.Filter +fixcrlf=org.apache.tools.ant.taskdefs.FixCRLF +patch=org.apache.tools.ant.taskdefs.Patch +style=org.apache.tools.ant.taskdefs.XSLTProcess +touch=org.apache.tools.ant.taskdefs.Touch +signjar=org.apache.tools.ant.taskdefs.SignJar +genkey=org.apache.tools.ant.taskdefs.GenerateKey +antstructure=org.apache.tools.ant.taskdefs.AntStructure +execon=org.apache.tools.ant.taskdefs.ExecuteOn +antcall=org.apache.tools.ant.taskdefs.CallTarget +sql=org.apache.tools.ant.taskdefs.SQLExec +mail=org.apache.tools.ant.taskdefs.SendEmail +fail=org.apache.tools.ant.taskdefs.Exit +war=org.apache.tools.ant.taskdefs.War +uptodate=org.apache.tools.ant.taskdefs.UpToDate +apply=org.apache.tools.ant.taskdefs.Transform +record=org.apache.tools.ant.taskdefs.Recorder +cvspass=org.apache.tools.ant.taskdefs.CVSPass +typedef=org.apache.tools.ant.taskdefs.Typedef +sleep=org.apache.tools.ant.taskdefs.Sleep +pathconvert=org.apache.tools.ant.taskdefs.PathConvert +ear=org.apache.tools.ant.taskdefs.Ear +parallel=org.apache.tools.ant.taskdefs.Parallel +sequential=org.apache.tools.ant.taskdefs.Sequential +condition=org.apache.tools.ant.taskdefs.ConditionTask +dependset=org.apache.tools.ant.taskdefs.DependSet +bzip2=org.apache.tools.ant.taskdefs.BZip2 +bunzip2=org.apache.tools.ant.taskdefs.BUnzip2 +checksum=org.apache.tools.ant.taskdefs.Checksum +waitfor=org.apache.tools.ant.taskdefs.WaitFor +input=org.apache.tools.ant.taskdefs.Input +manifest=org.apache.tools.ant.taskdefs.Manifest + +# optional tasks +script=org.apache.tools.ant.taskdefs.optional.Script +netrexxc=org.apache.tools.ant.taskdefs.optional.NetRexxC +renameext=org.apache.tools.ant.taskdefs.optional.RenameExtensions +ejbc=org.apache.tools.ant.taskdefs.optional.ejb.Ejbc +ddcreator=org.apache.tools.ant.taskdefs.optional.ejb.DDCreator +wlrun=org.apache.tools.ant.taskdefs.optional.ejb.WLRun +wlstop=org.apache.tools.ant.taskdefs.optional.ejb.WLStop +vssget=org.apache.tools.ant.taskdefs.optional.vss.MSVSSGET +ejbjar=org.apache.tools.ant.taskdefs.optional.ejb.EjbJar +mparse=org.apache.tools.ant.taskdefs.optional.metamata.MParse +mmetrics=org.apache.tools.ant.taskdefs.optional.metamata.MMetrics +maudit=org.apache.tools.ant.taskdefs.optional.metamata.MAudit +junit=org.apache.tools.ant.taskdefs.optional.junit.JUnitTask +cab=org.apache.tools.ant.taskdefs.optional.Cab +ftp=org.apache.tools.ant.taskdefs.optional.net.FTP +icontract=org.apache.tools.ant.taskdefs.optional.IContract +javacc=org.apache.tools.ant.taskdefs.optional.javacc.JavaCC +jjtree=org.apache.tools.ant.taskdefs.optional.javacc.JJTree +starteam=org.apache.tools.ant.taskdefs.optional.scm.AntStarTeamCheckOut +wljspc=org.apache.tools.ant.taskdefs.optional.jsp.WLJspc +jlink=org.apache.tools.ant.taskdefs.optional.jlink.JlinkTask +native2ascii=org.apache.tools.ant.taskdefs.optional.Native2Ascii +propertyfile=org.apache.tools.ant.taskdefs.optional.PropertyFile +depend=org.apache.tools.ant.taskdefs.optional.depend.Depend +antlr=org.apache.tools.ant.taskdefs.optional.ANTLR +vajload=org.apache.tools.ant.taskdefs.optional.ide.VAJLoadProjects +vajexport=org.apache.tools.ant.taskdefs.optional.ide.VAJExport +vajimport=org.apache.tools.ant.taskdefs.optional.ide.VAJImport +telnet=org.apache.tools.ant.taskdefs.optional.net.TelnetTask +csc=org.apache.tools.ant.taskdefs.optional.dotnet.CSharp +ilasm=org.apache.tools.ant.taskdefs.optional.dotnet.Ilasm +stylebook=org.apache.tools.ant.taskdefs.optional.StyleBook +test=org.apache.tools.ant.taskdefs.optional.Test +pvcs=org.apache.tools.ant.taskdefs.optional.pvcs.Pvcs +p4change=org.apache.tools.ant.taskdefs.optional.perforce.P4Change +p4label=org.apache.tools.ant.taskdefs.optional.perforce.P4Label +p4have=org.apache.tools.ant.taskdefs.optional.perforce.P4Have +p4sync=org.apache.tools.ant.taskdefs.optional.perforce.P4Sync +p4edit=org.apache.tools.ant.taskdefs.optional.perforce.P4Edit +p4submit=org.apache.tools.ant.taskdefs.optional.perforce.P4Submit +p4counter=org.apache.tools.ant.taskdefs.optional.perforce.P4Counter +javah=org.apache.tools.ant.taskdefs.optional.Javah +ccupdate=org.apache.tools.ant.taskdefs.optional.clearcase.CCUpdate +cccheckout=org.apache.tools.ant.taskdefs.optional.clearcase.CCCheckout +cccheckin=org.apache.tools.ant.taskdefs.optional.clearcase.CCCheckin +ccuncheckout=org.apache.tools.ant.taskdefs.optional.clearcase.CCUnCheckout +sound=org.apache.tools.ant.taskdefs.optional.sound.SoundTask +junitreport=org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator +vsslabel=org.apache.tools.ant.taskdefs.optional.vss.MSVSSLABEL +vsshistory=org.apache.tools.ant.taskdefs.optional.vss.MSVSSHISTORY +blgenclient=org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient +rpm=org.apache.tools.ant.taskdefs.optional.Rpm +xmlvalidate=org.apache.tools.ant.taskdefs.optional.XMLValidateTask +vsscheckin=org.apache.tools.ant.taskdefs.optional.vss.MSVSSCHECKIN +vsscheckout=org.apache.tools.ant.taskdefs.optional.vss.MSVSSCHECKOUT +iplanet-ejbc=org.apache.tools.ant.taskdefs.optional.ejb.IPlanetEjbcTask +jdepend=org.apache.tools.ant.taskdefs.optional.jdepend.JDependTask +mimemail=org.apache.tools.ant.taskdefs.optional.net.MimeMail +ccmcheckin=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckin +ccmcheckout=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckout +ccmcheckintask=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckinDefault +ccmreconfigure=org.apache.tools.ant.taskdefs.optional.ccm.CCMReconfigure +ccmcreatetask=org.apache.tools.ant.taskdefs.optional.ccm.CCMCreateTask +jpcoverage=org.apache.tools.ant.taskdefs.optional.sitraka.Coverage +jpcovmerge=org.apache.tools.ant.taskdefs.optional.sitraka.CovMerge +jpcovreport=org.apache.tools.ant.taskdefs.optional.sitraka.CovReport +p4add=org.apache.tools.ant.taskdefs.optional.perforce.P4Add +jspc=org.apache.tools.ant.taskdefs.optional.jsp.JspC +replaceregexp=org.apache.tools.ant.taskdefs.optional.ReplaceRegExp +translate=org.apache.tools.ant.taskdefs.optional.i18n.Translate + +# deprecated ant tasks (kept for back compatibility) +javadoc2=org.apache.tools.ant.taskdefs.Javadoc +#compileTask=org.apache.tools.ant.taskdefs.CompileTask +copydir=org.apache.tools.ant.taskdefs.Copydir +copyfile=org.apache.tools.ant.taskdefs.Copyfile +deltree=org.apache.tools.ant.taskdefs.Deltree +rename=org.apache.tools.ant.taskdefs.Rename diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ANTLR.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ANTLR.java new file mode 100644 index 000000000..7269bf2b2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ANTLR.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.URL; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ExitException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteJava; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; + +/** + * ANTLR task. + * + * @author Erik Meade + * @author <classpath> allows classpath to be set because a + * directory might be given for Antlr debug... + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return commandline.createClasspath( project ).createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg() + { + return commandline.createVmArgument(); + } + + public void execute() + throws BuildException + { + validateAttributes(); + + //TODO: use ANTLR to parse the grammer file to do this. + if( target.lastModified() > getGeneratedFile().lastModified() ) + { + commandline.createArgument().setValue( "-o" ); + commandline.createArgument().setValue( outputDirectory.toString() ); + commandline.createArgument().setValue( target.toString() ); + + if( fork ) + { + log( "Forking " + commandline.toString(), Project.MSG_VERBOSE ); + int err = run( commandline.getCommandline() ); + if( err == 1 ) + { + throw new BuildException( "ANTLR returned: " + err, location ); + } + } + else + { + ExecuteJava exe = new ExecuteJava(); + exe.setJavaCommand( commandline.getJavaCommand() ); + exe.setClasspath( commandline.getClasspath() ); + try + { + exe.execute( project ); + } + catch( ExitException e ) + { + if( e.getStatus() != 0 ) + { + throw new BuildException( "ANTLR returned: " + e.getStatus(), location ); + } + } + } + } + } + + /** + * Adds the jars or directories containing Antlr this should make the forked + * JVM work without having to specify it directly. + * + * @exception BuildException Description of Exception + */ + public void init() + throws BuildException + { + addClasspathEntry( "/antlr/Tool.class" ); + } + + /** + * Search for the given resource and add the directory or archive that + * contains it to the classpath.

              + * + * Doesn't work for archives in JDK 1.1 as the URL returned by getResource + * doesn't contain the name of the archive.

              + * + * @param resource The feature to be added to the ClasspathEntry attribute + */ + protected void addClasspathEntry( String resource ) + { + URL url = getClass().getResource( resource ); + if( url != null ) + { + String u = url.toString(); + if( u.startsWith( "jar:file:" ) ) + { + int pling = u.indexOf( "!" ); + String jarName = u.substring( 9, pling ); + log( "Implicitly adding " + jarName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( jarName ) ).getAbsolutePath() ) ); + } + else if( u.startsWith( "file:" ) ) + { + int tail = u.indexOf( resource ); + String dirName = u.substring( 5, tail ); + log( "Implicitly adding " + dirName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( dirName ) ).getAbsolutePath() ) ); + } + else + { + log( "Don\'t know how to handle resource URL " + u, + Project.MSG_DEBUG ); + } + } + else + { + log( "Couldn\'t find " + resource, Project.MSG_DEBUG ); + } + } + + private File getGeneratedFile() + throws BuildException + { + String generatedFileName = null; + try + { + BufferedReader in = new BufferedReader( new FileReader( target ) ); + String line; + while( ( line = in.readLine() ) != null ) + { + int extendsIndex = line.indexOf( " extends " ); + if( line.startsWith( "class " ) && extendsIndex > -1 ) + { + generatedFileName = line.substring( 6, extendsIndex ).trim(); + break; + } + } + in.close(); + } + catch( Exception e ) + { + throw new BuildException( "Unable to determine generated class", e ); + } + if( generatedFileName == null ) + { + throw new BuildException( "Unable to determine generated class" ); + } + return new File( outputDirectory, generatedFileName + ".java" ); + } + + /** + * execute in a forked VM + * + * @param command Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int run( String[] command ) + throws BuildException + { + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), null ); + exe.setAntRun( project ); + if( workingdir != null ) + { + exe.setWorkingDirectory( workingdir ); + } + exe.setCommandline( command ); + try + { + return exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + private void validateAttributes() + throws BuildException + { + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // if no output directory is specified, used the target's directory + if( outputDirectory == null ) + { + String fileName = target.toString(); + setOutputdirectory( new File( target.getParent() ) ); + } + if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "Invalid output directory: " + outputDirectory ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java new file mode 100644 index 000000000..53d6d80ed --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.exolab.adaptx.xslt.XSLTProcessor; +import org.exolab.adaptx.xslt.XSLTReader; +import org.exolab.adaptx.xslt.XSLTStylesheet; + +/** + * @author
              Arnaud Blandin + * @version $Revision$ $Date$ + */ +public class AdaptxLiaison implements XSLTLiaison +{ + + protected XSLTProcessor processor; + protected XSLTStylesheet xslSheet; + + public AdaptxLiaison() + { + processor = new XSLTProcessor(); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File fileName ) + throws Exception + { + XSLTReader xslReader = new XSLTReader(); + xslSheet = xslReader.read( fileName.getAbsolutePath() ); + } + + public void addParam( String name, String expression ) + { + processor.setProperty( name, expression ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileOutputStream fos = new FileOutputStream( outfile ); + OutputStreamWriter out = new OutputStreamWriter( fos, "UTF8" ); + processor.process( infile.getAbsolutePath(), xslSheet, out ); + } + +}//-- AdaptxLiaison diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Cab.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Cab.java new file mode 100644 index 000000000..65510cf56 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Cab.java @@ -0,0 +1,349 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.FileUtils; + + +/** + * Create a CAB archive. + * + * @author Roger Vaughn + * rvaughn@seaconinc.com + */ + +public class Cab extends MatchingTask +{ + private Vector filesets = new Vector(); + private boolean doCompress = true; + private boolean doVerbose = false; + + protected String archiveType = "cab"; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + private File baseDir; + + private File cabFile; + private String cmdOptions; + + /** + * This is the base directory to look in for things to cab. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * This is the name/location of where to create the .cab file. + * + * @param cabFile The new Cabfile value + */ + public void setCabfile( File cabFile ) + { + this.cabFile = cabFile; + } + + /** + * Sets whether we want to compress the files or only store them. + * + * @param compress The new Compress value + */ + public void setCompress( boolean compress ) + { + doCompress = compress; + } + + /** + * Sets additional cabarc options that aren't supported directly. + * + * @param options The new Options value + */ + public void setOptions( String options ) + { + cmdOptions = options; + } + + /** + * Sets whether we want to see or suppress cabarc output. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + doVerbose = verbose; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + public void execute() + throws BuildException + { + + checkConfiguration(); + + Vector files = getFileList(); + + // quick exit if the target is up to date + if( isUpToDate( files ) ) + return; + + log( "Building " + archiveType + ": " + cabFile.getAbsolutePath() ); + + if( !Os.isFamily( "windows" ) ) + { + log( "Using listcab/libcabinet", Project.MSG_VERBOSE ); + + StringBuffer sb = new StringBuffer(); + + Enumeration fileEnum = files.elements(); + + while( fileEnum.hasMoreElements() ) + { + sb.append( fileEnum.nextElement() ).append( "\n" ); + } + sb.append( "\n" ).append( cabFile.getAbsolutePath() ).append( "\n" ); + + try + { + Process p = Runtime.getRuntime().exec( "listcab" ); + OutputStream out = p.getOutputStream(); + out.write( sb.toString().getBytes() ); + out.flush(); + out.close(); + } + catch( IOException ex ) + { + String msg = "Problem creating " + cabFile + " " + ex.getMessage(); + throw new BuildException( msg ); + } + } + else + { + try + { + File listFile = createListFile( files ); + ExecTask exec = createExec(); + File outFile = null; + + // die if cabarc fails + exec.setFailonerror( true ); + exec.setDir( baseDir ); + + if( !doVerbose ) + { + outFile = fileUtils.createTempFile( "ant", "", null ); + exec.setOutput( outFile ); + } + + exec.setCommand( createCommand( listFile ) ); + exec.execute(); + + if( outFile != null ) + { + outFile.delete(); + } + + listFile.delete(); + } + catch( IOException ioe ) + { + String msg = "Problem creating " + cabFile + " " + ioe.getMessage(); + throw new BuildException( msg ); + } + } + } + + /** + * Get the complete list of files to be included in the cab. Filenames are + * gathered from filesets if any have been added, otherwise from the + * traditional include parameters. + * + * @return The FileList value + * @exception BuildException Description of Exception + */ + protected Vector getFileList() + throws BuildException + { + Vector files = new Vector(); + + if( filesets.size() == 0 ) + { + // get files from old methods - includes and nested include + appendFiles( files, super.getDirectoryScanner( baseDir ) ); + } + else + { + // get files from filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + if( fs != null ) + { + appendFiles( files, fs.getDirectoryScanner( project ) ); + } + } + } + + return files; + } + + /** + * Check to see if the target is up to date with respect to input files. + * + * @param files Description of Parameter + * @return true if the cab file is newer than its dependents. + */ + protected boolean isUpToDate( Vector files ) + { + boolean upToDate = true; + for( int i = 0; i < files.size() && upToDate; i++ ) + { + String file = files.elementAt( i ).toString(); + if( new File( baseDir, file ).lastModified() > + cabFile.lastModified() ) + upToDate = false; + } + return upToDate; + } + + /** + * Append all files found by a directory scanner to a vector. + * + * @param files Description of Parameter + * @param ds Description of Parameter + */ + protected void appendFiles( Vector files, DirectoryScanner ds ) + { + String[] dsfiles = ds.getIncludedFiles(); + + for( int i = 0; i < dsfiles.length; i++ ) + { + files.addElement( dsfiles[i] ); + } + } + + /* + * I'm not fond of this pattern: "sub-method expected to throw + * task-cancelling exceptions". It feels too much like programming + * for side-effects to me... + */ + protected void checkConfiguration() + throws BuildException + { + if( baseDir == null ) + { + throw new BuildException( "basedir attribute must be set!" ); + } + if( !baseDir.exists() ) + { + throw new BuildException( "basedir does not exist!" ); + } + if( cabFile == null ) + { + throw new BuildException( "cabfile attribute must be set!" ); + } + } + + /** + * Create the cabarc command line to use. + * + * @param listFile Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline createCommand( File listFile ) + { + Commandline command = new Commandline(); + command.setExecutable( "cabarc" ); + command.createArgument().setValue( "-r" ); + command.createArgument().setValue( "-p" ); + + if( !doCompress ) + { + command.createArgument().setValue( "-m" ); + command.createArgument().setValue( "none" ); + } + + if( cmdOptions != null ) + { + command.createArgument().setLine( cmdOptions ); + } + + command.createArgument().setValue( "n" ); + command.createArgument().setFile( cabFile ); + command.createArgument().setValue( "@" + listFile.getAbsolutePath() ); + + return command; + } + + /** + * Create a new exec delegate. The delegate task is populated so that it + * appears in the logs to be the same task as this one. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecTask createExec() + throws BuildException + { + ExecTask exec = ( ExecTask )project.createTask( "exec" ); + exec.setOwningTarget( this.getOwningTarget() ); + exec.setTaskName( this.getTaskName() ); + exec.setDescription( this.getDescription() ); + + return exec; + } + + /** + * Creates a list file. This temporary file contains a list of all files to + * be included in the cab, one file per line. + * + * @param files Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + protected File createListFile( Vector files ) + throws IOException + { + File listFile = fileUtils.createTempFile( "ant", "", null ); + + PrintWriter writer = new PrintWriter( new FileOutputStream( listFile ) ); + + for( int i = 0; i < files.size(); i++ ) + { + writer.println( files.elementAt( i ).toString() ); + } + writer.close(); + + return listFile; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/IContract.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/IContract.java new file mode 100644 index 000000000..cc52a4708 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/IContract.java @@ -0,0 +1,1105 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Date; +import java.util.Properties; +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.taskdefs.Java; +import org.apache.tools.ant.taskdefs.Javac; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.Mkdir; +import org.apache.tools.ant.taskdefs.compilers.DefaultCompilerAdapter; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Instruments Java classes with + * iContract DBC preprocessor.
              + * The task can generate a properties file for iControl , a graphical + * user interface that lets you turn on/off assertions. iControl generates a + * control file that you can refer to from this task using the controlfile + * attribute.

              + * + * Thanks to Rainer Schmitz for enhancements and comments. + * + * @author Aslak Hellesøy

              + * + * + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * srcdir + * + * + * + * Location of the java files. + * + * + * + * Yes + * + * + * + * + * + * + * + * instrumentdir + * + * + * + * Indicates where the instrumented source files should go. + * + * + * + * Yes + * + * + * + * + * + * + * + * repositorydir + * + * + * + * Indicates where the repository source files should go. + * + * + * + * Yes + * + * + * + * + * + * + * + * builddir + * + * + * + * Indicates where the compiled instrumented classes should go. + * Defaults to the value of instrumentdir.

              NOTE: Don't + * use the same directory for compiled instrumented classes and + * uninstrumented classes. It will break the dependency checking. + * (Classes will not be reinstrumented if you change them). + * + * + * + * No + * + * + * + * + * + * + * + * repositorybuilddir + * + * + * + * Indicates where the compiled repository classes should go. + * Defaults to the value of repositorydir. + * + * + * + * No + * + * + * + * + * + * + * + * pre + * + * + * + * Indicates whether or not to instrument for preconditions. Defaults + * to true unless controlfile is specified, in which + * case it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * post + * + * + * + * Indicates whether or not to instrument for postconditions. + * Defaults to true unless controlfile is specified, in + * which case it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * invariant + * + * + * + * Indicates whether or not to instrument for invariants. Defaults to + * true unless controlfile is specified, in which case + * it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * failthrowable + * + * + * + * The full name of the Throwable (Exception) that should be thrown + * when an assertion is violated. Defaults to java.lang.Error + * + * + * + * + * No + * + * + * + * + * + * + * + * verbosity + * + * + * + * Indicates the verbosity level of iContract. Any combination of + * error*,warning*,note*,info*,progress*,debug* (comma + * separated) can be used. Defaults to error* + * + * + * + * No + * + * + * + * + * + * + * + * quiet + * + * + * + * Indicates if iContract should be quiet. Turn it off if many your + * classes extend uninstrumented classes and you don't want warnings + * about this. Defaults to false + * + * + * + * No + * + * + * + * + * + * + * + * updateicontrol + * + * + * + * If set to true, it indicates that the properties file for iControl + * in the current directory should be updated (or created if it + * doesn't exist). Defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * controlfile + * + * + * + * The name of the control file to pass to iContract. Consider using + * iControl to generate the file. Default is not to pass a file. + * + * + * + * + * Only if updateicontrol=true + * + * + * + * + * + * + * + * classdir + * + * + * + * Indicates where compiled (unistrumented) classes are located. This + * is required in order to properly update the icontrol.properties + * file, not for instrumentation. + * + * + * + * Only if updateicontrol=true + * + * + * + * + * + * + * + * targets + * + * + * + * Name of the file that will be generated by this task, which lists + * all the classes that iContract will instrument. If specified, the + * file will not be deleted after execution. If not specified, a file + * will still be created, but it will be deleted after execution. + * + * + * + * + * No + * + * + * + * + * + *

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

              + * + * Nested includes and excludes are also supported.

              + * + * Example:

              + * <icontract
              + *    srcdir="${build.src}"
              + *    instrumentdir="${build.instrument}"
              + *    repositorydir="${build.repository}"
              + *    builddir="${build.instrclasses}"
              + *    updateicontrol="true"
              + *    classdir="${build.classes}"
              + *    controlfile="control"
              + *    targets="targets"
              + *    verbosity="error*,warning*"
              + *    quiet="true"
              + * >
              + *    <classpath refid="compile-classpath"/>
              + * </icontract>
              + * 
              + */ +public class IContract extends MatchingTask +{ + + private final static String ICONTROL_PROPERTIES_HEADER = + " You might want to set classRoot to point to your normal compilation class root directory."; + + private final static String ICONTROL_PROPERTIES_MESSAGE = + "You should probably modify icontrol.properties' classRoot to where comiled (uninstrumented) classes go."; + + /** + * \ on windows, / on linux/unix + */ + private final static 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 src root + */ + private File instrumentDir = null; + + /** + * instrumentation build root + */ + private File buildDir = null; + + /** + * repository src root + */ + private File repositoryDir = null; + + /** + * repository build root + */ + private File repBuildDir = 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*"; + + /** + * The -q option + */ + private boolean quiet = false; + + /** + * 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; + private boolean preModified = false; + + /** + * Indicates whether or not to instrument for postconditions + */ + private boolean post = true; + private boolean postModified = false; + + /** + * Indicates whether or not to instrument for invariants + */ + private boolean invariant = true; + private boolean invariantModified = false; + + /** + * 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 = false; + + /** + * Indicates the name of a properties file (intentionally for iControl) + * where the classpath property should be updated. + */ + private boolean updateIcontrol = false; + + /** + * Regular compilation class root + */ + private File classDir = null; + + /** + * Sets the build directory for instrumented classes + * + * @param buildDir the build directory + */ + public void setBuilddir( File buildDir ) + { + this.buildDir = buildDir; + } + + /** + * Sets the class directory (uninstrumented classes) + * + * @param classDir The new Classdir value + */ + public void setClassdir( File classDir ) + { + this.classDir = classDir; + } + + /** + * Sets the classpath to be used for invocation of iContract. + * + * @param path The new Classpath value + * @path the classpath + */ + public void setClasspath( Path path ) + { + createClasspath().append( path ); + } + + /** + * Adds a reference to a classpath defined elsewhere. + * + * @param reference referenced classpath + */ + public void setClasspathRef( Reference reference ) + { + createClasspath().setRefid( reference ); + } + + /** + * Sets the control file to pass to iContract. + * + * @param controlFile the control file + */ + public void setControlfile( File controlFile ) + { + if( !controlFile.exists() ) + { + log( "WARNING: Control file " + controlFile.getAbsolutePath() + " doesn't exist. iContract will be run without control file." ); + } + this.controlFile = controlFile; + } + + /** + * Sets the Throwable (Exception) to be thrown on assertion violation + * + * @param clazz the fully qualified Throwable class name + */ + public void setFailthrowable( String clazz ) + { + this.failThrowable = clazz; + } + + /** + * Sets the instrumentation directory + * + * @param instrumentDir the source directory + */ + public void setInstrumentdir( File instrumentDir ) + { + this.instrumentDir = instrumentDir; + if( this.buildDir == null ) + { + setBuilddir( instrumentDir ); + } + } + + /** + * Turns on/off invariant instrumentation + * + * @param invariant true turns it on + */ + public void setInvariant( boolean invariant ) + { + this.invariant = invariant; + invariantModified = true; + } + + /** + * Turns on/off postcondition instrumentation + * + * @param post true turns it on + */ + public void setPost( boolean post ) + { + this.post = post; + postModified = true; + } + + /** + * Turns on/off precondition instrumentation + * + * @param pre true turns it on + */ + public void setPre( boolean pre ) + { + this.pre = pre; + preModified = true; + } + + /** + * Tells iContract to be quiet. + * + * @param quiet true if iContract should be quiet. + */ + public void setQuiet( boolean quiet ) + { + this.quiet = quiet; + } + + /** + * Sets the build directory for instrumented classes + * + * @param repBuildDir The new Repbuilddir value + */ + public void setRepbuilddir( File repBuildDir ) + { + this.repBuildDir = repBuildDir; + } + + /** + * Sets the build directory for repository classes + * + * @param repositoryDir the source directory + */ + public void setRepositorydir( File repositoryDir ) + { + this.repositoryDir = repositoryDir; + if( this.repBuildDir == null ) + { + setRepbuilddir( repositoryDir ); + } + } + + /** + * Sets the source directory + * + * @param srcDir the source directory + */ + public void setSrcdir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Sets the name of the file where targets will be written. That is the file + * that tells iContract what files to process. + * + * @param targets the targets file name + */ + public void setTargets( File targets ) + { + this.targets = targets; + } + + /** + * Decides whether or not to update iControl properties file + * + * @param updateIcontrol true if iControl properties file should be updated + */ + public void setUpdateicontrol( boolean updateIcontrol ) + { + this.updateIcontrol = updateIcontrol; + } + + /** + * 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 verbosity verbosity level + */ + public void setVerbosity( String verbosity ) + { + this.verbosity = verbosity; + } + + /** + * Creates a nested classpath element + * + * @return the nested classpath element + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( getProject() ); + } + return classpath; + } + + /** + * Executes the task + * + * @exception BuildException if the instrumentation fails + */ + public void execute() + throws BuildException + { + preconditions(); + scan(); + if( dirty ) + { + + // turn off assertions if we're using controlfile, unless they are not explicitly set. + boolean useControlFile = ( controlFile != null ) && controlFile.exists(); + if( useControlFile && !preModified ) + { + pre = false; + } + if( useControlFile && !postModified ) + { + post = false; + } + if( useControlFile && !invariantModified ) + { + invariant = false; + } + // issue warning if pre,post or invariant is used together with controlfile + if( ( pre || post || invariant ) && controlFile != null ) + { + log( "WARNING: specifying pre,post or invariant will override control file settings" ); + } + + + // 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( buildDir ); + mkdir.execute(); + mkdir.setDir( repositoryDir ); + mkdir.execute(); + + // Set the classpath that is needed for regular Javac compilation + Path baseClasspath = createClasspath(); + + // Might need to add the core classes if we're not using Sun's Javac (like Jikes) + String compiler = project.getProperty( "build.compiler" ); + ClasspathHelper classpathHelper = new ClasspathHelper( compiler ); + classpathHelper.modify( baseClasspath ); + + // 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() ) ); + afterInstrumentationClasspath.append( new Path( getProject(), buildDir.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() ) ); + repositoryClasspath.append( new Path( getProject(), buildDir.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() ) ); + iContractClasspath.append( new Path( getProject(), buildDir.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( "\"" ).append( icCompiler ).append( " -classpath " ).append( beforeInstrumentationClasspath ).append( "\" " ); + args.append( "-c" ).append( "\"" ).append( icCompiler ).append( " -classpath " ).append( afterInstrumentationClasspath ).append( " -d " ).append( buildDir ).append( "\" " ); + args.append( "-n" ).append( "\"" ).append( icCompiler ).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( quiet ? "-q " : "" ); + args.append( instrumentall ? "-a " : "" );// reinstrument everything if controlFile exists and is newer than any class + args.append( "@" ).append( targets.getAbsolutePath() ); + iContract.createArg().setLine( args.toString() ); + +//System.out.println( "JAVA -classpath " + iContractClasspath + " com.reliablesystems.iContract.Tool " + args.toString() ); + + // update iControlProperties if it's set. + if( updateIcontrol ) + { + Properties iControlProps = new Properties(); + try + {// to read existing propertiesfile + iControlProps.load( new FileInputStream( "icontrol.properties" ) ); + } + catch( IOException e ) + { + log( "File icontrol.properties not found. That's ok. Writing a default one." ); + } + iControlProps.setProperty( "sourceRoot", srcDir.getAbsolutePath() ); + iControlProps.setProperty( "classRoot", classDir.getAbsolutePath() ); + iControlProps.setProperty( "classpath", afterInstrumentationClasspath.toString() ); + iControlProps.setProperty( "controlFile", controlFile.getAbsolutePath() ); + iControlProps.setProperty( "targetsFile", targets.getAbsolutePath() ); + + try + {// to read existing propertiesfile + iControlProps.store( new FileOutputStream( "icontrol.properties" ), ICONTROL_PROPERTIES_HEADER ); + log( "Updated icontrol.properties" ); + } + catch( IOException e ) + { + log( "Couldn't write icontrol.properties." ); + } + } + + // do it! + 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 + {// not dirty + //log( "Nothing to do. Everything up to date." ); + } + } + + + /** + * Creates the -m option based on the values of controlFile, pre, post and + * invariant. + * + * @return Description of the Returned Value + */ + private final String directiveString() + { + StringBuffer sb = new StringBuffer(); + boolean comma = false; + + boolean useControlFile = ( controlFile != null ) && controlFile.exists(); + if( useControlFile || pre || post || invariant ) + { + sb.append( "-m" ); + } + if( useControlFile ) + { + 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" ); + } + sb.append( " " ); + return sb.toString(); + } + + /** + * Checks that the required attributes are set. + * + * @exception BuildException Description of Exception + */ + 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 ); + } + if( updateIcontrol == true && classDir == null ) + { + throw new BuildException( "classdir attribute must be specified when updateicontrol=true!", location ); + } + if( updateIcontrol == true && controlFile == null ) + { + throw new BuildException( "controlfile attribute must be specified when updateicontrol=true!", 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 or the control 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. + * + * @exception BuildException Description of Exception + */ + private void scan() + throws BuildException + { + long now = ( new Date() ).getTime(); + + DirectoryScanner ds = null; + + ds = getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + + FileOutputStream targetOutputStream = null; + PrintStream targetPrinter = null; + boolean writeTargets = false; + try + { + if( targets == null ) + { + targets = new File( "targets" ); + log( "Warning: targets file not specified. generating file: " + targets.getName() ); + writeTargets = true; + } + else if( !targets.exists() ) + { + log( "Specified targets file doesn't exist. generating file: " + targets.getName() ); + writeTargets = true; + } + if( writeTargets ) + { + log( "You should consider using iControl to create a target file." ); + targetOutputStream = new FileOutputStream( targets ); + targetPrinter = new PrintStream( targetOutputStream ); + } + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".java" ) ) + { + // print the target, while we're at here. (Only if generatetarget=true). + if( targetPrinter != null ) + { + targetPrinter.println( srcFile.getAbsolutePath() ); + } + File classFile = new File( buildDir, 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; + } + } + } + if( targetPrinter != null ) + { + targetPrinter.flush(); + targetPrinter.close(); + } + } + catch( IOException e ) + { + throw new BuildException( "Could not create target file:" + e.getMessage() ); + } + + // also, check controlFile timestamp + long controlFileTime = -1; + try + { + if( controlFile != null ) + { + if( controlFile.exists() && buildDir.exists() ) + { + controlFileTime = controlFile.lastModified(); + ds = getDirectoryScanner( buildDir ); + 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; + } + } + } + } + } + } + catch( Throwable t ) + { + throw new BuildException( "Got an interesting exception:" + t.getMessage() ); + } + } + + /** + * This class is a helper to set correct classpath for other compilers, like + * Jikes. It reuses the logic from DefaultCompilerAdapter, which is + * protected, so we have to subclass it. + * + * @author RT + */ + private class ClasspathHelper extends DefaultCompilerAdapter + { + private final String compiler; + + public ClasspathHelper( String compiler ) + { + super(); + this.compiler = compiler; + } + + // dummy implementation. Never called + public void setJavac( Javac javac ) { } + + public boolean execute() + { + return true; + } + + // make it public + public void modify( Path path ) + { + // depending on what compiler to use, set the includeJavaRuntime flag + if( "jikes".equals( compiler ) ) + { + icCompiler = compiler; + includeJavaRuntime = true; + path.append( getCompileClasspath() ); + } + } + } + + /** + * 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 + * + * @author RT + */ + 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 ) { } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Javah.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Javah.java new file mode 100644 index 000000000..76d48b24c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Javah.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Task to generate JNI header files using javah. This task can take the + * following arguments: + *
                + *
              • classname - the fully-qualified name of a class
              • + *
              • outputFile - Concatenates the resulting header or source files for all + * the classes listed into this file
              • + *
              • destdir - Sets the directory where javah saves the header files or the + * stub files
              • + *
              • classpath
              • + *
              • bootclasspath
              • + *
              • force - Specifies that output files should always be written (JDK1.2 + * only)
              • + *
              • old - Specifies that old JDK1.0-style header files should be generated + * (otherwise output file contain JNI-style native method function prototypes) + * (JDK1.2 only)
              • + *
              • stubs - generate C declarations from the Java object file (used with + * old)
              • + *
              • verbose - causes javah to print a message to stdout concerning the + * status of the generated files
              • + *
              • extdirs - Override location of installed extensions
              • + *
              + * Of these arguments, either outputFile or destdir is required, + * but not both. More than one classname may be specified, using a + * comma-separated list or by using <class name="xxx"> + * elements within the task.

              + * + * When this task executes, it will generate C header and source files that are + * needed to implement native methods. + * + * @author Rick Beton + * richard.beton@physics.org + */ + +public class Javah extends Task +{ + + private final static String FAIL_MSG = "Compile failed, messages should have been provided."; + //private Path extdirs; + private static String lSep = System.getProperty( "line.separator" ); + + private Vector classes = new Vector( 2 ); + private Path classpath = null; + private File outputFile = null; + private boolean verbose = false; + private boolean force = false; + private boolean old = false; + private boolean stubs = false; + private Path bootclasspath; + private String cls; + private File destDir; + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + public void setBootclasspath( Path src ) + { + if( bootclasspath == null ) + { + bootclasspath = src; + } + else + { + bootclasspath.append( src ); + } + } + + public void setClass( String cls ) + { + this.cls = cls; + } + + public void setClasspath( Path src ) + { + if( classpath == null ) + { + classpath = src; + } + else + { + classpath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the destination directory into which the Java source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the force-write flag. + * + * @param force The new Force value + */ + public void setForce( boolean force ) + { + this.force = force; + } + + /** + * Set the old flag. + * + * @param old The new Old value + */ + public void setOld( boolean old ) + { + this.old = old; + } + + ///** + // * Sets the extension directories that will be used during the + // * compilation. + // */ + //public void setExtdirs(Path extdirs) { + // if (this.extdirs == null) { + // this.extdirs = extdirs; + // } else { + // this.extdirs.append(extdirs); + // } + //} + + ///** + // * Maybe creates a nested classpath element. + // */ + //public Path createExtdirs() { + // if (extdirs == null) { + // extdirs = new Path(project); + // } + // return extdirs.createPath(); + //} + + /** + * Set the output file name. + * + * @param outputFile The new OutputFile value + */ + public void setOutputFile( File outputFile ) + { + this.outputFile = outputFile; + } + + /** + * Set the stubs flag. + * + * @param stubs The new Stubs value + */ + public void setStubs( boolean stubs ) + { + this.stubs = stubs; + } + + /** + * Set the verbose flag. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + this.verbose = verbose; + } + + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + public ClassArgument createClass() + { + ClassArgument ga = new ClassArgument(); + classes.addElement( ga ); + return ga; + } + + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + + if( ( cls == null ) && ( classes.size() == 0 ) ) + { + throw new BuildException( "class attribute must be set!", location ); + } + + if( ( cls != null ) && ( classes.size() > 0 ) ) + { + throw new BuildException( "set class attribute or class element, not both.", location ); + } + + if( destDir != null ) + { + if( !destDir.isDirectory() ) + { + throw new BuildException( "destination directory \"" + destDir + "\" does not exist or is not a directory", location ); + } + if( outputFile != null ) + { + throw new BuildException( "destdir and outputFile are mutually exclusive", location ); + } + } + + if( classpath == null ) + { + classpath = Path.systemClasspath; + } + + String compiler = project.getProperty( "build.compiler" ); + if( compiler == null ) + { + if( Project.getJavaVersion() != Project.JAVA_1_1 && + Project.getJavaVersion() != Project.JAVA_1_2 ) + { + compiler = "modern"; + } + else + { + compiler = "classic"; + } + } + + doClassicCompile(); + } + + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( Commandline cmd ) + { + int n = 0; + log( "Compilation args: " + cmd.toString(), + Project.MSG_VERBOSE ); + + StringBuffer niceClassList = new StringBuffer(); + if( cls != null ) + { + StringTokenizer tok = new StringTokenizer( cls, ",", false ); + while( tok.hasMoreTokens() ) + { + String aClass = tok.nextToken().trim(); + cmd.createArgument().setValue( aClass ); + niceClassList.append( " " + aClass + lSep ); + n++; + } + } + + Enumeration enum = classes.elements(); + while( enum.hasMoreElements() ) + { + ClassArgument arg = ( ClassArgument )enum.nextElement(); + String aClass = arg.getName(); + cmd.createArgument().setValue( aClass ); + niceClassList.append( " " + aClass + lSep ); + n++; + } + + StringBuffer prefix = new StringBuffer( "Class" ); + if( n > 1 ) + { + prefix.append( "es" ); + } + prefix.append( " to be compiled:" ); + prefix.append( lSep ); + + log( prefix.toString() + niceClassList.toString(), Project.MSG_VERBOSE ); + } + + /** + * Does the command line argument processing common to classic and modern. + * + * @return Description of the Returned Value + */ + private Commandline setupJavahCommand() + { + Commandline cmd = new Commandline(); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + if( outputFile != null ) + { + cmd.createArgument().setValue( "-o" ); + cmd.createArgument().setFile( outputFile ); + } + + if( classpath != null ) + { + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + } + + // JDK1.1 is rather simpler than JDK1.2 + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + if( verbose ) + { + cmd.createArgument().setValue( "-v" ); + } + } + else + { + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + if( old ) + { + cmd.createArgument().setValue( "-old" ); + } + if( force ) + { + cmd.createArgument().setValue( "-force" ); + } + } + + if( stubs ) + { + if( !old ) + { + throw new BuildException( "stubs only available in old mode.", location ); + } + cmd.createArgument().setValue( "-stubs" ); + } + if( bootclasspath != null ) + { + cmd.createArgument().setValue( "-bootclasspath" ); + cmd.createArgument().setPath( bootclasspath ); + } + + logAndAddFilesToCompile( cmd ); + return cmd; + } + + // XXX + // we need a way to not use the current classpath. + + /** + * Peforms a compile using the classic compiler that shipped with JDK 1.1 + * and 1.2. + * + * @exception BuildException Description of Exception + */ + + private void doClassicCompile() + throws BuildException + { + Commandline cmd = setupJavahCommand(); + + // Use reflection to be able to build on all JDKs + /* + * / provide the compiler a different message sink - namely our own + * sun.tools.javac.Main compiler = + * new sun.tools.javac.Main(new LogOutputStream(this, Project.MSG_WARN), "javac"); + * if (!compiler.compile(cmd.getArguments())) { + * throw new BuildException("Compile failed"); + * } + */ + try + { + // Javac uses logstr to change the output stream and calls + // the constructor's invoke method to create a compiler instance + // dynamically. However, javah has a different interface and this + // makes it harder, so here's a simple alternative. + //------------------------------------------------------------------ + com.sun.tools.javah.Main main = new com.sun.tools.javah.Main( cmd.getArguments() ); + main.run(); + } + //catch (ClassNotFoundException ex) { + // throw new BuildException("Cannot use javah because it is not available"+ + // " A common solution is to set the environment variable"+ + // " JAVA_HOME to your jdk directory.", location); + //} + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting javah: ", ex, location ); + } + } + } + + public class ClassArgument + { + private String name; + + public ClassArgument() { } + + public void setName( String name ) + { + this.name = name; + log( "ClassArgument.name=" + name ); + } + + public String getName() + { + return name; + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ManifestFile.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ManifestFile.java new file mode 100644 index 000000000..4ef6878db --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ManifestFile.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.ListIterator; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Task for creating a manifest file for a jar archiv. use:

              + *   
              + *   
              + *     
              + *         
              + *       
              + * + * @author Thomas Kerle + * @version 1.0 2001-10-11 + */ +public class ManifestFile extends Task +{ + + private final static String newLine = System.getProperty( "line.separator" ); + private final static String keyValueSeparator = ":"; + private final static String UPDATE_ = "update"; + private final static String REPLACEALL_ = "replaceAll"; + private EntryContainer container; + private String currentMethod; + private Vector entries; + + private File manifestFile; + + public ManifestFile() + { + entries = new Vector(); + container = new EntryContainer(); + } + + /** + * Setter for the file attribute + * + * @param f The new File value + */ + public void setFile( File f ) + { + manifestFile = f; + } + + /** + * Setter for the method attribute (update/replaceAll) + * + * @param method Method to set task + */ + public void setMethod( String method ) + { + currentMethod = method.toUpperCase(); + } + + /** + * creating entries by Ant + * + * @return Description of the Returned Value + */ + public Entry createEntry() + { + Entry entry = new Entry(); + entries.addElement( entry ); + return entry; + } + + /** + * execute task + * + * @exception BuildException : Failure in building + */ + public void execute() + throws BuildException + { + checkParameters(); + if( isUpdate( currentMethod ) ) + readFile(); + + executeOperation(); + writeFile(); + } + + private StringTokenizer getLineTokens( StringBuffer buffer ) + { + String manifests = buffer.toString(); + StringTokenizer strTokens = new StringTokenizer( manifests, newLine ); + return strTokens; + } + + private boolean isReplaceAll( String method ) + { + return method.equals( REPLACEALL_.toUpperCase() ); + } + + + private boolean isUpdate( String method ) + { + return method.equals( UPDATE_.toUpperCase() ); + } + + private void addLine( String line ) + { + Entry entry = new Entry(); + + entry.setValue( line ); + entry.addTo( container ); + } + + + private StringBuffer buildBuffer() + { + StringBuffer buffer = new StringBuffer(); + + ListIterator iterator = container.elements(); + + while( iterator.hasNext() ) + { + Entry entry = ( Entry )iterator.next(); + + String key = ( String )entry.getKey(); + String value = ( String )entry.getValue(); + String entry_string = key + keyValueSeparator + value; + + buffer.append( entry_string + this.newLine ); + } + + return buffer; + } + + private boolean checkParam( String param ) + { + return !( ( param == null ) || ( param.equals( "null" ) ) ); + } + + private boolean checkParam( File param ) + { + return !( param == null ); + } + + private void checkParameters() + throws BuildException + { + if( !checkParam( manifestFile ) ) + { + throw new BuildException( "file token must not be null.", location ); + } + } + + /** + * adding entries to a container + * + * @exception BuildException + */ + private void executeOperation() + throws BuildException + { + Enumeration enum = entries.elements(); + + while( enum.hasMoreElements() ) + { + Entry entry = ( Entry )enum.nextElement(); + entry.addTo( container ); + } + } + + private void readFile() + throws BuildException + { + + if( manifestFile.exists() ) + { + this.log( "update existing manifest file " + manifestFile.getAbsolutePath() ); + + if( container != null ) + { + try + { + FileInputStream fis = new FileInputStream( manifestFile ); + + int c; + StringBuffer buffer = new StringBuffer( "" ); + boolean stop = false; + while( !stop ) + { + c = fis.read(); + if( c == -1 ) + { + stop = true; + } + else + buffer.append( ( char )c ); + } + fis.close(); + StringTokenizer lineTokens = getLineTokens( buffer ); + while( lineTokens.hasMoreElements() ) + { + String currentLine = ( String )lineTokens.nextElement(); + addLine( currentLine ); + } + } + catch( FileNotFoundException fnfe ) + { + throw new BuildException( "File not found exception " + fnfe.toString() ); + } + catch( IOException ioe ) + { + throw new BuildException( "Unknown input/output exception " + ioe.toString() ); + } + } + } + + } + + + private void writeFile() + throws BuildException + { + try + { + manifestFile.delete(); + log( "Replacing or creating new manifest file " + manifestFile.getAbsolutePath() ); + if( manifestFile.createNewFile() ) + { + FileOutputStream fos = new FileOutputStream( manifestFile ); + + StringBuffer buffer = buildBuffer(); + + int size = buffer.length(); + + for( int i = 0; i < size; i++ ) + { + fos.write( ( char )buffer.charAt( i ) ); + } + + fos.flush(); + fos.close(); + } + else + { + throw new BuildException( "Can't create manifest file" ); + } + + } + catch( IOException ioe ) + { + throw new BuildException( "An input/ouput error occured" + ioe.toString() ); + } + } + + public class Entry implements Comparator + { + //extern format + private String value = null; + + //intern representation + private String val = null; + private String key = null; + + public Entry() { } + + public void setValue( String value ) + { + this.value = new String( value ); + } + + public String getKey() + { + return key; + } + + public String getValue() + { + return val; + } + + public int compare( Object o1, Object o2 ) + { + int result = -1; + + try + { + Entry e1 = ( Entry )o1; + Entry e2 = ( Entry )o2; + + String key_1 = e1.getKey(); + String key_2 = e2.getKey(); + + result = key_1.compareTo( key_2 ); + } + catch( Exception e ) + { + + } + return result; + } + + + public boolean equals( Object obj ) + { + Entry ent = new Entry(); + boolean result = false; + int res = ent.compare( this, ( Entry )obj ); + if( res == 0 ) + result = true; + + return result; + } + + + protected void addTo( EntryContainer container ) + throws BuildException + { + checkFormat(); + split(); + container.set( this ); + } + + private void checkFormat() + throws BuildException + { + + if( value == null ) + { + throw new BuildException( "no argument for value" ); + } + + StringTokenizer st = new StringTokenizer( value, ManifestFile.keyValueSeparator ); + int size = st.countTokens(); + + if( size < 2 ) + { + throw new BuildException( "value has not the format of a manifest entry" ); + } + } + + private void split() + { + StringTokenizer st = new StringTokenizer( value, ManifestFile.keyValueSeparator ); + key = ( String )st.nextElement(); + val = ( String )st.nextElement(); + } + + } + + public class EntryContainer + { + + private ArrayList list = null; + + public EntryContainer() + { + list = new ArrayList(); + } + + public void set( Entry entry ) + { + + if( list.contains( entry ) ) + { + int index = list.indexOf( entry ); + + list.remove( index ); + list.add( index, entry ); + } + else + { + list.add( entry ); + } + } + + public ListIterator elements() + { + ListIterator iterator = list.listIterator(); + return iterator; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java new file mode 100644 index 000000000..882335c60 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.IdentityMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Convert files from native encodings to ascii. + * + * @author Drew Sudell + * @author Stefan Bodewig + */ +public class Native2Ascii extends MatchingTask +{ + + private boolean reverse = false;// convert from ascii back to native + private String encoding = null;// encoding to convert to/from + private File srcDir = null;// Where to find input files + private File destDir = null;// Where to put output files + private String extension = null;// Extension of output files if different + + private Mapper mapper; + + + /** + * Set the destination dirctory to place converted files into. + * + * @param destDir directory to place output file into. + */ + public void setDest( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the encoding to translate to/from. If unset, the default encoding for + * the JVM is used. + * + * @param encoding String containing the name of the Native encoding to + * convert from or to. + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Set the extension which converted files should have. If unset, files will + * not be renamed. + * + * @param ext File extension to use for converted files. + */ + public void setExt( String ext ) + { + this.extension = ext; + } + + /** + * Flag the conversion to run in the reverse sense, that is Ascii to Native + * encoding. + * + * @param reverse True if the conversion is to be reversed, otherwise false; + */ + public void setReverse( boolean reverse ) + { + this.reverse = reverse; + } + + /** + * Set the source directory in which to find files to convert. + * + * @param srcDir Direcrory to find input file in. + */ + public void setSrc( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapper != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapper = new Mapper( project ); + return mapper; + } + + public void execute() + throws BuildException + { + + Commandline baseCmd = null;// the common portion of our cmd line + DirectoryScanner scanner = null;// Scanner to find our inputs + String[] files;// list of files to process + + // default srcDir to basedir + if( srcDir == null ) + { + srcDir = project.resolveFile( "." ); + } + + // Require destDir + if( destDir == null ) + { + throw new BuildException( "The dest attribute must be set." ); + } + + // if src and dest dirs are the same, require the extension + // to be set, so we don't stomp every file. One could still + // include a file with the same extension, but .... + if( srcDir.equals( destDir ) && extension == null && mapper == null ) + { + throw new BuildException( "The ext attribute or a mapper must be set if" + + " src and dest dirs are the same." ); + } + + FileNameMapper m = null; + if( mapper == null ) + { + if( extension == null ) + { + m = new IdentityMapper(); + } + else + { + m = new ExtMapper(); + } + } + else + { + m = mapper.getImplementation(); + } + + scanner = getDirectoryScanner( srcDir ); + files = scanner.getIncludedFiles(); + SourceFileScanner sfs = new SourceFileScanner( this ); + files = sfs.restrict( files, srcDir, destDir, m ); + int count = files.length; + if( count == 0 ) + { + return; + } + String message = "Converting " + count + " file" + + ( count != 1 ? "s" : "" ) + " from "; + log( message + srcDir + " to " + destDir ); + for( int i = 0; i < files.length; i++ ) + { + convert( files[i], m.mapFileName( files[i] )[0] ); + } + } + + /** + * Convert a single file. + * + * @param srcName Description of Parameter + * @param destName Description of Parameter + * @exception BuildException Description of Exception + */ + private void convert( String srcName, String destName ) + throws BuildException + { + + Commandline cmd = new Commandline();// Command line to run + File srcFile;// File to convert + File destFile;// where to put the results + + // Set up the basic args (this could be done once, but + // it's cleaner here) + if( reverse ) + { + cmd.createArgument().setValue( "-reverse" ); + } + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + + // Build the full file names + srcFile = new File( srcDir, srcName ); + destFile = new File( destDir, destName ); + + cmd.createArgument().setFile( srcFile ); + cmd.createArgument().setFile( destFile ); + // Make sure we're not about to clobber something + if( srcFile.equals( destFile ) ) + { + throw new BuildException( "file " + srcFile + + " would overwrite its self" ); + } + + // Make intermediate directories if needed + // XXX JDK 1.1 dosen't have File.getParentFile, + String parentName = destFile.getParent(); + if( parentName != null ) + { + File parentFile = new File( parentName ); + + if( ( !parentFile.exists() ) && ( !parentFile.mkdirs() ) ) + { + throw new BuildException( "cannot create parent directory " + + parentName ); + } + } + + log( "converting " + srcName, Project.MSG_VERBOSE ); + sun.tools.native2ascii.Main n2a + = new sun.tools.native2ascii.Main(); + if( !n2a.convert( cmd.getArguments() ) ) + { + throw new BuildException( "conversion failed" ); + } + } + + private class ExtMapper implements FileNameMapper + { + + public void setFrom( String s ) { } + + public void setTo( String s ) { } + + public String[] mapFileName( String fileName ) + { + int lastDot = fileName.lastIndexOf( '.' ); + if( lastDot >= 0 ) + { + return new String[]{fileName.substring( 0, lastDot ) + extension}; + } + else + { + return new String[]{fileName + extension}; + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/NetRexxC.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/NetRexxC.java new file mode 100644 index 000000000..3b05253a1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/NetRexxC.java @@ -0,0 +1,766 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; +import netrexx.lang.Rexx; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; + +/** + * Task to compile NetRexx source files. This task can take the following + * arguments: + *
                + *
              • binary
              • + *
              • classpath
              • + *
              • comments
              • + *
              • compile
              • + *
              • console
              • + *
              • crossref
              • + *
              • decimal
              • + *
              • destdir
              • + *
              • diag
              • + *
              • explicit
              • + *
              • format
              • + *
              • keep
              • + *
              • logo
              • + *
              • replace
              • + *
              • savelog
              • + *
              • srcdir
              • + *
              • sourcedir
              • + *
              • strictargs
              • + *
              • strictassign
              • + *
              • strictcase
              • + *
              • strictimport
              • + *
              • symbols
              • + *
              • time
              • + *
              • trace
              • + *
              • utf8
              • + *
              • verbose
              • + *
              + * Of these arguments, the srcdir argument is required.

              + * + * When this task executes, it will recursively scan the srcdir looking for + * NetRexx source files to compile. This task makes its compile decision based + * on timestamp.

              + * + * Before files are compiled they and any other file in the srcdir will be + * copied to the destdir allowing support files to be located properly in the + * classpath. The reason for copying the source files before the compile is that + * NetRexxC has only two destinations for classfiles: + *

                + *
              1. The current directory, and,
              2. + *
              3. The directory the source is in (see sourcedir option) + *
              + * + * + * @author dIon Gillard + * dion@multitask.com.au + */ + +public class NetRexxC extends MatchingTask +{ + private boolean compile = true; + private boolean decimal = true; + private boolean logo = true; + private boolean sourcedir = true; + private String trace = "trace2"; + private String verbose = "verbose3"; + + // other implementation variables + private Vector compileList = new Vector(); + private Hashtable filecopyList = new Hashtable(); + private String oldClasspath = System.getProperty( "java.class.path" ); + + // variables to hold arguments + private boolean binary; + private String classpath; + private boolean comments; + private boolean compact; + private boolean console; + private boolean crossref; + private File destDir; + private boolean diag; + private boolean explicit; + private boolean format; + private boolean java; + private boolean keep; + private boolean replace; + private boolean savelog; + private File srcDir;// ?? Should this be the default for ant? + private boolean strictargs; + private boolean strictassign; + private boolean strictcase; + private boolean strictimport; + private boolean strictprops; + private boolean strictsignal; + private boolean symbols; + private boolean time; + private boolean utf8; + + + /** + * Set whether literals are treated as binary, rather than NetRexx types + * + * @param binary The new Binary value + */ + public void setBinary( boolean binary ) + { + this.binary = binary; + } + + /** + * Set the classpath used for NetRexx compilation + * + * @param classpath The new Classpath value + */ + public void setClasspath( String classpath ) + { + this.classpath = classpath; + } + + /** + * Set whether comments are passed through to the generated java source. + * Valid true values are "on" or "true". Anything else sets the flag to + * false. The default value is false + * + * @param comments The new Comments value + */ + public void setComments( boolean comments ) + { + this.comments = comments; + } + + /** + * Set whether error messages come out in compact or verbose format. Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false + * + * @param compact The new Compact value + */ + public void setCompact( boolean compact ) + { + this.compact = compact; + } + + /** + * Set whether the NetRexx compiler should compile the generated java code + * Valid true values are "on" or "true". Anything else sets the flag to + * false. The default value is true. Setting this flag to false, will + * automatically set the keep flag to true. + * + * @param compile The new Compile value + */ + public void setCompile( boolean compile ) + { + this.compile = compile; + if( !this.compile && !this.keep ) + this.keep = true; + } + + /** + * Set whether or not messages should be displayed on the 'console' Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is true. + * + * @param console The new Console value + */ + public void setConsole( boolean console ) + { + this.console = console; + } + + /** + * Whether variable cross references are generated + * + * @param crossref The new Crossref value + */ + public void setCrossref( boolean crossref ) + { + this.crossref = crossref; + } + + /** + * Set whether decimal arithmetic should be used for the netrexx code. + * Binary arithmetic is used when this flag is turned off. Valid true values + * are "on" or "true". Anything else sets the flag to false. The default + * value is true. + * + * @param decimal The new Decimal value + */ + public void setDecimal( boolean decimal ) + { + this.decimal = decimal; + } + + /** + * Set the destination directory into which the NetRexx source files should + * be copied and then compiled. + * + * @param destDirName The new DestDir value + */ + public void setDestDir( File destDirName ) + { + destDir = destDirName; + } + + /** + * Whether diagnostic information about the compile is generated + * + * @param diag The new Diag value + */ + public void setDiag( boolean diag ) + { + this.diag = diag; + } + + /** + * Sets whether variables must be declared explicitly before use. Valid true + * values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param explicit The new Explicit value + */ + public void setExplicit( boolean explicit ) + { + this.explicit = explicit; + } + + /** + * Whether the generated java code is formatted nicely or left to match + * NetRexx line numbers for call stack debugging + * + * @param format The new Format value + */ + public void setFormat( boolean format ) + { + this.format = format; + } + + /** + * Whether the generated java code is produced Valid true values are "on" or + * "true". Anything else sets the flag to false. The default value is false. + * + * @param java The new Java value + */ + public void setJava( boolean java ) + { + this.java = java; + } + + + /** + * Sets whether the generated java source file should be kept after + * compilation. The generated files will have an extension of .java.keep, + * not .java Valid true values are "on" or "true". Anything else sets + * the flag to false. The default value is false. + * + * @param keep The new Keep value + */ + public void setKeep( boolean keep ) + { + this.keep = keep; + } + + /** + * Whether the compiler text logo is displayed when compiling + * + * @param logo The new Logo value + */ + public void setLogo( boolean logo ) + { + this.logo = logo; + } + + /** + * Whether the generated .java file should be replaced when compiling Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param replace The new Replace value + */ + public void setReplace( boolean replace ) + { + this.replace = replace; + } + + /** + * Sets whether the compiler messages will be written to NetRexxC.log as + * well as to the console Valid true values are "on" or "true". Anything + * else sets the flag to false. The default value is false. + * + * @param savelog The new Savelog value + */ + public void setSavelog( boolean savelog ) + { + this.savelog = savelog; + } + + /** + * Tells the NetRexx compiler to store the class files in the same directory + * as the source files. The alternative is the working directory Valid true + * values are "on" or "true". Anything else sets the flag to false. The + * default value is true. + * + * @param sourcedir The new Sourcedir value + */ + public void setSourcedir( boolean sourcedir ) + { + this.sourcedir = sourcedir; + } + + /** + * Set the source dir to find the source Java files. + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + srcDir = srcDirName; + } + + /** + * Tells the NetRexx compiler that method calls always need parentheses, + * even if no arguments are needed, e.g. aStringVar.getBytes + * vs. aStringVar.getBytes() Valid true values are "on" or + * "true". Anything else sets the flag to false. The default value is false. + * + * @param strictargs The new Strictargs value + */ + public void setStrictargs( boolean strictargs ) + { + this.strictargs = strictargs; + } + + /** + * Tells the NetRexx compile that assignments must match exactly on type + * + * @param strictassign The new Strictassign value + */ + public void setStrictassign( boolean strictassign ) + { + this.strictassign = strictassign; + } + + /** + * Specifies whether the NetRexx compiler should be case sensitive or not + * + * @param strictcase The new Strictcase value + */ + public void setStrictcase( boolean strictcase ) + { + this.strictcase = strictcase; + } + + /** + * Sets whether classes need to be imported explicitly using an import + * statement. By default the NetRexx compiler will import certain packages + * automatically Valid true values are "on" or "true". Anything else sets + * the flag to false. The default value is false. + * + * @param strictimport The new Strictimport value + */ + public void setStrictimport( boolean strictimport ) + { + this.strictimport = strictimport; + } + + /** + * Sets whether local properties need to be qualified explicitly using + * this Valid true values are "on" or "true". Anything else + * sets the flag to false. The default value is false. + * + * @param strictprops The new Strictprops value + */ + public void setStrictprops( boolean strictprops ) + { + this.strictprops = strictprops; + } + + + /** + * Whether the compiler should force catching of exceptions by explicitly + * named types + * + * @param strictsignal The new Strictsignal value + */ + public void setStrictsignal( boolean strictsignal ) + { + this.strictsignal = strictsignal; + } + + /** + * Sets whether debug symbols should be generated into the class file Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param symbols The new Symbols value + */ + public void setSymbols( boolean symbols ) + { + this.symbols = symbols; + } + + /** + * Asks the NetRexx compiler to print compilation times to the console Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param time The new Time value + */ + public void setTime( boolean time ) + { + this.time = time; + } + + /** + * Turns on or off tracing and directs the resultant trace output Valid + * values are: "trace", "trace1", "trace2" and "notrace". "trace" and + * "trace2" + * + * @param trace The new Trace value + */ + public void setTrace( String trace ) + { + if( trace.equalsIgnoreCase( "trace" ) + || trace.equalsIgnoreCase( "trace1" ) + || trace.equalsIgnoreCase( "trace2" ) + || trace.equalsIgnoreCase( "notrace" ) ) + { + this.trace = trace; + } + else + { + throw new BuildException( "Unknown trace value specified: '" + trace + "'" ); + } + } + + /** + * Tells the NetRexx compiler that the source is in UTF8 Valid true values + * are "on" or "true". Anything else sets the flag to false. The default + * value is false. + * + * @param utf8 The new Utf8 value + */ + public void setUtf8( boolean utf8 ) + { + this.utf8 = utf8; + } + + /** + * Whether lots of warnings and error messages should be generated + * + * @param verbose The new Verbose value + */ + public void setVerbose( String verbose ) + { + this.verbose = verbose; + } + + /** + * Executes the task, i.e. does the actual compiler call + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // first off, make sure that we've got a srcdir and destdir + if( srcDir == null || destDir == null ) + { + throw new BuildException( "srcDir and destDir attributes must be set!" ); + } + + // scan source and dest dirs to build up both copy lists and + // compile lists + // scanDir(srcDir, destDir); + DirectoryScanner ds = getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + + scanDir( srcDir, destDir, files ); + + // copy the source and support files + copyFilesToDestination(); + + // compile the source files + if( compileList.size() > 0 ) + { + log( "Compiling " + compileList.size() + " source file" + + ( compileList.size() == 1 ? "" : "s" ) + + " to " + destDir ); + doNetRexxCompile(); + } + } + + /** + * Builds the compilation classpath. + * + * @return The CompileClasspath value + */ + private String getCompileClasspath() + { + StringBuffer classpath = new StringBuffer(); + + // add dest dir to classpath so that previously compiled and + // untouched classes are on classpath + classpath.append( destDir.getAbsolutePath() ); + + // add our classpath to the mix + if( this.classpath != null ) + { + addExistingToClasspath( classpath, this.classpath ); + } + + // add the system classpath + // addExistingToClasspath(classpath,System.getProperty("java.class.path")); + return classpath.toString(); + } + + /** + * This + * + * @return The CompileOptionsAsArray value + */ + private String[] getCompileOptionsAsArray() + { + Vector options = new Vector(); + options.addElement( binary ? "-binary" : "-nobinary" ); + options.addElement( comments ? "-comments" : "-nocomments" ); + options.addElement( compile ? "-compile" : "-nocompile" ); + options.addElement( compact ? "-compact" : "-nocompact" ); + options.addElement( console ? "-console" : "-noconsole" ); + options.addElement( crossref ? "-crossref" : "-nocrossref" ); + options.addElement( decimal ? "-decimal" : "-nodecimal" ); + options.addElement( diag ? "-diag" : "-nodiag" ); + options.addElement( explicit ? "-explicit" : "-noexplicit" ); + options.addElement( format ? "-format" : "-noformat" ); + options.addElement( keep ? "-keep" : "-nokeep" ); + options.addElement( logo ? "-logo" : "-nologo" ); + options.addElement( replace ? "-replace" : "-noreplace" ); + options.addElement( savelog ? "-savelog" : "-nosavelog" ); + options.addElement( sourcedir ? "-sourcedir" : "-nosourcedir" ); + options.addElement( strictargs ? "-strictargs" : "-nostrictargs" ); + options.addElement( strictassign ? "-strictassign" : "-nostrictassign" ); + options.addElement( strictcase ? "-strictcase" : "-nostrictcase" ); + options.addElement( strictimport ? "-strictimport" : "-nostrictimport" ); + options.addElement( strictprops ? "-strictprops" : "-nostrictprops" ); + options.addElement( strictsignal ? "-strictsignal" : "-nostrictsignal" ); + options.addElement( symbols ? "-symbols" : "-nosymbols" ); + options.addElement( time ? "-time" : "-notime" ); + options.addElement( "-" + trace ); + options.addElement( utf8 ? "-utf8" : "-noutf8" ); + options.addElement( "-" + verbose ); + String[] results = new String[options.size()]; + options.copyInto( results ); + return results; + } + + /** + * Takes a classpath-like string, and adds each element of this string to a + * new classpath, if the components exist. Components that don't exist, + * aren't added. We do this, because jikes issues warnings for non-existant + * files/dirs in his classpath, and these warnings are pretty annoying. + * + * @param target - target classpath + * @param source - source classpath to get file objects. + */ + private void addExistingToClasspath( StringBuffer target, String source ) + { + StringTokenizer tok = new StringTokenizer( source, + System.getProperty( "path.separator" ), false ); + while( tok.hasMoreTokens() ) + { + File f = project.resolveFile( tok.nextToken() ); + + if( f.exists() ) + { + target.append( File.pathSeparator ); + target.append( f.getAbsolutePath() ); + } + else + { + log( "Dropping from classpath: " + + f.getAbsolutePath(), Project.MSG_VERBOSE ); + } + } + + } + + /** + * Copy eligible files from the srcDir to destDir + */ + private void copyFilesToDestination() + { + if( filecopyList.size() > 0 ) + { + log( "Copying " + filecopyList.size() + " file" + + ( filecopyList.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + Enumeration enum = filecopyList.keys(); + while( enum.hasMoreElements() ) + { + String fromFile = ( String )enum.nextElement(); + String toFile = ( String )filecopyList.get( fromFile ); + try + { + project.copyFile( fromFile, toFile ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + } + } + } + + /** + * Peforms a copmile using the NetRexx 1.1.x compiler + * + * @exception BuildException Description of Exception + */ + private void doNetRexxCompile() + throws BuildException + { + log( "Using NetRexx compiler", Project.MSG_VERBOSE ); + String classpath = getCompileClasspath(); + StringBuffer compileOptions = new StringBuffer(); + StringBuffer fileList = new StringBuffer(); + + // create an array of strings for input to the compiler: one array + // comes from the compile options, the other from the compileList + String[] compileOptionsArray = getCompileOptionsAsArray(); + String[] fileListArray = new String[compileList.size()]; + Enumeration e = compileList.elements(); + int j = 0; + while( e.hasMoreElements() ) + { + fileListArray[j] = ( String )e.nextElement(); + j++; + } + // create a single array of arguments for the compiler + String compileArgs[] = new String[compileOptionsArray.length + fileListArray.length]; + for( int i = 0; i < compileOptionsArray.length; i++ ) + { + compileArgs[i] = compileOptionsArray[i]; + } + for( int i = 0; i < fileListArray.length; i++ ) + { + compileArgs[i + compileOptionsArray.length] = fileListArray[i]; + } + + // print nice output about what we are doing for the log + compileOptions.append( "Compilation args: " ); + for( int i = 0; i < compileOptionsArray.length; i++ ) + { + compileOptions.append( compileOptionsArray[i] ); + compileOptions.append( " " ); + } + log( compileOptions.toString(), Project.MSG_VERBOSE ); + + String eol = System.getProperty( "line.separator" ); + StringBuffer niceSourceList = new StringBuffer( "Files to be compiled:" + eol ); + + for( int i = 0; i < compileList.size(); i++ ) + { + niceSourceList.append( " " ); + niceSourceList.append( compileList.elementAt( i ).toString() ); + niceSourceList.append( eol ); + } + + log( niceSourceList.toString(), Project.MSG_VERBOSE ); + + // need to set java.class.path property and restore it later + // since the NetRexx compiler has no option for the classpath + String currentClassPath = System.getProperty( "java.class.path" ); + Properties currentProperties = System.getProperties(); + currentProperties.put( "java.class.path", classpath ); + + try + { + StringWriter out = new StringWriter(); + int rc = + COM.ibm.netrexx.process.NetRexxC.main( new Rexx( compileArgs ), new PrintWriter( out ) ); + + if( rc > 1 ) + {// 1 is warnings from real NetRexxC + log( out.toString(), Project.MSG_ERR ); + String msg = "Compile failed, messages should have been provided."; + throw new BuildException( msg ); + } + else if( rc == 1 ) + { + log( out.toString(), Project.MSG_WARN ); + } + else + { + log( out.toString(), Project.MSG_INFO ); + } + } + finally + { + // need to reset java.class.path property + // since the NetRexx compiler has no option for the classpath + currentProperties = System.getProperties(); + currentProperties.put( "java.class.path", currentClassPath ); + } + } + + /** + * Scans the directory looking for source files to be compiled and support + * files to be copied. + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + private void scanDir( File srcDir, File destDir, String[] files ) + { + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + File destFile = new File( destDir, files[i] ); + String filename = files[i]; + // if it's a non source file, copy it if a later date than the + // dest + // if it's a source file, see if the destination class file + // needs to be recreated via compilation + if( filename.toLowerCase().endsWith( ".nrx" ) ) + { + File classFile = + new File( destDir, + filename.substring( 0, filename.lastIndexOf( '.' ) ) + ".class" ); + + if( !compile || srcFile.lastModified() > classFile.lastModified() ) + { + filecopyList.put( srcFile.getAbsolutePath(), destFile.getAbsolutePath() ); + compileList.addElement( destFile.getAbsolutePath() ); + } + } + else + { + if( srcFile.lastModified() > destFile.lastModified() ) + { + filecopyList.put( srcFile.getAbsolutePath(), destFile.getAbsolutePath() ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/PropertyFile.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/PropertyFile.java new file mode 100644 index 000000000..ab7bfd7b1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/PropertyFile.java @@ -0,0 +1,791 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * PropertyFile task uses java.util.Properties to modify integer, String and + * Date settings in a property file.

              + * + * The following is an example of its usage: + *

                <target name="setState">
                + * + *
                  <property
                  + * + *
                    name="header"
                    + * value="##Generated file - do not modify!"/>
                    + * <propertyfile file="apropfile.properties" comment="${header}"> + *
                    + * <entry key="product.version.major" type="int" value="5"/>
                    + * <entry key="product.version.minor" type="int" value="0"/>
                    + * <entry key="product.build.major" type="int" value="0" />
                    + * <entry key="product.build.minor" type="int" operation="+" />
                    + * <entry key="product.build.date" type="date" operation="now" /> + *
                    + * <entry key="intSet" type="int" operation="=" value="681"/>
                    + * <entry key="intDec" type="int" operation="-"/>
                    + * <entry key="NeverDate" type="date" operation="never"/>
                    + * <entry key="StringEquals" type="string" value="testValue"/>
                    + * <entry key="NowDate" type="date" operation="now"/>
                    + * + *
                  + * </propertyfile>
                  + * + *
                + * </target> + *
              + *

              + * + * The <propertyfile> task must have:
              + * + *

                + *
              • file
              • + *
              + * Other parameters are:
              + * + *
                + *
              • comment, key, operation, type and value (the final four being + * eliminated shortly)
              • + *
              + * The <entry> task must have:
              + * + *
                + *
              • key
              • + *
              + * Other parameters are:
              + * + *
                + *
              • operation
              • + *
              • type
              • + *
              • value
              • + *
              • offset
              • + *
              + * If type is unspecified, it defaults to string Parameter values:
              + * + *
                + *
              • operation:
              • + *
                  + *
                • "=" (set -- default)
                • + *
                • "-" (dec)
                • + *
                • "+" (inc)
                • + *
                • type:
                • + *
                    + *
                  • "int"
                  • + *
                  • "date"
                  • + *
                  • "string"
                  • + *
                  + * + *
                + * + *
              • value:
              • + *
                  + *
                • holds the default value, if the property was not found in property + * file
                • + *
                • "now" In case of type "date", the value "now" will be replaced by + * the current date/time and used even if a valid date was found in the + * property file.
                • + *
                + * + *
              • offset:
                + * valid for "-" or "+", the offset (default set to 1) will be added or + * subtracted from "int" or "date" type value.
              • + *
              + * String property types can only use the "=" operation. Date property types can + * only use the "never" or "now" operations. Int property types can only use the + * "=", "-" or "+" operations.

              + * + * The message property is used for the property file header, with "\\" being a + * newline delimiter charater. + * + * @author Thomas Christen chr@active.ch + * @author Jeremy Mawson + * jem@loftinspace.com.au + */ +public class PropertyFile extends Task +{ + + /* + * ======================================================================== + * + * Static variables. + */ + private final static String NEWLINE = System.getProperty( "line.separator" ); + + private Vector entries = new Vector(); + + /* + * ======================================================================== + * + * Instance variables. + */ + // Use this to prepend a message to the properties file + private String m_comment; + + private Properties m_properties; + private File m_propertyfile; + + public void setComment( String hdr ) + { + m_comment = hdr; + } + + public void setFile( File file ) + { + m_propertyfile = file; + } + + public Entry createEntry() + { + Entry e = new Entry(); + entries.addElement( e ); + return e; + } + + /* + * ======================================================================== + * + * Constructors + */ + /* + * ======================================================================== + * + * Methods + */ + public void execute() + throws BuildException + { + checkParameters(); + readFile(); + executeOperation(); + writeFile(); + } + + /* + * Returns whether the given parameter has been defined. + */ + private boolean checkParam( String param ) + { + return !( ( param == null ) || ( param.equals( "null" ) ) ); + } + + private boolean checkParam( File param ) + { + return !( param == null ); + } + + private void checkParameters() + throws BuildException + { + if( !checkParam( m_propertyfile ) ) + { + throw new BuildException( "file token must not be null.", location ); + } + } + + private void executeOperation() + throws BuildException + { + for( Enumeration e = entries.elements(); e.hasMoreElements(); ) + { + Entry entry = ( Entry )e.nextElement(); + entry.executeOn( m_properties ); + } + } + + private void readFile() + throws BuildException + { + // Create the PropertyFile + m_properties = new Properties(); + try + { + if( m_propertyfile.exists() ) + { + log( "Updating property file: " + m_propertyfile.getAbsolutePath() ); + FileInputStream fis = null; + try + { + fis = new FileInputStream( m_propertyfile ); + BufferedInputStream bis = new BufferedInputStream( fis ); + m_properties.load( bis ); + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + } + else + { + log( "Creating new property file: " + + m_propertyfile.getAbsolutePath() ); + FileOutputStream out = null; + try + { + out = new FileOutputStream( m_propertyfile.getAbsolutePath() ); + out.flush(); + } + finally + { + if( out != null ) + { + out.close(); + } + } + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.toString() ); + } + } + + private void writeFile() + throws BuildException + { + BufferedOutputStream bos = null; + try + { + bos = new BufferedOutputStream( new FileOutputStream( m_propertyfile ) ); + + // Properties.store is not available in JDK 1.1 + Method m = + Properties.class.getMethod( "store", + new Class[]{ + OutputStream.class, + String.class} + ); + m.invoke( m_properties, new Object[]{bos, m_comment} ); + + } + catch( NoSuchMethodException nsme ) + { + m_properties.save( bos, m_comment ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t ); + } + catch( IllegalAccessException iae ) + { + // impossible + throw new BuildException( iae ); + } + catch( IOException ioe ) + { + throw new BuildException( ioe ); + } + finally + { + if( bos != null ) + { + try + { + bos.close(); + } + catch( IOException ioex ) + {} + } + } + } + + /** + * Instance of this class represents nested elements of a task propertyfile. + * + * @author RT + */ + public static class Entry + { + + final static String NOW_VALUE_ = "now"; + final static String NULL_VALUE_ = "never"; + + private final static int DEFAULT_INT_VALUE = 1; + private final static GregorianCalendar + DEFAULT_DATE_VALUE = new GregorianCalendar(); + + private String m_key = null; + private int m_type = Type.STRING_TYPE; + private int m_operation = Operation.EQUALS_OPER; + private String m_value = ""; + private String m_default = null; + private String m_pattern = null; + + public void setDefault( String value ) + { + this.m_default = value; + } + + public void setKey( String value ) + { + this.m_key = value; + } + + public void setOperation( Operation value ) + { + int newOperation = Operation.toOperation( value.getValue() ); + if( newOperation == Operation.NOW_VALUE ) + { + this.m_operation = Operation.EQUALS_OPER; + this.setValue( this.NOW_VALUE_ ); + } + else if( newOperation == Operation.NULL_VALUE ) + { + this.m_operation = Operation.EQUALS_OPER; + this.setValue( this.NULL_VALUE_ ); + } + else + { + this.m_operation = newOperation; + } + } + + public void setPattern( String value ) + { + this.m_pattern = value; + } + + public void setType( Type value ) + { + this.m_type = Type.toType( value.getValue() ); + } + + public void setValue( String value ) + { + this.m_value = value; + } + + protected void executeOn( Properties props ) + throws BuildException + { + checkParameters(); + + // m_type may be null because it wasn't set + try + { + if( m_type == Type.INTEGER_TYPE ) + { + executeInteger( ( String )props.get( m_key ) ); + } + else if( m_type == Type.DATE_TYPE ) + { + executeDate( ( String )props.get( m_key ) ); + } + else if( m_type == Type.STRING_TYPE ) + { + executeString( ( String )props.get( m_key ) ); + } + else + { + throw new BuildException( "Unknown operation type: " + m_type + "" ); + } + } + catch( NullPointerException npe ) + { + // Default to string type + // which means do nothing + npe.printStackTrace(); + } + // Insert as a string by default + props.put( m_key, m_value ); + + } + + /** + * Check if parameter combinations can be supported + * + * @exception BuildException Description of Exception + */ + private void checkParameters() + throws BuildException + { + if( m_type == Type.STRING_TYPE && + m_operation == Operation.DECREMENT_OPER ) + { + throw new BuildException( "- is not suported for string properties (key:" + m_key + ")" ); + } + if( m_value == null && m_default == null ) + { + throw new BuildException( "value and/or default must be specified (key:" + m_key + ")" ); + } + if( m_key == null ) + { + throw new BuildException( "key is mandatory" ); + } + if( m_type == Type.STRING_TYPE && + m_pattern != null ) + { + throw new BuildException( "pattern is not suported for string properties (key:" + m_key + ")" ); + } + } + + /** + * Handle operations for type date. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeDate( String oldValue ) + throws BuildException + { + GregorianCalendar value = new GregorianCalendar(); + GregorianCalendar newValue = new GregorianCalendar(); + + if( m_pattern == null ) + m_pattern = "yyyy/MM/dd HH:mm"; + DateFormat fmt = new SimpleDateFormat( m_pattern ); + + // special case + if( m_default != null && + NOW_VALUE_.equals( m_default.toLowerCase() ) && + ( m_operation == Operation.INCREMENT_OPER || + m_operation == Operation.DECREMENT_OPER ) ) + { + oldValue = null; + } + + if( oldValue != null ) + { + try + { + value.setTime( fmt.parse( oldValue ) ); + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + + if( m_value != null ) + { + if( NOW_VALUE_.equals( m_value.toLowerCase() ) ) + { + value.setTime( new Date() ); + } + else if( NULL_VALUE_.equals( m_value.toLowerCase() ) ) + { + value = null; + } + else + { + try + { + value.setTime( fmt.parse( m_value ) ); + } + catch( Exception ex ) + { + // obviously not a date, try a simple int + try + { + int offset = Integer.parseInt( m_value ); + value.clear(); + value.set( Calendar.DAY_OF_YEAR, offset ); + } + catch( Exception ex_ ) + { + value.clear(); + value.set( Calendar.DAY_OF_YEAR, 1 ); + } + } + + } + } + + if( m_default != null && oldValue == null ) + { + if( NOW_VALUE_.equals( m_default.toLowerCase() ) ) + { + value.setTime( new Date() ); + } + else if( NULL_VALUE_.equals( m_default.toLowerCase() ) ) + { + value = null; + } + else + { + try + { + value.setTime( fmt.parse( m_default ) ); + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue.add( Calendar.SECOND, value.get( Calendar.SECOND ) ); + newValue.add( Calendar.MINUTE, value.get( Calendar.MINUTE ) ); + newValue.add( Calendar.HOUR_OF_DAY, value.get( Calendar.HOUR_OF_DAY ) ); + newValue.add( Calendar.DAY_OF_YEAR, value.get( Calendar.DAY_OF_YEAR ) ); + } + else if( m_operation == Operation.DECREMENT_OPER ) + { + newValue.add( Calendar.SECOND, -1 * value.get( Calendar.SECOND ) ); + newValue.add( Calendar.MINUTE, -1 * value.get( Calendar.MINUTE ) ); + newValue.add( Calendar.HOUR_OF_DAY, -1 * value.get( Calendar.HOUR_OF_DAY ) ); + newValue.add( Calendar.DAY_OF_YEAR, -1 * value.get( Calendar.DAY_OF_YEAR ) ); + } + if( newValue != null ) + { + m_value = fmt.format( newValue.getTime() ); + } + else + { + m_value = ""; + } + } + + + /** + * Handle operations for type int. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeInteger( String oldValue ) + throws BuildException + { + int value = 0; + int newValue = 0; + + DecimalFormat fmt = ( m_pattern != null ) ? new DecimalFormat( m_pattern ) + : new DecimalFormat(); + + if( oldValue != null ) + { + try + { + value = fmt.parse( oldValue ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + if( m_value != null ) + { + try + { + value = fmt.parse( m_value ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + if( m_default != null && oldValue == null ) + { + try + { + value = fmt.parse( m_default ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue = ++value; + } + else if( m_operation == Operation.DECREMENT_OPER ) + { + newValue = --value; + } + m_value = fmt.format( newValue ); + } + + /** + * Handle operations for type string. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeString( String oldValue ) + throws BuildException + { + String value = ""; + String newValue = ""; + + // the order of events is, of course, very important here + // default initially to the old value + if( oldValue != null ) + { + value = oldValue; + } + // but if a value is specified, use it + if( m_value != null ) + { + value = m_value; + } + // even if value is specified, ignore it and set to the default + // value if it is specified and there is no previous value + if( m_default != null && oldValue == null ) + { + value = m_default; + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue += value; + } + m_value = newValue; + } + + /** + * Enumerated attribute with the values "+", "-", "=", "now" and + * "never". + * + * @author RT + */ + public static class Operation extends EnumeratedAttribute + { + + // Property type operations + public final static int INCREMENT_OPER = 0; + public final static int DECREMENT_OPER = 1; + public final static int EQUALS_OPER = 2; + + // Special values + public final static int NOW_VALUE = 3; + public final static int NULL_VALUE = 4; + + public static int toOperation( String oper ) + { + if( "+".equals( oper ) ) + { + return INCREMENT_OPER; + } + else if( "-".equals( oper ) ) + { + return DECREMENT_OPER; + } + else if( NOW_VALUE_.equals( oper ) ) + { + return NOW_VALUE; + } + else if( NULL_VALUE_.equals( oper ) ) + { + return NULL_VALUE; + } + return EQUALS_OPER; + } + + public String[] getValues() + { + return new String[]{"+", "-", "=", NOW_VALUE_, NULL_VALUE_}; + } + } + + /** + * Enumerated attribute with the values "int", "date" and "string". + * + * @author RT + */ + public static class Type extends EnumeratedAttribute + { + + // Property types + public final static int INTEGER_TYPE = 0; + public final static int DATE_TYPE = 1; + public final static int STRING_TYPE = 2; + + public static int toType( String type ) + { + if( "int".equals( type ) ) + { + return INTEGER_TYPE; + } + else if( "date".equals( type ) ) + { + return DATE_TYPE; + } + return STRING_TYPE; + } + + public String[] getValues() + { + return new String[]{"int", "date", "string"}; + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java new file mode 100644 index 000000000..1aa5ab674 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.Move; +import org.apache.tools.ant.types.Mapper; + +/** + * @author dIon Gillard + * dion@multitask.com.au + * @author Stefan Bodewig + * @version 1.2 + */ +public class RenameExtensions extends MatchingTask +{ + + private String fromExtension = ""; + private String toExtension = ""; + private boolean replace = false; + + private Mapper.MapperType globType; + private File srcDir; + + + /** + * Creates new RenameExtensions + */ + public RenameExtensions() + { + super(); + globType = new Mapper.MapperType(); + globType.setValue( "glob" ); + } + + /** + * store fromExtension * + * + * @param from The new FromExtension value + */ + public void setFromExtension( String from ) + { + fromExtension = from; + } + + /** + * store replace attribute - this determines whether the target file should + * be overwritten if present + * + * @param replace The new Replace value + */ + public void setReplace( boolean replace ) + { + this.replace = replace; + } + + /** + * Set the source dir to find the files to be renamed. + * + * @param srcDir The new SrcDir value + */ + public void setSrcDir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * store toExtension * + * + * @param to The new ToExtension value + */ + public void setToExtension( String to ) + { + toExtension = to; + } + + /** + * Executes the task, i.e. does the actual compiler call + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // first off, make sure that we've got a from and to extension + if( fromExtension == null || toExtension == null || srcDir == null ) + { + throw new BuildException( "srcDir, fromExtension and toExtension " + + "attributes must be set!" ); + } + + log( "DEPRECATED - The renameext task is deprecated. Use move instead.", + Project.MSG_WARN ); + log( "Replace this with:", Project.MSG_INFO ); + log( "", + Project.MSG_INFO ); + log( " ", Project.MSG_INFO ); + log( " ", Project.MSG_INFO ); + log( "", Project.MSG_INFO ); + log( "using the same patterns on as you\'ve used here", + Project.MSG_INFO ); + + Move move = ( Move )project.createTask( "move" ); + move.setOwningTarget( target ); + move.setTaskName( getTaskName() ); + move.setLocation( getLocation() ); + move.setTodir( srcDir ); + move.setOverwrite( replace ); + + fileset.setDir( srcDir ); + move.addFileset( fileset ); + + Mapper me = move.createMapper(); + me.setType( globType ); + me.setFrom( "*" + fromExtension ); + me.setTo( "*" + toExtension ); + + move.execute(); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java new file mode 100644 index 000000000..ece6ed56e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.RegularExpression; +import org.apache.tools.ant.types.Substitution; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.regexp.Regexp; + +/** + *

              + * Task to do regular expression string replacements in a text
              + * file.  The input file(s) must be able to be properly processed by
              + * a Reader instance.  That is, they must be text only, no binary.
              + *
              + * The syntax of the regular expression depends on the implemtation that
              + * you choose to use. The system property ant.regexp.regexpimpl
              + * will be the classname of the implementation that will be used (the default is
              + * org.apache.tools.ant.util.regexp.JakartaOroRegexp and requires
              + * the Jakarta Oro Package). 
              + * For jdk  <= 1.3, there are two available implementations:
              + *   org.apache.tools.ant.util.regexp.JakartaOroRegexp (the default)
              + *        Requires  the jakarta-oro package
              + *
              + *   org.apache.tools.ant.util.regexp.JakartaRegexpRegexp
              + *        Requires the jakarta-regexp package
              + *
              + * For jdk >= 1.4 an additional implementation is available:
              + *   org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp
              + *        Requires the jdk 1.4 built in regular expression package.
              + * 
              Usage: Call Syntax: <replaceregexp file="file" match="pattern" + * replace="pattern" flags="options"? byline="true|false"? > + * regularexpression? substitution? fileset* </replaceregexp> NOTE: You + * must have either the file attribute specified, or at least one fileset + * subelement to operation on. You may not have the file attribute specified if + * you nest fileset elements inside this task. Also, you cannot specify both + * match and a regular expression subelement at the same time, nor can you + * specify the replace attribute and the substitution subelement at the same + * time. Attributes: file --> A single file to operation on (mutually + * exclusive with the fileset subelements) match --> The Regular expression + * to match replace --> The Expression replacement string flags --> The + * options to give to the replacement g = Substitute all occurrences. default is + * to replace only the first one i = Case insensitive match byline --> Should + * this file be processed a single line at a time (default is false) "true" + * indicates to perform replacement on a line by line basis "false" indicates to + * perform replacement on the whole file at once. Example: The following call + * could be used to replace an old property name in a ".properties" file with a + * new name. In the replace attribute, you can refer to any part of the match + * expression in parenthesis using backslash followed by a number like '\1'. + * <replaceregexp file="test.properties" match="MyProperty=(.*)" + * replace="NewProperty=\1" byline="true" />
              + * + * @author Matthew Inger + */ +public class ReplaceRegExp extends Task +{ + + private FileUtils fileUtils = FileUtils.newFileUtils(); + private boolean byline; + + private File file; + private Vector filesets; + private String flags;// Keep jdk 1.1 compliant so others can use this + private RegularExpression regex; + private Substitution subs; + + /** + * Default Constructor + */ + public ReplaceRegExp() + { + super(); + this.file = null; + this.filesets = new Vector(); + this.flags = ""; + this.byline = false; + + this.regex = null; + this.subs = null; + } + + public void setByLine( String byline ) + { + Boolean res = Boolean.valueOf( byline ); + if( res == null ) + res = Boolean.FALSE; + this.byline = res.booleanValue(); + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setFlags( String flags ) + { + this.flags = flags; + } + + public void setMatch( String match ) + { + if( regex != null ) + throw new BuildException( "Only one regular expression is allowed" ); + + regex = new RegularExpression(); + regex.setPattern( match ); + } + + public void setReplace( String replace ) + { + if( subs != null ) + throw new BuildException( "Only one substitution expression is allowed" ); + + subs = new Substitution(); + subs.setExpression( replace ); + } + + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + public RegularExpression createRegularExpression() + { + if( regex != null ) + throw new BuildException( "Only one regular expression is allowed." ); + + regex = new RegularExpression(); + return regex; + } + + public Substitution createSubstitution() + { + if( subs != null ) + throw new BuildException( "Only one substitution expression is allowed" ); + + subs = new Substitution(); + return subs; + } + + public void execute() + throws BuildException + { + if( regex == null ) + throw new BuildException( "No expression to match." ); + if( subs == null ) + throw new BuildException( "Nothing to replace expression with." ); + + if( file != null && filesets.size() > 0 ) + throw new BuildException( "You cannot supply the 'file' attribute and filesets at the same time." ); + + int options = 0; + + if( flags.indexOf( 'g' ) != -1 ) + options |= Regexp.REPLACE_ALL; + + if( flags.indexOf( 'i' ) != -1 ) + options |= Regexp.MATCH_CASE_INSENSITIVE; + + if( flags.indexOf( 'm' ) != -1 ) + options |= Regexp.MATCH_MULTILINE; + + if( flags.indexOf( 's' ) != -1 ) + options |= Regexp.MATCH_SINGLELINE; + + if( file != null && file.exists() ) + { + try + { + doReplace( file, options ); + } + catch( IOException e ) + { + log( "An error occurred processing file: '" + file.getAbsolutePath() + "': " + e.toString(), + Project.MSG_ERR ); + } + } + else if( file != null ) + { + log( "The following file is missing: '" + file.getAbsolutePath() + "'", + Project.MSG_ERR ); + } + + int sz = filesets.size(); + for( int i = 0; i < sz; i++ ) + { + FileSet fs = ( FileSet )( filesets.elementAt( i ) ); + DirectoryScanner ds = fs.getDirectoryScanner( getProject() ); + + String files[] = ds.getIncludedFiles(); + for( int j = 0; j < files.length; j++ ) + { + File f = new File( files[j] ); + if( f.exists() ) + { + try + { + doReplace( f, options ); + } + catch( Exception e ) + { + log( "An error occurred processing file: '" + f.getAbsolutePath() + "': " + e.toString(), + Project.MSG_ERR ); + } + } + else + { + log( "The following file is missing: '" + file.getAbsolutePath() + "'", + Project.MSG_ERR ); + } + } + } + } + + + protected String doReplace( RegularExpression r, + Substitution s, + String input, + int options ) + { + String res = input; + Regexp regexp = r.getRegexp( project ); + + if( regexp.matches( input, options ) ) + { + res = regexp.substitute( input, s.getExpression( project ), options ); + } + + return res; + } + + /** + * Perform the replace on the entire file + * + * @param f Description of Parameter + * @param options Description of Parameter + * @exception IOException Description of Exception + */ + protected void doReplace( File f, int options ) + throws IOException + { + File parentDir = new File( new File( f.getAbsolutePath() ).getParent() ); + File temp = fileUtils.createTempFile( "replace", ".txt", parentDir ); + + FileReader r = null; + FileWriter w = null; + + try + { + r = new FileReader( f ); + w = new FileWriter( temp ); + + BufferedReader br = new BufferedReader( r ); + BufferedWriter bw = new BufferedWriter( w ); + PrintWriter pw = new PrintWriter( bw ); + + boolean changes = false; + + log( "Replacing pattern '" + regex.getPattern( project ) + "' with '" + subs.getExpression( project ) + + "' in '" + f.getPath() + "'" + + ( byline ? " by line" : "" ) + + ( flags.length() > 0 ? " with flags: '" + flags + "'" : "" ) + + ".", + Project.MSG_WARN ); + + if( byline ) + { + LineNumberReader lnr = new LineNumberReader( br ); + String line = null; + + while( ( line = lnr.readLine() ) != null ) + { + String res = doReplace( regex, subs, line, options ); + if( !res.equals( line ) ) + changes = true; + + pw.println( res ); + } + pw.flush(); + } + else + { + int flen = ( int )( f.length() ); + char tmpBuf[] = new char[flen]; + int numread = 0; + int totread = 0; + while( numread != -1 && totread < flen ) + { + numread = br.read( tmpBuf, totread, flen ); + totread += numread; + } + + String buf = new String( tmpBuf ); + + String res = doReplace( regex, subs, buf, options ); + if( !res.equals( buf ) ) + changes = true; + + pw.println( res ); + pw.flush(); + } + + r.close(); + r = null; + w.close(); + w = null; + + if( changes ) + { + f.delete(); + temp.renameTo( f ); + } + else + { + temp.delete(); + } + } + finally + { + try + { + if( r != null ) + r.close(); + } + catch( Exception e ) + {} + ; + + try + { + if( w != null ) + r.close(); + } + catch( Exception e ) + {} + ; + } + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Rpm.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Rpm.java new file mode 100644 index 000000000..3811e701b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Rpm.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.taskdefs.PumpStreamHandler; +import org.apache.tools.ant.types.Commandline; + +/** + * @author lucas@collab.net + */ +public class Rpm extends Task +{ + + /** + * the rpm command to use + */ + private String command = "-bb"; + + /** + * clean BUILD directory + */ + private boolean cleanBuildDir = false; + + /** + * remove spec file + */ + private boolean removeSpec = false; + + /** + * remove sources + */ + private boolean removeSource = false; + + /** + * the file to direct standard error from the command + */ + private File error; + + /** + * the file to direct standard output from the command + */ + private File output; + + /** + * the spec file + */ + private String specFile; + + /** + * the rpm top dir + */ + private File topDir; + + public void setCleanBuildDir( boolean cbd ) + { + cleanBuildDir = cbd; + } + + public void setCommand( String c ) + { + this.command = c; + } + + public void setError( File error ) + { + this.error = error; + } + + public void setOutput( File output ) + { + this.output = output; + } + + public void setRemoveSource( boolean rs ) + { + removeSource = rs; + } + + public void setRemoveSpec( boolean rs ) + { + removeSpec = rs; + } + + public void setSpecFile( String sf ) + { + if( ( sf == null ) || ( sf.trim().equals( "" ) ) ) + { + throw new BuildException( "You must specify a spec file", location ); + } + this.specFile = sf; + } + + public void setTopDir( File td ) + { + this.topDir = td; + } + + public void execute() + throws BuildException + { + + Commandline toExecute = new Commandline(); + + toExecute.setExecutable( "rpm" ); + if( topDir != null ) + { + toExecute.createArgument().setValue( "--define" ); + toExecute.createArgument().setValue( "_topdir" + topDir ); + } + + toExecute.createArgument().setLine( command ); + + if( cleanBuildDir ) + { + toExecute.createArgument().setValue( "--clean" ); + } + if( removeSpec ) + { + toExecute.createArgument().setValue( "--rmspec" ); + } + if( removeSource ) + { + toExecute.createArgument().setValue( "--rmsource" ); + } + + toExecute.createArgument().setValue( "SPECS/" + specFile ); + + ExecuteStreamHandler streamhandler = null; + OutputStream outputstream = null; + OutputStream errorstream = null; + if( error == null && output == null ) + { + streamhandler = new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ); + } + else + { + if( output != null ) + { + try + { + outputstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + outputstream = new LogOutputStream( this, Project.MSG_INFO ); + } + if( error != null ) + { + try + { + errorstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( error ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + errorstream = new LogOutputStream( this, Project.MSG_WARN ); + } + streamhandler = new PumpStreamHandler( outputstream, errorstream ); + } + + Execute exe = new Execute( streamhandler, null ); + + exe.setAntRun( project ); + if( topDir == null ) + topDir = project.getBaseDir(); + exe.setWorkingDirectory( topDir ); + + exe.setCommandline( toExecute.getCommandline() ); + try + { + exe.execute(); + log( "Building the RPM based on the " + specFile + " file" ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + if( output != null ) + { + try + { + outputstream.close(); + } + catch( IOException e ) + {} + } + if( error != null ) + { + try + { + errorstream.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Script.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Script.java new file mode 100644 index 000000000..43a545ec1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Script.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import com.ibm.bsf.BSFException; +import com.ibm.bsf.BSFManager; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Execute a script + * + * @author Sam Ruby rubys@us.ibm.com + */ +public class Script extends Task +{ + private String script = ""; + private Hashtable beans = new Hashtable(); + private String language; + + /** + * Defines the language (required). + * + * @param language The new Language value + */ + public void setLanguage( String language ) + { + this.language = language; + } + + /** + * Load the script from an external file + * + * @param fileName The new Src value + */ + public void setSrc( String fileName ) + { + File file = new File( fileName ); + if( !file.exists() ) + throw new BuildException( "file " + fileName + " not found." ); + + int count = ( int )file.length(); + byte data[] = new byte[count]; + + try + { + FileInputStream inStream = new FileInputStream( file ); + inStream.read( data ); + inStream.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + script += new String( data ); + } + + /** + * Defines the script. + * + * @param text The feature to be added to the Text attribute + */ + public void addText( String text ) + { + this.script += text; + } + + /** + * Do the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + try + { + addBeans( project.getProperties() ); + addBeans( project.getUserProperties() ); + addBeans( project.getTargets() ); + addBeans( project.getReferences() ); + + beans.put( "project", getProject() ); + + beans.put( "self", this ); + + BSFManager manager = new BSFManager(); + + for( Enumeration e = beans.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + Object value = beans.get( key ); + manager.declareBean( key, value, value.getClass() ); + } + + // execute the script + manager.exec( language, "", 0, 0, script ); + } + catch( BSFException be ) + { + Throwable t = be; + Throwable te = be.getTargetException(); + if( te != null ) + { + if( te instanceof BuildException ) + { + throw ( BuildException )te; + } + else + { + t = te; + } + } + throw new BuildException( t ); + } + } + + /** + * Add a list of named objects to the list to be exported to the script + * + * @param dictionary The feature to be added to the Beans attribute + */ + private void addBeans( Hashtable dictionary ) + { + for( Enumeration e = dictionary.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + + boolean isValid = key.length() > 0 && + Character.isJavaIdentifierStart( key.charAt( 0 ) ); + + for( int i = 1; isValid && i < key.length(); i++ ) + isValid = Character.isJavaIdentifierPart( key.charAt( i ) ); + + if( isValid ) + beans.put( key, dictionary.get( key ) ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/StyleBook.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/StyleBook.java new file mode 100644 index 000000000..0f37fc687 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/StyleBook.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Java; + +/** + * Basic task for apache stylebook. + * + * @author Peter Donald + * @author Marcus + * Börger + */ +public class StyleBook + extends Java +{ + protected File m_book; + protected String m_loaderConfig; + protected File m_skinDirectory; + protected File m_targetDirectory; + + public StyleBook() + { + setClassname( "org.apache.stylebook.StyleBook" ); + setFork( true ); + setFailonerror( true ); + } + + public void setBook( final File book ) + { + m_book = book; + } + + public void setLoaderConfig( final String loaderConfig ) + { + m_loaderConfig = loaderConfig; + } + + public void setSkinDirectory( final File skinDirectory ) + { + m_skinDirectory = skinDirectory; + } + + public void setTargetDirectory( final File targetDirectory ) + { + m_targetDirectory = targetDirectory; + } + + public void execute() + throws BuildException + { + + if( null == m_targetDirectory ) + { + throw new BuildException( "TargetDirectory attribute not set." ); + } + + if( null == m_skinDirectory ) + { + throw new BuildException( "SkinDirectory attribute not set." ); + } + + if( null == m_book ) + { + throw new BuildException( "book attribute not set." ); + } + + createArg().setValue( "targetDirectory=" + m_targetDirectory ); + createArg().setValue( m_book.toString() ); + createArg().setValue( m_skinDirectory.toString() ); + if( null != m_loaderConfig ) + { + createArg().setValue( "loaderConfig=" + m_loaderConfig ); + } + + super.execute(); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Test.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Test.java new file mode 100644 index 000000000..3f248fdc4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Test.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Java; + +/** + * @author Peter Donald + */ +public class Test + extends Java +{ + + protected Vector m_tests = new Vector(); + + public Test() + { + setClassname( "org.apache.testlet.engine.TextTestEngine" ); + } + + public void setForceShowTrace( final boolean forceShowTrace ) + { + createArg().setValue( "-f=" + forceShowTrace ); + } + + public void setShowBanner( final String showBanner ) + { + createArg().setValue( "-b=" + showBanner ); + } + + public void setShowSuccess( final boolean showSuccess ) + { + createArg().setValue( "-s=" + showSuccess ); + } + + public void setShowTrace( final boolean showTrace ) + { + createArg().setValue( "-t=" + showTrace ); + } + + public TestletEntry createTestlet() + { + final TestletEntry entry = new TestletEntry(); + m_tests.addElement( entry ); + return entry; + } + + public void execute() + throws BuildException + { + + final int size = m_tests.size(); + + for( int i = 0; i < size; i++ ) + { + createArg().setValue( m_tests.elementAt( i ).toString() ); + } + + super.execute(); + } + + protected final static class TestletEntry + { + + protected String m_testname = ""; + + public void addText( final String testname ) + { + m_testname += testname; + } + + public String toString() + { + return m_testname; + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java new file mode 100644 index 000000000..e608bfcd2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.apache.tools.ant.taskdefs.XSLTLogger; +import org.apache.tools.ant.taskdefs.XSLTLoggerAware; + +/** + * Concrete liaison for XSLT processor implementing TraX. (ie JAXP 1.1) + * + * @author Sam Ruby + * @author Davanum Srinivas + * @author Stephane Bailliez + */ +public class TraXLiaison implements XSLTLiaison, ErrorListener, XSLTLoggerAware +{ + + /** + * The trax TransformerFactory + */ + private TransformerFactory tfactory = null; + + /** + * stylesheet stream, close it asap + */ + private FileInputStream xslStream = null; + + /** + * Stylesheet template + */ + private Templates templates = null; + + /** + * transformer + */ + private Transformer transformer = null; + + private XSLTLogger logger; + + public TraXLiaison() + throws Exception + { + tfactory = TransformerFactory.newInstance(); + tfactory.setErrorListener( this ); + } + + public void setLogger( XSLTLogger l ) + { + logger = l; + } + + public void setOutputtype( String type ) + throws Exception + { + transformer.setOutputProperty( OutputKeys.METHOD, type ); + } + +//------------------- IMPORTANT + // 1) Don't use the StreamSource(File) ctor. It won't work with + // xalan prior to 2.2 because of systemid bugs. + + // 2) Use a stream so that you can close it yourself quickly + // and avoid keeping the handle until the object is garbaged. + // (always keep control), otherwise you won't be able to delete + // the file quickly on windows. + + // 3) Always set the systemid to the source for imports, includes... + // in xsl and xml... + + public void setStylesheet( File stylesheet ) + throws Exception + { + xslStream = new FileInputStream( stylesheet ); + StreamSource src = new StreamSource( xslStream ); + src.setSystemId( getSystemId( stylesheet ) ); + templates = tfactory.newTemplates( src ); + transformer = templates.newTransformer(); + transformer.setErrorListener( this ); + } + + public void addParam( String name, String value ) + { + transformer.setParameter( name, value ); + } + + public void error( TransformerException e ) + { + logError( e, "Error" ); + } + + public void fatalError( TransformerException e ) + { + logError( e, "Fatal Error" ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileInputStream fis = null; + FileOutputStream fos = null; + try + { + fis = new FileInputStream( infile ); + fos = new FileOutputStream( outfile ); + StreamSource src = new StreamSource( fis ); + src.setSystemId( getSystemId( infile ) ); + StreamResult res = new StreamResult( fos ); + // not sure what could be the need of this... + res.setSystemId( getSystemId( outfile ) ); + + transformer.transform( src, res ); + } + finally + { + // make sure to close all handles, otherwise the garbage + // collector will close them...whenever possible and + // Windows may complain about not being able to delete files. + try + { + if( xslStream != null ) + { + xslStream.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fis != null ) + { + fis.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fos != null ) + { + fos.close(); + } + } + catch( IOException ignored ) + {} + } + } + + public void warning( TransformerException e ) + { + logError( e, "Warning" ); + } + + // make sure that the systemid is made of '/' and not '\' otherwise + // crimson will complain that it cannot resolve relative entities + // because it grabs the base uri via lastIndexOf('/') without + // making sure it is really a /'ed path + protected String getSystemId( File file ) + { + String path = file.getAbsolutePath(); + path = path.replace( '\\', '/' ); + return FILE_PROTOCOL_PREFIX + path; + } + + private void logError( TransformerException e, String type ) + { + StringBuffer msg = new StringBuffer(); + if( e.getLocator() != null ) + { + if( e.getLocator().getSystemId() != null ) + { + String url = e.getLocator().getSystemId(); + if( url.startsWith( "file:///" ) ) + url = url.substring( 8 ); + msg.append( url ); + } + else + { + msg.append( "Unknown file" ); + } + if( e.getLocator().getLineNumber() != -1 ) + { + msg.append( ":" + e.getLocator().getLineNumber() ); + if( e.getLocator().getColumnNumber() != -1 ) + { + msg.append( ":" + e.getLocator().getColumnNumber() ); + } + } + } + msg.append( ": " + type + "! " ); + msg.append( e.getMessage() ); + if( e.getCause() != null ) + { + msg.append( " Cause: " + e.getCause() ); + } + + logger.log( msg.toString() ); + } + +}//-- TraXLiaison diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java new file mode 100644 index 000000000..02abefc30 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java @@ -0,0 +1,648 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Parser; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.ParserAdapter; + +/** + * The XMLValidateTask checks that an XML document is valid, with a + * SAX validating parser. + * + * @author Raphael Pierquin + * raphael.pierquin@agisphere.com + */ +public class XMLValidateTask extends Task +{ + + /** + * The default implementation parser classname used by the task to process + * validation. + */ + // The crimson implementation is shipped with ant. + public static String DEFAULT_XML_READER_CLASSNAME = "org.apache.crimson.parser.XMLReaderImpl"; + + protected static String INIT_FAILED_MSG = "Could not start xml validation: "; + + // ant task properties + // defaults + protected boolean failOnError = true; + protected boolean warn = true; + protected boolean lenient = false; + protected String readerClassName = DEFAULT_XML_READER_CLASSNAME; + + protected File file = null;// file to be validated + protected Vector filesets = new Vector(); + + /** + * the parser is viewed as a SAX2 XMLReader. If a SAX1 parser is specified, + * it's wrapped in an adapter that make it behave as a XMLReader. a more + * 'standard' way of doing this would be to use the JAXP1.1 SAXParser + * interface. + */ + protected XMLReader xmlReader = null;// XMLReader used to validation process + protected ValidatorErrorHandler errorHandler + = new ValidatorErrorHandler();// to report sax parsing errors + protected Hashtable features = new Hashtable(); + + /** + * The list of configured DTD locations + */ + public Vector dtdLocations = new Vector();// sets of file to be validated + protected Path classpath; + + /** + * Specify the class name of the SAX parser to be used. (optional) + * + * @param className should be an implementation of SAX2 org.xml.sax.XMLReader + * or SAX2 org.xml.sax.Parser.

              + * + * if className is an implementation of org.xml.sax.Parser + * , {@link #setLenient(boolean)}, will be ignored.

              + * + * if not set, the default {@link #DEFAULT_XML_READER_CLASSNAME} will + * be used. + * @see org.xml.sax.XMLReader + * @see org.xml.sax.Parser + */ + public void setClassName( String className ) + { + + readerClassName = className; + } + + + /** + * Specify the classpath to be searched to load the parser (optional) + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * @param r The new ClasspathRef value + * @see #setClasspath + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Specify how parser error are to be handled.

              + * + * If set to true (default), throw a buildException if the + * parser yields an error. + * + * @param fail The new FailOnError value + */ + public void setFailOnError( boolean fail ) + { + + failOnError = fail; + } + + /** + * specifify the file to be checked + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Specify whether the parser should be validating. Default is true + * .

              + * + * If set to false, the validation will fail only if the parsed document is + * not well formed XML.

              + * + * this option is ignored if the specified class with {@link + * #setClassName(String)} is not a SAX2 XMLReader. + * + * @param bool The new Lenient value + */ + public void setLenient( boolean bool ) + { + + lenient = bool; + } + + /** + * Specify how parser error are to be handled.

              + * + * If set to true + * + *(default), log a warn message for each SAX warn event. + * + * @param bool The new Warn value + */ + public void setWarn( boolean bool ) + { + + warn = bool; + } + + /** + * specifify a set of file to be checked + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * @return Description of the Returned Value + * @see #setClasspath + */ + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + /** + * Create a DTD location record. This stores the location of a DTD. The DTD + * is identified by its public Id. The location may either be a file + * location or a resource location. + * + * @return Description of the Returned Value + */ + public DTDLocation createDTD() + { + DTDLocation dtdLocation = new DTDLocation(); + dtdLocations.addElement( dtdLocation ); + + return dtdLocation; + } + + public void execute() + throws BuildException + { + + int fileProcessed = 0; + if( file == null && ( filesets.size() == 0 ) ) + { + throw new BuildException( "Specify at least one source - a file or a fileset." ); + } + + initValidator(); + + if( file != null ) + { + if( file.exists() && file.canRead() && file.isFile() ) + { + doValidate( file ); + fileProcessed++; + } + else + { + String errorMsg = "File " + file + " cannot be read"; + if( failOnError ) + throw new BuildException( errorMsg ); + else + log( errorMsg, Project.MSG_ERR ); + } + } + + for( int i = 0; i < filesets.size(); i++ ) + { + + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] files = ds.getIncludedFiles(); + + for( int j = 0; j < files.length; j++ ) + { + File srcFile = new File( fs.getDir( project ), files[j] ); + doValidate( srcFile ); + fileProcessed++; + } + } + log( fileProcessed + " file(s) have been successfully validated." ); + } + + protected EntityResolver getEntityResolver() + { + LocalResolver resolver = new LocalResolver(); + + for( Enumeration i = dtdLocations.elements(); i.hasMoreElements(); ) + { + DTDLocation location = ( DTDLocation )i.nextElement(); + resolver.registerDTD( location ); + } + return resolver; + } + + /* + * set a feature on the parser. + * TODO: find a way to set any feature from build.xml + */ + private boolean setFeature( String feature, boolean value, boolean warn ) + { + + boolean toReturn = false; + try + { + xmlReader.setFeature( feature, value ); + toReturn = true; + } + catch( SAXNotRecognizedException e ) + { + if( warn ) + log( "Could not set feature '" + + feature + + "' because the parser doesn't recognize it", + Project.MSG_WARN ); + } + catch( SAXNotSupportedException e ) + { + if( warn ) + log( "Could not set feature '" + + feature + + "' because the parser doesn't support it", + Project.MSG_WARN ); + } + return toReturn; + } + + /* + * parse the file + */ + private void doValidate( File afile ) + { + try + { + log( "Validating " + afile.getName() + "... ", Project.MSG_VERBOSE ); + errorHandler.init( afile ); + InputSource is = new InputSource( new FileReader( afile ) ); + String uri = "file:" + afile.getAbsolutePath().replace( '\\', '/' ); + for( int index = uri.indexOf( '#' ); index != -1; + index = uri.indexOf( '#' ) ) + { + uri = uri.substring( 0, index ) + "%23" + uri.substring( index + 1 ); + } + is.setSystemId( uri ); + xmlReader.parse( is ); + } + catch( SAXException ex ) + { + if( failOnError ) + throw new BuildException( "Could not validate document " + afile ); + } + catch( IOException ex ) + { + throw new BuildException( "Could not validate document " + afile, ex ); + } + + if( errorHandler.getFailure() ) + { + if( failOnError ) + throw new BuildException( afile + " is not a valid XML document." ); + else + log( afile + " is not a valid XML document", Project.MSG_ERR ); + } + } + + /** + * init the parser : load the parser class, and set features if necessary + */ + private void initValidator() + { + + try + { + // load the parser class + // with JAXP, we would use a SAXParser factory + Class readerClass = null; + //Class readerImpl = null; + //Class parserImpl = null; + if( classpath != null ) + { + AntClassLoader loader = new AntClassLoader( project, classpath ); +// loader.addSystemPackageRoot("org.xml"); // needed to avoid conflict + readerClass = loader.loadClass( readerClassName ); + AntClassLoader.initializeClass( readerClass ); + } + else + readerClass = Class.forName( readerClassName ); + + // then check it implements XMLReader + if( XMLReader.class.isAssignableFrom( readerClass ) ) + { + + xmlReader = ( XMLReader )readerClass.newInstance(); + log( "Using SAX2 reader " + readerClassName, Project.MSG_VERBOSE ); + } + else + { + + // see if it is a SAX1 Parser + if( Parser.class.isAssignableFrom( readerClass ) ) + { + Parser parser = ( Parser )readerClass.newInstance(); + xmlReader = new ParserAdapter( parser ); + log( "Using SAX1 parser " + readerClassName, Project.MSG_VERBOSE ); + } + else + { + throw new BuildException( INIT_FAILED_MSG + + readerClassName + + " implements nor SAX1 Parser nor SAX2 XMLReader." ); + } + } + } + catch( ClassNotFoundException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + catch( InstantiationException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + + xmlReader.setEntityResolver( getEntityResolver() ); + xmlReader.setErrorHandler( errorHandler ); + + if( !( xmlReader instanceof ParserAdapter ) ) + { + // turn validation on + if( !lenient ) + { + boolean ok = setFeature( "http://xml.org/sax/features/validation", true, true ); + if( !ok ) + { + throw new BuildException( INIT_FAILED_MSG + + readerClassName + + " doesn't provide validation" ); + } + } + // set other features + Enumeration enum = features.keys(); + while( enum.hasMoreElements() ) + { + String featureId = ( String )enum.nextElement(); + setFeature( featureId, ( ( Boolean )features.get( featureId ) ).booleanValue(), true ); + } + } + } + + public static class DTDLocation + { + private String publicId = null; + private String location = null; + + public void setLocation( String location ) + { + this.location = location; + } + + public void setPublicId( String publicId ) + { + this.publicId = publicId; + } + + public String getLocation() + { + return location; + } + + public String getPublicId() + { + return publicId; + } + } + + /* + * ValidatorErrorHandler role : + *

                + *
              • log SAX parse exceptions, + *
              • remember if an error occured + *
              + */ + protected class ValidatorErrorHandler implements ErrorHandler + { + + protected File currentFile = null; + protected String lastErrorMessage = null; + protected boolean failed = false; + + // did an error happen during last parsing ? + public boolean getFailure() + { + + return failed; + } + + public void error( SAXParseException exception ) + { + failed = true; + doLog( exception, Project.MSG_ERR ); + } + + public void fatalError( SAXParseException exception ) + { + failed = true; + doLog( exception, Project.MSG_ERR ); + } + + public void init( File file ) + { + currentFile = file; + failed = false; + } + + public void warning( SAXParseException exception ) + { + // depending on implementation, XMLReader can yield hips of warning, + // only output then if user explicitely asked for it + if( warn ) + doLog( exception, Project.MSG_WARN ); + } + + private String getMessage( SAXParseException e ) + { + String sysID = e.getSystemId(); + if( sysID != null ) + { + try + { + int line = e.getLineNumber(); + int col = e.getColumnNumber(); + return new URL( sysID ).getFile() + + ( line == -1 ? "" : ( ":" + line + + ( col == -1 ? "" : ( ":" + col ) ) ) ) + + ": " + e.getMessage(); + } + catch( MalformedURLException mfue ) + { + } + } + return e.getMessage(); + } + + private void doLog( SAXParseException e, int logLevel ) + { + + log( getMessage( e ), logLevel ); + } + } + + private class LocalResolver + implements EntityResolver + { + private Hashtable fileDTDs = new Hashtable(); + private Hashtable resourceDTDs = new Hashtable(); + private Hashtable urlDTDs = new Hashtable(); + + public LocalResolver() { } + + public void registerDTD( String publicId, String location ) + { + if( location == null ) + { + return; + } + + File fileDTD = new File( location ); + if( fileDTD.exists() ) + { + if( publicId != null ) + { + fileDTDs.put( publicId, fileDTD ); + log( "Mapped publicId " + publicId + " to file " + fileDTD, Project.MSG_VERBOSE ); + } + return; + } + + if( LocalResolver.this.getClass().getResource( location ) != null ) + { + if( publicId != null ) + { + resourceDTDs.put( publicId, location ); + log( "Mapped publicId " + publicId + " to resource " + location, Project.MSG_VERBOSE ); + } + } + + try + { + if( publicId != null ) + { + URL urldtd = new URL( location ); + urlDTDs.put( publicId, urldtd ); + } + } + catch( java.net.MalformedURLException e ) + { + //ignored + } + } + + public void registerDTD( DTDLocation location ) + { + registerDTD( location.getPublicId(), location.getLocation() ); + } + + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + File dtdFile = ( File )fileDTDs.get( publicId ); + if( dtdFile != null ) + { + try + { + log( "Resolved " + publicId + " to local file " + dtdFile, Project.MSG_VERBOSE ); + return new InputSource( new FileInputStream( dtdFile ) ); + } + catch( FileNotFoundException ex ) + { + // ignore + } + } + + String dtdResourceName = ( String )resourceDTDs.get( publicId ); + if( dtdResourceName != null ) + { + InputStream is = this.getClass().getResourceAsStream( dtdResourceName ); + if( is != null ) + { + log( "Resolved " + publicId + " to local resource " + dtdResourceName, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + } + + URL dtdUrl = ( URL )urlDTDs.get( publicId ); + if( dtdUrl != null ) + { + try + { + InputStream is = dtdUrl.openStream(); + log( "Resolved " + publicId + " to url " + dtdUrl, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + catch( IOException ioe ) + { + //ignore + } + } + + log( "Could not resolve ( publicId: " + publicId + ", systemId: " + systemId + ") to a local entity", + Project.MSG_INFO ); + + return null; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java new file mode 100644 index 000000000..3798629fc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.apache.xalan.xslt.XSLTInputSource; +import org.apache.xalan.xslt.XSLTProcessor; +import org.apache.xalan.xslt.XSLTProcessorFactory; +import org.apache.xalan.xslt.XSLTResultTarget; + +/** + * Concrete liaison for Xalan 1.x API. + * + * @author Sam Ruby + * @author Stephane Bailliez + */ +public class XalanLiaison implements XSLTLiaison +{ + + protected XSLTProcessor processor; + protected File stylesheet; + + public XalanLiaison() + throws Exception + { + processor = XSLTProcessorFactory.getProcessor(); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File stylesheet ) + throws Exception + { + this.stylesheet = stylesheet; + } + + public void addParam( String name, String value ) + { + processor.setStylesheetParam( name, value ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileInputStream fis = null; + FileOutputStream fos = null; + FileInputStream xslStream = null; + try + { + xslStream = new FileInputStream( stylesheet ); + fis = new FileInputStream( infile ); + fos = new FileOutputStream( outfile ); + // systemid such as file:/// + getAbsolutePath() are considered + // invalid here... + XSLTInputSource xslSheet = new XSLTInputSource( xslStream ); + xslSheet.setSystemId( stylesheet.getAbsolutePath() ); + XSLTInputSource src = new XSLTInputSource( fis ); + src.setSystemId( infile.getAbsolutePath() ); + XSLTResultTarget res = new XSLTResultTarget( fos ); + processor.process( src, xslSheet, res ); + } + finally + { + // make sure to close all handles, otherwise the garbage + // collector will close them...whenever possible and + // Windows may complain about not being able to delete files. + try + { + if( xslStream != null ) + { + xslStream.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fis != null ) + { + fis.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fos != null ) + { + fos.close(); + } + } + catch( IOException ignored ) + {} + } + } +}//-- XalanLiaison diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java new file mode 100644 index 000000000..7ae71c808 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import com.kvisco.xsl.XSLProcessor; +import com.kvisco.xsl.XSLReader; +import com.kvisco.xsl.XSLStylesheet; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; + + + +/** + * Concrete liaison for XSLP + * + * @author Sam Ruby + * @author Stephane Bailliez + */ +public class XslpLiaison implements XSLTLiaison +{ + + protected XSLProcessor processor; + protected XSLStylesheet xslSheet; + + public XslpLiaison() + { + processor = new XSLProcessor(); + // uh ?! I'm forced to do that otherwise a setProperty crashes with NPE ! + // I don't understand why the property map is static though... + // how can we do multithreading w/ multiple identical parameters ? + processor.getProperty( "dummy-to-init-properties-map" ); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File fileName ) + throws Exception + { + XSLReader xslReader = new XSLReader(); + // a file:/// + getAbsolutePath() does not work here + // it is really the pathname + xslSheet = xslReader.read( fileName.getAbsolutePath() ); + } + + public void addParam( String name, String expression ) + { + processor.setProperty( name, expression ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileOutputStream fos = new FileOutputStream( outfile ); + // XSLP does not support encoding...we're in hot water. + OutputStreamWriter out = new OutputStreamWriter( fos, "UTF8" ); + processor.process( infile.getAbsolutePath(), xslSheet, out ); + } + +}//-- XSLPLiaison diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java new file mode 100644 index 000000000..974e06ce5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * Class common to all check commands (checkout, checkin,checkin default task); + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheck extends Continuus +{ + + /** + * -comment flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "/comment"; + + /** + * -task flag -- associate checckout task with task + */ + public final static String FLAG_TASK = "/task"; + + private File _file = null; + private String _comment = null; + private String _task = null; + + public CCMCheck() + { + super(); + } + + /** + * Set the value of comment. + * + * @param v Value to assign to comment. + */ + public void setComment( String v ) + { + this._comment = v; + } + + /** + * Set the value of file. + * + * @param v Value to assign to file. + */ + public void setFile( File v ) + { + this._file = v; + } + + /** + * Set the value of task. + * + * @param v Value to assign to task. + */ + public void setTask( String v ) + { + this._task = v; + } + + /** + * Get the value of comment. + * + * @return value of comment. + */ + public String getComment() + { + return _comment; + } + + /** + * Get the value of file. + * + * @return value of file. + */ + public File getFile() + { + return _file; + } + + + /** + * Get the value of task. + * + * @return value of task. + */ + public String getTask() + { + return _task; + } + + + /** + * Executes the task.

              + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

              + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format is + // ccm co /t .. files + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + + if( getTask() != null ) + { + cmd.createArgument().setValue( FLAG_TASK ); + cmd.createArgument().setValue( getTask() ); + }// end of if () + + if( getFile() != null ) + { + cmd.createArgument().setValue( _file.getAbsolutePath() ); + }// end of if () + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java new file mode 100644 index 000000000..ed7d1d1f1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.util.Date; + +/** + * Task to perform Checkin command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckin extends CCMCheck +{ + + public CCMCheckin() + { + super(); + setCcmAction( COMMAND_CHECKIN ); + setComment( "Checkin " + new Date() ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java new file mode 100644 index 000000000..de5fb324d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; + +/** + * Task to perform Checkin Default task command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckinDefault extends CCMCheck +{ + + public final static String DEFAULT_TASK = "default"; + + public CCMCheckinDefault() + { + super(); + setCcmAction( COMMAND_CHECKIN ); + setTask( DEFAULT_TASK ); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java new file mode 100644 index 000000000..a01d72e68 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; + +/** + * Task to perform Checkout command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckout extends CCMCheck +{ + + public CCMCheckout() + { + super(); + setCcmAction( COMMAND_CHECKOUT ); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java new file mode 100644 index 000000000..f4ed8a11a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java @@ -0,0 +1,356 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * Task allows to create new ccm task and set it as the default + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCreateTask extends Continuus implements ExecuteStreamHandler +{ + + /** + * /comment -- comments associated to the task + */ + public final static String FLAG_COMMENT = "/synopsis"; + + /** + * /platform flag -- target platform + */ + public final static String FLAG_PLATFORM = "/plat"; + + /** + * /resolver flag + */ + public final static String FLAG_RESOLVER = "/resolver"; + + /** + * /release flag + */ + public final static String FLAG_RELEASE = "/release"; + + /** + * /release flag + */ + public final static String FLAG_SUBSYSTEM = "/subsystem"; + + /** + * -task flag -- associate checckout task with task + */ + public final static String FLAG_TASK = "/task"; + + private String comment = null; + private String platform = null; + private String resolver = null; + private String release = null; + private String subSystem = null; + private String task = null; + + public CCMCreateTask() + { + super(); + setCcmAction( COMMAND_CREATE_TASK ); + } + + /** + * Set the value of comment. + * + * @param v Value to assign to comment. + */ + public void setComment( String v ) + { + this.comment = v; + } + + /** + * Set the value of platform. + * + * @param v Value to assign to platform. + */ + public void setPlatform( String v ) + { + this.platform = v; + } + + /** + * @param is The new ProcessErrorStream value + * @exception IOException Description of Exception + */ + public void setProcessErrorStream( InputStream is ) + throws IOException + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String s = reader.readLine(); + if( s != null ) + { + log( "err " + s, Project.MSG_DEBUG ); + }// end of if () + } + + /** + * @param param1 + * @exception IOException Description of Exception + */ + public void setProcessInputStream( OutputStream param1 ) + throws IOException { } + + /** + * read the output stream to retrieve the new task number. + * + * @param is InputStream + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + + String buffer = ""; + try + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + buffer = reader.readLine(); + if( buffer != null ) + { + log( "buffer:" + buffer, Project.MSG_DEBUG ); + String taskstring = buffer.substring( buffer.indexOf( ' ' ) ).trim(); + taskstring = taskstring.substring( 0, taskstring.lastIndexOf( ' ' ) ).trim(); + setTask( taskstring ); + log( "task is " + getTask(), Project.MSG_DEBUG ); + }// end of if () + } + catch( NullPointerException npe ) + { + log( "error procession stream , null pointer exception", Project.MSG_ERR ); + npe.printStackTrace(); + throw new BuildException( npe.getClass().getName() ); + }// end of catch + catch( Exception e ) + { + log( "error procession stream " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( e.getMessage() ); + }// end of try-catch + + } + + /** + * Set the value of release. + * + * @param v Value to assign to release. + */ + public void setRelease( String v ) + { + this.release = v; + } + + /** + * Set the value of resolver. + * + * @param v Value to assign to resolver. + */ + public void setResolver( String v ) + { + this.resolver = v; + } + + /** + * Set the value of subSystem. + * + * @param v Value to assign to subSystem. + */ + public void setSubSystem( String v ) + { + this.subSystem = v; + } + + /** + * Set the value of task. + * + * @param v Value to assign to task. + */ + public void setTask( String v ) + { + this.task = v; + } + + + /** + * Get the value of comment. + * + * @return value of comment. + */ + public String getComment() + { + return comment; + } + + + /** + * Get the value of platform. + * + * @return value of platform. + */ + public String getPlatform() + { + return platform; + } + + + /** + * Get the value of release. + * + * @return value of release. + */ + public String getRelease() + { + return release; + } + + + /** + * Get the value of resolver. + * + * @return value of resolver. + */ + public String getResolver() + { + return resolver; + } + + /** + * Get the value of subSystem. + * + * @return value of subSystem. + */ + public String getSubSystem() + { + return subSystem; + } + + + /** + * Get the value of task. + * + * @return value of task. + */ + public String getTask() + { + return task; + } + + + /** + * Executes the task.

              + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

              + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format + // as specified in the CCM.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine, this ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + + //create task ok, set this task as the default one + Commandline commandLine2 = new Commandline(); + commandLine2.setExecutable( getCcmCommand() ); + commandLine2.createArgument().setValue( COMMAND_DEFAULT_TASK ); + commandLine2.createArgument().setValue( getTask() ); + + log( commandLine.toString(), Project.MSG_DEBUG ); + + result = run( commandLine2 ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine2.toString(); + throw new BuildException( msg, location ); + } + + } + + + // implementation of org.apache.tools.ant.taskdefs.ExecuteStreamHandler interface + + /** + * @exception IOException Description of Exception + */ + public void start() + throws IOException { } + + /** + */ + public void stop() { } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( "\"" + getComment() + "\"" ); + } + + if( getPlatform() != null ) + { + cmd.createArgument().setValue( FLAG_PLATFORM ); + cmd.createArgument().setValue( getPlatform() ); + }// end of if () + + if( getResolver() != null ) + { + cmd.createArgument().setValue( FLAG_RESOLVER ); + cmd.createArgument().setValue( getResolver() ); + }// end of if () + + if( getSubSystem() != null ) + { + cmd.createArgument().setValue( FLAG_SUBSYSTEM ); + cmd.createArgument().setValue( "\"" + getSubSystem() + "\"" ); + }// end of if () + + if( getRelease() != null ) + { + cmd.createArgument().setValue( FLAG_RELEASE ); + cmd.createArgument().setValue( getRelease() ); + }// end of if () + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java new file mode 100644 index 000000000..d079f2ae7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + +/** + * Task allows to reconfigure a project, recurcively or not + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMReconfigure extends Continuus +{ + + /** + * /recurse -- + */ + public final static String FLAG_RECURSE = "/recurse"; + + /** + * /recurse -- + */ + public final static String FLAG_VERBOSE = "/verbose"; + + /** + * /project flag -- target project + */ + public final static String FLAG_PROJECT = "/project"; + + private String project = null; + private boolean recurse = false; + private boolean verbose = false; + + public CCMReconfigure() + { + super(); + setCcmAction( COMMAND_RECONFIGURE ); + } + + /** + * Set the value of project. + * + * @param v Value to assign to project. + */ + public void setCcmProject( String v ) + { + this.project = v; + } + + /** + * Set the value of recurse. + * + * @param v Value to assign to recurse. + */ + public void setRecurse( boolean v ) + { + this.recurse = v; + } + + /** + * Set the value of verbose. + * + * @param v Value to assign to verbose. + */ + public void setVerbose( boolean v ) + { + this.verbose = v; + } + + /** + * Get the value of project. + * + * @return value of project. + */ + public String getCcmProject() + { + return project; + } + + + /** + * Get the value of recurse. + * + * @return value of recurse. + */ + public boolean isRecurse() + { + return recurse; + } + + + /** + * Get the value of verbose. + * + * @return value of verbose. + */ + public boolean isVerbose() + { + return verbose; + } + + + /** + * Executes the task.

              + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

              + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format + // as specified in the CCM.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + + if( isRecurse() == true ) + { + cmd.createArgument().setValue( FLAG_RECURSE ); + }// end of if () + + if( isVerbose() == true ) + { + cmd.createArgument().setValue( FLAG_VERBOSE ); + }// end of if () + + if( getCcmProject() != null ) + { + cmd.createArgument().setValue( FLAG_PROJECT ); + cmd.createArgument().setValue( getCcmProject() ); + } + + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java new file mode 100644 index 000000000..efc30650d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * A base class for creating tasks for executing commands on Continuus 5.1

              + * + * The class extends the task as it operates by executing the ccm.exe program + * supplied with Continuus/Synergy. By default the task expects the ccm + * executable to be in the path, you can override this be specifying the ccmdir + * attribute.

              + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public abstract class Continuus extends Task +{ + + /** + * Constant for the thing to execute + */ + private final static String CCM_EXE = "ccm"; + + /** + * The 'CreateTask' command + */ + public final static String COMMAND_CREATE_TASK = "create_task"; + /** + * The 'Checkout' command + */ + public final static String COMMAND_CHECKOUT = "co"; + /** + * The 'Checkin' command + */ + public final static String COMMAND_CHECKIN = "ci"; + /** + * The 'Reconfigure' command + */ + public final static String COMMAND_RECONFIGURE = "reconfigure"; + + /** + * The 'Reconfigure' command + */ + public final static String COMMAND_DEFAULT_TASK = "default_task"; + + private String _ccmDir = ""; + private String _ccmAction = ""; + + + /** + * Set the directory where the ccm executable is located + * + * @param dir the directory containing the ccm executable + */ + public final void setCcmDir( String dir ) + { + _ccmDir = project.translatePath( dir ); + } + + /** + * Set the value of ccmAction. + * + * @param v Value to assign to ccmAction. + */ + public void setCcmAction( String v ) + { + this._ccmAction = v; + } + + /** + * Get the value of ccmAction. + * + * @return value of ccmAction. + */ + public String getCcmAction() + { + return _ccmAction; + } + + /** + * Builds and returns the command string to execute ccm + * + * @return String containing path to the executable + */ + protected final String getCcmCommand() + { + String toReturn = _ccmDir; + if( !toReturn.equals( "" ) && !toReturn.endsWith( "/" ) ) + { + toReturn += "/"; + } + + toReturn += CCM_EXE; + + return toReturn; + } + + + protected int run( Commandline cmd, ExecuteStreamHandler handler ) + { + try + { + Execute exe = new Execute( handler ); + exe.setAntRun( getProject() ); + exe.setWorkingDirectory( getProject().getBaseDir() ); + exe.setCommandline( cmd.getCommandline() ); + return exe.execute(); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + + protected int run( Commandline cmd ) + { + return run( cmd, new LogStreamHandler( this, Project.MSG_VERBOSE, Project.MSG_WARN ) ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java new file mode 100644 index 000000000..e4104295a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform Checkin command to ClearCase.

              + * + * The following attributes are interpreted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * comment + * + * + * + * Specify a comment. Only one of comment or cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * commentfile + * + * + * + * Specify a file containing a comment. Only one of comment or + * cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * nowarn + * + * + * + * Suppress warning messages + * + * + * + * No + * + * + * + * + * + * + * + * preservetime + * + * + * + * Preserve the modification time + * + * + * + * No + * + * + * + * + * + * + * + * keepcopy + * + * + * + * Keeps a copy of the file with a .keep extension + * + * + * + * + * No + * + * + * + * + * + * + * + * identical + * + * + * + * Allows the file to be checked in even if it is + * identical to the original + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCCheckin extends ClearCase +{ + + /** + * -c flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "-c"; + /** + * -cfile flag -- file containing a comment to attach to the file + */ + public final static String FLAG_COMMENTFILE = "-cfile"; + /** + * -nc flag -- no comment is specified + */ + public final static String FLAG_NOCOMMENT = "-nc"; + /** + * -nwarn flag -- suppresses warning messages + */ + public final static String FLAG_NOWARN = "-nwarn"; + /** + * -ptime flag -- preserves the modification time + */ + public final static String FLAG_PRESERVETIME = "-ptime"; + /** + * -keep flag -- keeps a copy of the file with a .keep extension + */ + public final static String FLAG_KEEPCOPY = "-keep"; + /** + * -identical flag -- allows the file to be checked in even if it is + * identical to the original + */ + public final static String FLAG_IDENTICAL = "-identical"; + private String m_Comment = null; + private String m_Cfile = null; + private boolean m_Nwarn = false; + private boolean m_Ptime = false; + private boolean m_Keep = false; + private boolean m_Identical = true; + + + /** + * Set comment string + * + * @param comment the comment string + */ + public void setComment( String comment ) + { + m_Comment = comment; + } + + /** + * Set comment file + * + * @param cfile the path to the comment file + */ + public void setCommentFile( String cfile ) + { + m_Cfile = cfile; + } + + /** + * Set the identical flag + * + * @param identical the status to set the flag to + */ + public void setIdentical( boolean identical ) + { + m_Identical = identical; + } + + /** + * Set the keepcopy flag + * + * @param keep the status to set the flag to + */ + public void setKeepCopy( boolean keep ) + { + m_Keep = keep; + } + + /** + * Set the nowarn flag + * + * @param nwarn the status to set the flag to + */ + public void setNoWarn( boolean nwarn ) + { + m_Nwarn = nwarn; + } + + /** + * Set preservetime flag + * + * @param ptime the status to set the flag to + */ + public void setPreserveTime( boolean ptime ) + { + m_Ptime = ptime; + } + + /** + * Get comment string + * + * @return String containing the comment + */ + public String getComment() + { + return m_Comment; + } + + /** + * Get comment file + * + * @return String containing the path to the comment file + */ + public String getCommentFile() + { + return m_Cfile; + } + + /** + * Get identical flag status + * + * @return boolean containing status of identical flag + */ + public boolean getIdentical() + { + return m_Identical; + } + + /** + * Get keepcopy flag status + * + * @return boolean containing status of keepcopy flag + */ + public boolean getKeepCopy() + { + return m_Keep; + } + + /** + * Get nowarn flag status + * + * @return boolean containing status of nwarn flag + */ + public boolean getNoWarn() + { + return m_Nwarn; + } + + /** + * Get preservetime flag status + * + * @return boolean containing status of preservetime flag + */ + public boolean getPreserveTime() + { + return m_Ptime; + } + + /** + * Executes the task.

              + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got. the format is + // cleartool checkin [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_CHECKIN ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Get the 'comment' command + * + * @param cmd Description of Parameter + */ + private void getCommentCommand( Commandline cmd ) + { + if( getComment() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + } + + /** + * Get the 'commentfile' command + * + * @param cmd Description of Parameter + */ + private void getCommentFileCommand( Commandline cmd ) + { + if( getCommentFile() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENTFILE ); + cmd.createArgument().setValue( getCommentFile() ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + // -c + getCommentCommand( cmd ); + } + else + { + if( getCommentFile() != null ) + { + // -cfile + getCommentFileCommand( cmd ); + } + else + { + cmd.createArgument().setValue( FLAG_NOCOMMENT ); + } + } + + if( getNoWarn() ) + { + // -nwarn + cmd.createArgument().setValue( FLAG_NOWARN ); + } + + if( getPreserveTime() ) + { + // -ptime + cmd.createArgument().setValue( FLAG_PRESERVETIME ); + } + + if( getKeepCopy() ) + { + // -keep + cmd.createArgument().setValue( FLAG_KEEPCOPY ); + } + + if( getIdentical() ) + { + // -identical + cmd.createArgument().setValue( FLAG_IDENTICAL ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java new file mode 100644 index 000000000..7c649bc5b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java @@ -0,0 +1,602 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform Checkout command to ClearCase.

              + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * reserved + * + * + * + * Specifies whether to check out the file as reserved or not + * + * + * + * Yes + * + * + * + * + * + * + * + * out + * + * + * + * Creates a writable file under a different filename + * + * + * + * No + * + * + * + * + * + * + * + * nodata + * + * + * + * Checks out the file but does not create an editable file + * containing its data + * + * + * + * No + * + * + * + * + * + * + * + * branch + * + * + * + * Specify a branch to check out the file to + * + * + * + * No + * + * + * + * + * + * + * + * version + * + * + * + * Allows checkout of a version other than main latest + * + * + * + * + * No + * + * + * + * + * + * + * + * nowarn + * + * + * + * Suppress warning messages + * + * + * + * No + * + * + * + * + * + * + * + * comment + * + * + * + * Specify a comment. Only one of comment or + * cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * commentfile + * + * + * + * Specify a file containing a comment. + * Only one of comment or cfile may be + * used. + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCCheckout extends ClearCase +{ + + /** + * -reserved flag -- check out the file as reserved + */ + public final static String FLAG_RESERVED = "-reserved"; + /** + * -reserved flag -- check out the file as unreserved + */ + public final static String FLAG_UNRESERVED = "-unreserved"; + /** + * -out flag -- create a writable file under a different filename + */ + public final static String FLAG_OUT = "-out"; + /** + * -ndata flag -- checks out the file but does not create an editable file + * containing its data + */ + public final static String FLAG_NODATA = "-ndata"; + /** + * -branch flag -- checks out the file on a specified branch + */ + public final static String FLAG_BRANCH = "-branch"; + /** + * -version flag -- allows checkout of a version that is not main latest + */ + public final static String FLAG_VERSION = "-version"; + /** + * -nwarn flag -- suppresses warning messages + */ + public final static String FLAG_NOWARN = "-nwarn"; + /** + * -c flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "-c"; + /** + * -cfile flag -- file containing a comment to attach to the file + */ + public final static String FLAG_COMMENTFILE = "-cfile"; + /** + * -nc flag -- no comment is specified + */ + public final static String FLAG_NOCOMMENT = "-nc"; + private boolean m_Reserved = true; + private String m_Out = null; + private boolean m_Ndata = false; + private String m_Branch = null; + private boolean m_Version = false; + private boolean m_Nwarn = false; + private String m_Comment = null; + private String m_Cfile = null; + + /** + * Set branch name + * + * @param branch the name of the branch + */ + public void setBranch( String branch ) + { + m_Branch = branch; + } + + /** + * Set comment string + * + * @param comment the comment string + */ + public void setComment( String comment ) + { + m_Comment = comment; + } + + /** + * Set comment file + * + * @param cfile the path to the comment file + */ + public void setCommentFile( String cfile ) + { + m_Cfile = cfile; + } + + /** + * Set the nodata flag + * + * @param ndata the status to set the flag to + */ + public void setNoData( boolean ndata ) + { + m_Ndata = ndata; + } + + /** + * Set the nowarn flag + * + * @param nwarn the status to set the flag to + */ + public void setNoWarn( boolean nwarn ) + { + m_Nwarn = nwarn; + } + + /** + * Set out file + * + * @param outf the path to the out file + */ + public void setOut( String outf ) + { + m_Out = outf; + } + + /** + * Set reserved flag status + * + * @param reserved the status to set the flag to + */ + public void setReserved( boolean reserved ) + { + m_Reserved = reserved; + } + + /** + * Set the version flag + * + * @param version the status to set the flag to + */ + public void setVersion( boolean version ) + { + m_Version = version; + } + + /** + * Get branch name + * + * @return String containing the name of the branch + */ + public String getBranch() + { + return m_Branch; + } + + /** + * Get comment string + * + * @return String containing the comment + */ + public String getComment() + { + return m_Comment; + } + + /** + * Get comment file + * + * @return String containing the path to the comment file + */ + public String getCommentFile() + { + return m_Cfile; + } + + /** + * Get nodata flag status + * + * @return boolean containing status of ndata flag + */ + public boolean getNoData() + { + return m_Ndata; + } + + /** + * Get nowarn flag status + * + * @return boolean containing status of nwarn flag + */ + public boolean getNoWarn() + { + return m_Nwarn; + } + + /** + * Get out file + * + * @return String containing the path to the out file + */ + public String getOut() + { + return m_Out; + } + + /** + * Get reserved flag status + * + * @return boolean containing status of reserved flag + */ + public boolean getReserved() + { + return m_Reserved; + } + + /** + * Get version flag status + * + * @return boolean containing status of version flag + */ + public boolean getVersion() + { + return m_Version; + } + + /** + * Executes the task.

              + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool checkout [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_CHECKOUT ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + /** + * Get the 'branch' command + * + * @param cmd Description of Parameter + */ + private void getBranchCommand( Commandline cmd ) + { + if( getBranch() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_BRANCH ); + cmd.createArgument().setValue( getBranch() ); + } + } + + + /** + * Get the 'comment' command + * + * @param cmd Description of Parameter + */ + private void getCommentCommand( Commandline cmd ) + { + if( getComment() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + } + + /** + * Get the 'cfile' command + * + * @param cmd Description of Parameter + */ + private void getCommentFileCommand( Commandline cmd ) + { + if( getCommentFile() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENTFILE ); + cmd.createArgument().setValue( getCommentFile() ); + } + } + + /** + * Get the 'out' command + * + * @param cmd Description of Parameter + */ + private void getOutCommand( Commandline cmd ) + { + if( getOut() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_OUT ); + cmd.createArgument().setValue( getOut() ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getReserved() ) + { + // -reserved + cmd.createArgument().setValue( FLAG_RESERVED ); + } + else + { + // -unreserved + cmd.createArgument().setValue( FLAG_UNRESERVED ); + } + + if( getOut() != null ) + { + // -out + getOutCommand( cmd ); + } + else + { + if( getNoData() ) + { + // -ndata + cmd.createArgument().setValue( FLAG_NODATA ); + } + + } + + if( getBranch() != null ) + { + // -branch + getBranchCommand( cmd ); + } + else + { + if( getVersion() ) + { + // -version + cmd.createArgument().setValue( FLAG_VERSION ); + } + + } + + if( getNoWarn() ) + { + // -nwarn + cmd.createArgument().setValue( FLAG_NOWARN ); + } + + if( getComment() != null ) + { + // -c + getCommentCommand( cmd ); + } + else + { + if( getCommentFile() != null ) + { + // -cfile + getCommentFileCommand( cmd ); + } + else + { + cmd.createArgument().setValue( FLAG_NOCOMMENT ); + } + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java new file mode 100644 index 000000000..02a442f2c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform UnCheckout command to ClearCase.

              + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * keepcopy + * + * + * + * Specifies whether to keep a copy of the file with a .keep extension + * or not + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCUnCheckout extends ClearCase +{ + + /** + * -keep flag -- keep a copy of the file with .keep extension + */ + public final static String FLAG_KEEPCOPY = "-keep"; + /** + * -rm flag -- remove the copy of the file + */ + public final static String FLAG_RM = "-rm"; + private boolean m_Keep = false; + + /** + * Set keepcopy flag status + * + * @param keep the status to set the flag to + */ + public void setKeepCopy( boolean keep ) + { + m_Keep = keep; + } + + /** + * Get keepcopy flag status + * + * @return boolean containing status of keep flag + */ + public boolean getKeepCopy() + { + return m_Keep; + } + + /** + * Executes the task.

              + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool uncheckout [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_UNCHECKOUT ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getKeepCopy() ) + { + // -keep + cmd.createArgument().setValue( FLAG_KEEPCOPY ); + } + else + { + // -rm + cmd.createArgument().setValue( FLAG_RM ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java new file mode 100644 index 000000000..2d88f71c4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform an Update command to ClearCase.

              + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * graphical + * + * + * + * Displays a graphical dialog during the update + * + * + * + * No + * + * + * + * + * + * + * + * log + * + * + * + * Specifies a log file for ClearCase to write to + * + * + * + * No + * + * + * + * + * + * + * + * overwrite + * + * + * + * Specifies whether to overwrite hijacked files or not + * + * + * + * No + * + * + * + * + * + * + * + * rename + * + * + * + * Specifies that hijacked files should be renamed with a + * .keep extension + * + * + * + * No + * + * + * + * + * + * + * + * currenttime + * + * + * + * Specifies that modification time should be written + * as the current time. Either currenttime or + * preservetime can be specified. + * + * + * + * No + * + * + * + * + * + * + * + * preservetime + * + * + * + * Specifies that modification time should + * preserved from the VOB time. Either currenttime + * or preservetime can be specified. + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCUpdate extends ClearCase +{ + + /** + * -graphical flag -- display graphical dialog during update operation + */ + public final static String FLAG_GRAPHICAL = "-graphical"; + /** + * -log flag -- file to log status to + */ + public final static String FLAG_LOG = "-log"; + /** + * -overwrite flag -- overwrite hijacked files + */ + public final static String FLAG_OVERWRITE = "-overwrite"; + /** + * -noverwrite flag -- do not overwrite hijacked files + */ + public final static String FLAG_NOVERWRITE = "-noverwrite"; + /** + * -rename flag -- rename hijacked files with .keep extension + */ + public final static String FLAG_RENAME = "-rename"; + /** + * -ctime flag -- modified time is written as the current time + */ + public final static String FLAG_CURRENTTIME = "-ctime"; + /** + * -ptime flag -- modified time is written as the VOB time + */ + public final static String FLAG_PRESERVETIME = "-ptime"; + private boolean m_Graphical = false; + private boolean m_Overwrite = false; + private boolean m_Rename = false; + private boolean m_Ctime = false; + private boolean m_Ptime = false; + private String m_Log = null; + + /** + * Set modified time based on current time + * + * @param ct the status to set the flag to + */ + public void setCurrentTime( boolean ct ) + { + m_Ctime = ct; + } + + /** + * Set graphical flag status + * + * @param graphical the status to set the flag to + */ + public void setGraphical( boolean graphical ) + { + m_Graphical = graphical; + } + + /** + * Set log file where cleartool can record the status of the command + * + * @param log the path to the log file + */ + public void setLog( String log ) + { + m_Log = log; + } + + /** + * Set overwrite hijacked files status + * + * @param ow the status to set the flag to + */ + public void setOverwrite( boolean ow ) + { + m_Overwrite = ow; + } + + /** + * Preserve modified time from the VOB time + * + * @param pt the status to set the flag to + */ + public void setPreserveTime( boolean pt ) + { + m_Ptime = pt; + } + + /** + * Set rename hijacked files status + * + * @param ren the status to set the flag to + */ + public void setRename( boolean ren ) + { + m_Rename = ren; + } + + /** + * Get current time status + * + * @return boolean containing status of current time flag + */ + public boolean getCurrentTime() + { + return m_Ctime; + } + + /** + * Get graphical flag status + * + * @return boolean containing status of graphical flag + */ + public boolean getGraphical() + { + return m_Graphical; + } + + /** + * Get log file + * + * @return String containing the path to the log file + */ + public String getLog() + { + return m_Log; + } + + /** + * Get overwrite hijacked files status + * + * @return boolean containing status of overwrite flag + */ + public boolean getOverwrite() + { + return m_Overwrite; + } + + /** + * Get preserve time status + * + * @return boolean containing status of preserve time flag + */ + public boolean getPreserveTime() + { + return m_Ptime; + } + + /** + * Get rename hijacked files status + * + * @return boolean containing status of rename flag + */ + public boolean getRename() + { + return m_Rename; + } + + /** + * Executes the task.

              + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool update [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_UPDATE ); + + // Check the command line options + checkOptions( commandLine ); + + // For debugging + System.out.println( commandLine.toString() ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + /** + * Get the 'log' command + * + * @param cmd Description of Parameter + */ + private void getLogCommand( Commandline cmd ) + { + if( getLog() == null ) + { + return; + } + else + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_LOG ); + cmd.createArgument().setValue( getLog() ); + } + } + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getGraphical() ) + { + // -graphical + cmd.createArgument().setValue( FLAG_GRAPHICAL ); + } + else + { + if( getOverwrite() ) + { + // -overwrite + cmd.createArgument().setValue( FLAG_OVERWRITE ); + } + else + { + if( getRename() ) + { + // -rename + cmd.createArgument().setValue( FLAG_RENAME ); + } + else + { + // -noverwrite + cmd.createArgument().setValue( FLAG_NOVERWRITE ); + } + } + + if( getCurrentTime() ) + { + // -ctime + cmd.createArgument().setValue( FLAG_CURRENTTIME ); + } + else + { + if( getPreserveTime() ) + { + // -ptime + cmd.createArgument().setValue( FLAG_PRESERVETIME ); + } + } + + // -log logname + getLogCommand( cmd ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java new file mode 100644 index 000000000..917152f39 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * A base class for creating tasks for executing commands on ClearCase.

              + * + * The class extends the 'exec' task as it operates by executing the cleartool + * program supplied with ClearCase. By default the task expects the cleartool + * executable to be in the path, * you can override this be specifying the + * cleartooldir attribute.

              + * + * This class provides set and get methods for the 'viewpath' attribute. It also + * contains constants for the flags that can be passed to cleartool.

              + * + * @author Curtis White + */ +public abstract class ClearCase extends Task +{ + + /** + * Constant for the thing to execute + */ + private final static String CLEARTOOL_EXE = "cleartool"; + + /** + * The 'Update' command + */ + public final static String COMMAND_UPDATE = "update"; + /** + * The 'Checkout' command + */ + public final static String COMMAND_CHECKOUT = "checkout"; + /** + * The 'Checkin' command + */ + public final static String COMMAND_CHECKIN = "checkin"; + /** + * The 'UndoCheckout' command + */ + public final static String COMMAND_UNCHECKOUT = "uncheckout"; + private String m_ClearToolDir = ""; + private String m_viewPath = null; + + /** + * Set the directory where the cleartool executable is located + * + * @param dir the directory containing the cleartool executable + */ + public final void setClearToolDir( String dir ) + { + m_ClearToolDir = project.translatePath( dir ); + } + + /** + * Set the path to the item in a clearcase view to operate on + * + * @param viewPath Path to the view directory or file + */ + public final void setViewPath( String viewPath ) + { + m_viewPath = viewPath; + } + + /** + * Get the path to the item in a clearcase view + * + * @return m_viewPath + */ + public String getViewPath() + { + return m_viewPath; + } + + /** + * Builds and returns the command string to execute cleartool + * + * @return String containing path to the executable + */ + protected final String getClearToolCommand() + { + String toReturn = m_ClearToolDir; + if( !toReturn.equals( "" ) && !toReturn.endsWith( "/" ) ) + { + toReturn += "/"; + } + + toReturn += CLEARTOOL_EXE; + + return toReturn; + } + + + protected int run( Commandline cmd ) + { + try + { + Project aProj = getProject(); + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ) ); + exe.setAntRun( aProj ); + exe.setWorkingDirectory( aProj.getBaseDir() ); + exe.setCommandline( cmd.getCommandline() ); + return exe.execute(); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java new file mode 100644 index 000000000..f0479ae8c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ClassCPInfo; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPool; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPoolEntry; + +/** + * A ClassFile object stores information about a Java class. The class may be + * read from a DataInputStream.and written to a DataOutputStream. These are + * usually streams from a Java class file or a class file component of a Jar + * file. + * + * @author Conor MacNeill + */ +public class ClassFile +{ + + /** + * The Magic Value that marks the start of a Java class file + */ + private final static int CLASS_MAGIC = 0xCAFEBABE; + + /** + * The class name for this class. + */ + private String className; + + /** + * This class' constant pool. + */ + private ConstantPool constantPool; + + + /** + * Get the classes which this class references. + * + * @return The ClassRefs value + */ + public Vector getClassRefs() + { + + Vector classRefs = new Vector(); + + for( int i = 0; i < constantPool.size(); ++i ) + { + ConstantPoolEntry entry = constantPool.getEntry( i ); + + if( entry != null && entry.getTag() == ConstantPoolEntry.CONSTANT_Class ) + { + ClassCPInfo classEntry = ( ClassCPInfo )entry; + + if( !classEntry.getClassName().equals( className ) ) + { + classRefs.addElement( ClassFileUtils.convertSlashName( classEntry.getClassName() ) ); + } + } + } + + return classRefs; + } + + /** + * Get the class' fully qualified name in dot format. + * + * @return the class name in dot format (eg. java.lang.Object) + */ + public String getFullClassName() + { + return ClassFileUtils.convertSlashName( className ); + } + + /** + * Read the class from a data stream. This method takes an InputStream as + * input and parses the class from the stream.

              + * + * + * + * @param stream an InputStream from which the class will be read + * @throws IOException if there is a problem reading from the given stream. + * @throws ClassFormatError if the class cannot be parsed correctly + */ + public void read( InputStream stream ) + throws IOException, ClassFormatError + { + DataInputStream classStream = new DataInputStream( stream ); + + if( classStream.readInt() != CLASS_MAGIC ) + { + throw new ClassFormatError( "No Magic Code Found - probably not a Java class file." ); + } + + // right we have a good looking class file. + int minorVersion = classStream.readUnsignedShort(); + int majorVersion = classStream.readUnsignedShort(); + + // read the constant pool in and resolve it + constantPool = new ConstantPool(); + + constantPool.read( classStream ); + constantPool.resolve(); + + int accessFlags = classStream.readUnsignedShort(); + int thisClassIndex = classStream.readUnsignedShort(); + int superClassIndex = classStream.readUnsignedShort(); + className = ( ( ClassCPInfo )constantPool.getEntry( thisClassIndex ) ).getClassName(); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java new file mode 100644 index 000000000..de4ad9499 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; + + +public interface ClassFileIterator +{ + + ClassFile getNextClassFile(); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java new file mode 100644 index 000000000..054ef68f8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; + +/** + * Utility class file routines. This class porovides a number of static utility + * methods to convert between the formats used in the Java class file format and + * those commonly used in Java programming. + * + * @author Conor MacNeill + */ +public class ClassFileUtils +{ + + /** + * Convert a class name from java source file dot notation to class file + * slash notation.. + * + * @param dotName the class name in dot notation (eg. java.lang.Object). + * @return the class name in slash notation (eg. java/lang/Object). + */ + public static String convertDotName( String dotName ) + { + return dotName.replace( '.', '/' ); + } + + /** + * Convert a class name from class file slash notation to java source file + * dot notation. + * + * @param name Description of Parameter + * @return the class name in dot notation (eg. java.lang.Object). + */ + public static String convertSlashName( String name ) + { + return name.replace( '\\', '.' ).replace( '/', '.' ); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/Depend.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/Depend.java new file mode 100644 index 000000000..62ec1812e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/Depend.java @@ -0,0 +1,764 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + + +/** + * Generate a dependency file for a given set of classes + * + * @author Conor MacNeill + */ +public class Depend extends MatchingTask +{ + + /** + * constants used with the cache file + */ + private final static String CACHE_FILE_NAME = "dependencies.txt"; + private final static String CLASSNAME_PREPEND = "||:"; + + /** + * indicates that the dependency relationships should be extended beyond + * direct dependencies to include all classes. So if A directly affects B + * abd B directly affects C, then A indirectly affects C. + */ + private boolean closure = false; + + /** + * Flag which controls whether the reversed dependencies should be dumped to + * the log + */ + private boolean dump = false; + + /** + * A map which gives for every class a list of te class which it affects. + */ + private Hashtable affectedClassMap; + + /** + * The directory which contains the dependency cache. + */ + private File cache; + + /** + * A map which gives information about a class + */ + private Hashtable classFileInfoMap; + + /** + * A map which gives the list of jars and classes from the classpath that a + * class depends upon + */ + private Hashtable classpathDependencies; + + /** + * The classpath to look for additional dependencies + */ + private Path dependClasspath; + + /** + * The path where compiled class files exist. + */ + private Path destPath; + + /** + * The list of classes which are out of date. + */ + private Hashtable outOfDateClasses; + + /** + * The path where source files exist + */ + private Path srcPath; + + public void setCache( File cache ) + { + this.cache = cache; + } + + /** + * Set the classpath to be used for this dependency check. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( dependClasspath == null ) + { + dependClasspath = classpath; + } + else + { + dependClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setClosure( boolean closure ) + { + this.closure = closure; + } + + /** + * Set the destination directory where the compiled java files exist. + * + * @param destPath The new DestDir value + */ + public void setDestDir( Path destPath ) + { + this.destPath = destPath; + } + + /** + * Flag to indicate whether the reverse dependency list should be dumped to + * debug + * + * @param dump The new Dump value + */ + public void setDump( boolean dump ) + { + this.dump = dump; + } + + + /** + * Set the source dirs to find the source Java files. + * + * @param srcPath The new Srcdir value + */ + public void setSrcdir( Path srcPath ) + { + this.srcPath = srcPath; + } + + /** + * Gets the classpath to be used for this dependency check. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return dependClasspath; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( dependClasspath == null ) + { + dependClasspath = new Path( project ); + } + return dependClasspath.createPath(); + } + + /** + * Does the work. + * + * @exception BuildException Thrown in unrecovrable error. + */ + public void execute() + throws BuildException + { + try + { + long start = System.currentTimeMillis(); + String[] srcPathList = srcPath.list(); + if( srcPathList.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + + if( destPath == null ) + { + destPath = srcPath; + } + + if( cache != null && cache.exists() && !cache.isDirectory() ) + { + throw new BuildException( "The cache, if specified, must point to a directory" ); + } + + if( cache != null && !cache.exists() ) + { + cache.mkdirs(); + } + + determineDependencies(); + + if( dump ) + { + log( "Reverse Dependency Dump for " + affectedClassMap.size() + + " classes:", Project.MSG_DEBUG ); + for( Enumeration e = affectedClassMap.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + log( " Class " + className + " affects:", Project.MSG_DEBUG ); + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( className ); + for( Enumeration e2 = affectedClasses.keys(); e2.hasMoreElements(); ) + { + String affectedClass = ( String )e2.nextElement(); + ClassFileInfo info = ( ClassFileInfo )affectedClasses.get( affectedClass ); + log( " " + affectedClass + " in " + info.absoluteFile.getPath(), Project.MSG_DEBUG ); + } + } + + if( classpathDependencies != null ) + { + log( "Classpath file dependencies (Forward):", Project.MSG_DEBUG ); + for( Enumeration e = classpathDependencies.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + log( " Class " + className + " depends on:", Project.MSG_DEBUG ); + Hashtable dependencies = ( Hashtable )classpathDependencies.get( className ); + for( Enumeration e2 = dependencies.elements(); e2.hasMoreElements(); ) + { + File classpathFile = ( File )e2.nextElement(); + log( " " + classpathFile.getPath(), Project.MSG_DEBUG ); + } + } + } + + } + + // we now need to scan for out of date files. When we have the list + // we go through and delete all class files which are affected by these files. + outOfDateClasses = new Hashtable(); + for( int i = 0; i < srcPathList.length; i++ ) + { + File srcDir = ( File )project.resolveFile( srcPathList[i] ); + if( srcDir.exists() ) + { + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + scanDir( srcDir, files ); + } + } + + // now check classpath file dependencies + if( classpathDependencies != null ) + { + for( Enumeration e = classpathDependencies.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + if( !outOfDateClasses.containsKey( className ) ) + { + ClassFileInfo info = ( ClassFileInfo )classFileInfoMap.get( className ); + + // if we have no info about the class - it may have been deleted already and we + // are using cached info. + if( info != null ) + { + Hashtable dependencies = ( Hashtable )classpathDependencies.get( className ); + for( Enumeration e2 = dependencies.elements(); e2.hasMoreElements(); ) + { + File classpathFile = ( File )e2.nextElement(); + if( classpathFile.lastModified() > info.absoluteFile.lastModified() ) + { + log( "Class " + className + + " is out of date with respect to " + classpathFile, Project.MSG_DEBUG ); + outOfDateClasses.put( className, className ); + break; + } + } + } + } + } + } + + // we now have a complete list of classes which are out of date + // We scan through the affected classes, deleting any affected classes. + int count = deleteAllAffectedFiles(); + + long duration = ( System.currentTimeMillis() - start ) / 1000; + log( "Deleted " + count + " out of date files in " + duration + " seconds" ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + /** + * Scans the directory looking for source files that are newer than their + * class files. The results are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, String files[] ) + { + + long now = System.currentTimeMillis(); + + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".java" ) ) + { + String filePath = srcFile.getPath(); + String className = filePath.substring( srcDir.getPath().length() + 1, + filePath.length() - ".java".length() ); + className = ClassFileUtils.convertSlashName( className ); + ClassFileInfo info = ( ClassFileInfo )classFileInfoMap.get( className ); + if( info == null ) + { + // there was no class file. add this class to the list + outOfDateClasses.put( className, className ); + } + else + { + if( srcFile.lastModified() > info.absoluteFile.lastModified() ) + { + outOfDateClasses.put( className, className ); + } + } + } + } + } + + + /** + * Get the list of class files we are going to analyse. + * + * @param classLocations a path structure containing all the directories + * where classes can be found. + * @return a vector containing the classes to analyse. + */ + private Vector getClassFiles( Path classLocations ) + { + // break the classLocations into its components. + String[] classLocationsList = classLocations.list(); + + Vector classFileList = new Vector(); + + for( int i = 0; i < classLocationsList.length; ++i ) + { + File dir = new File( classLocationsList[i] ); + if( dir.isDirectory() ) + { + addClassFiles( classFileList, dir, dir ); + } + } + + return classFileList; + } + + /** + * Add the list of class files from the given directory to the class file + * vector, including any subdirectories. + * + * @param classFileList The feature to be added to the ClassFiles attribute + * @param dir The feature to be added to the ClassFiles attribute + * @param root The feature to be added to the ClassFiles attribute + */ + private void addClassFiles( Vector classFileList, File dir, File root ) + { + String[] filesInDir = dir.list(); + + if( filesInDir != null ) + { + int length = filesInDir.length; + + for( int i = 0; i < length; ++i ) + { + File file = new File( dir, filesInDir[i] ); + if( file.isDirectory() ) + { + addClassFiles( classFileList, file, root ); + } + else if( file.getName().endsWith( ".class" ) ) + { + ClassFileInfo info = new ClassFileInfo(); + info.absoluteFile = file; + info.relativeName = file.getPath().substring( root.getPath().length() + 1, + file.getPath().length() - 6 ); + info.className = ClassFileUtils.convertSlashName( info.relativeName ); + classFileList.addElement( info ); + } + } + } + } + + private int deleteAffectedFiles( String className ) + { + int count = 0; + + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( className ); + if( affectedClasses != null ) + { + for( Enumeration e = affectedClasses.keys(); e.hasMoreElements(); ) + { + String affectedClassName = ( String )e.nextElement(); + ClassFileInfo affectedClassInfo = ( ClassFileInfo )affectedClasses.get( affectedClassName ); + if( affectedClassInfo.absoluteFile.exists() ) + { + log( "Deleting file " + affectedClassInfo.absoluteFile.getPath() + " since " + + className + " out of date", Project.MSG_VERBOSE ); + affectedClassInfo.absoluteFile.delete(); + count++; + if( closure ) + { + count += deleteAffectedFiles( affectedClassName ); + } + else + { + // without closure we may delete an inner class but not the + // top level class which would not trigger a recompile. + + if( affectedClassName.indexOf( "$" ) != -1 ) + { + // need to delete the main class + String topLevelClassName + = affectedClassName.substring( 0, affectedClassName.indexOf( "$" ) ); + log( "Top level class = " + topLevelClassName, Project.MSG_VERBOSE ); + ClassFileInfo topLevelClassInfo + = ( ClassFileInfo )classFileInfoMap.get( topLevelClassName ); + if( topLevelClassInfo != null && + topLevelClassInfo.absoluteFile.exists() ) + { + log( "Deleting file " + topLevelClassInfo.absoluteFile.getPath() + " since " + + "one of its inner classes was removed", Project.MSG_VERBOSE ); + topLevelClassInfo.absoluteFile.delete(); + count++; + if( closure ) + { + count += deleteAffectedFiles( topLevelClassName ); + } + } + } + } + } + } + } + return count; + } + + private int deleteAllAffectedFiles() + { + int count = 0; + for( Enumeration e = outOfDateClasses.elements(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + count += deleteAffectedFiles( className ); + ClassFileInfo classInfo = ( ClassFileInfo )classFileInfoMap.get( className ); + if( classInfo != null && classInfo.absoluteFile.exists() ) + { + classInfo.absoluteFile.delete(); + count++; + } + } + return count; + } + + + /** + * Determine the dependencies between classes. Class dependencies are + * determined by examining the class references in a class file to other + * classes + * + * @exception IOException Description of Exception + */ + private void determineDependencies() + throws IOException + { + affectedClassMap = new Hashtable(); + classFileInfoMap = new Hashtable(); + boolean cacheDirty = false; + + Hashtable dependencyMap = new Hashtable(); + File depCacheFile = null; + boolean depCacheFileExists = true; + long depCacheFileLastModified = Long.MAX_VALUE; + + // read the dependency cache from the disk + if( cache != null ) + { + dependencyMap = readCachedDependencies(); + depCacheFile = new File( cache, CACHE_FILE_NAME ); + depCacheFileExists = depCacheFile.exists(); + depCacheFileLastModified = depCacheFile.lastModified(); + } + for( Enumeration e = getClassFiles( destPath ).elements(); e.hasMoreElements(); ) + { + ClassFileInfo info = ( ClassFileInfo )e.nextElement(); + log( "Adding class info for " + info.className, Project.MSG_DEBUG ); + classFileInfoMap.put( info.className, info ); + + Vector dependencyList = null; + + if( cache != null ) + { + // try to read the dependency info from the map if it is not out of date + if( depCacheFileExists && depCacheFileLastModified > info.absoluteFile.lastModified() ) + { + // depFile exists and is newer than the class file + // need to get dependency list from the map. + dependencyList = ( Vector )dependencyMap.get( info.className ); + } + } + + if( dependencyList == null ) + { + // not cached - so need to read directly from the class file + FileInputStream inFileStream = null; + try + { + inFileStream = new FileInputStream( info.absoluteFile ); + ClassFile classFile = new ClassFile(); + classFile.read( inFileStream ); + + dependencyList = classFile.getClassRefs(); + if( dependencyList != null ) + { + cacheDirty = true; + dependencyMap.put( info.className, dependencyList ); + } + + } + finally + { + if( inFileStream != null ) + { + inFileStream.close(); + } + } + } + + // This class depends on each class in the dependency list. For each + // one of those, add this class into their affected classes list + for( Enumeration depEnum = dependencyList.elements(); depEnum.hasMoreElements(); ) + { + String dependentClass = ( String )depEnum.nextElement(); + + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( dependentClass ); + if( affectedClasses == null ) + { + affectedClasses = new Hashtable(); + affectedClassMap.put( dependentClass, affectedClasses ); + } + + affectedClasses.put( info.className, info ); + } + } + + classpathDependencies = null; + if( dependClasspath != null ) + { + // now determine which jars each class depends upon + classpathDependencies = new Hashtable(); + AntClassLoader loader = new AntClassLoader( getProject(), dependClasspath ); + + Hashtable classpathFileCache = new Hashtable(); + Object nullFileMarker = new Object(); + for( Enumeration e = dependencyMap.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + Vector dependencyList = ( Vector )dependencyMap.get( className ); + Hashtable dependencies = new Hashtable(); + classpathDependencies.put( className, dependencies ); + for( Enumeration e2 = dependencyList.elements(); e2.hasMoreElements(); ) + { + String dependency = ( String )e2.nextElement(); + Object classpathFileObject = classpathFileCache.get( dependency ); + if( classpathFileObject == null ) + { + classpathFileObject = nullFileMarker; + + if( !dependency.startsWith( "java." ) && !dependency.startsWith( "javax." ) ) + { + URL classURL = loader.getResource( dependency.replace( '.', '/' ) + ".class" ); + if( classURL != null ) + { + if( classURL.getProtocol().equals( "jar" ) ) + { + String jarFilePath = classURL.getFile(); + if( jarFilePath.startsWith( "file:" ) ) + { + int classMarker = jarFilePath.indexOf( '!' ); + jarFilePath = jarFilePath.substring( 5, classMarker ); + } + classpathFileObject = new File( jarFilePath ); + } + else if( classURL.getProtocol().equals( "file" ) ) + { + String classFilePath = classURL.getFile(); + classpathFileObject = new File( classFilePath ); + } + log( "Class " + className + + " depends on " + classpathFileObject + + " due to " + dependency, Project.MSG_DEBUG ); + } + } + classpathFileCache.put( dependency, classpathFileObject ); + } + if( classpathFileObject != null && classpathFileObject != nullFileMarker ) + { + // we need to add this jar to the list for this class. + File jarFile = ( File )classpathFileObject; + dependencies.put( jarFile, jarFile ); + } + } + } + } + + // write the dependency cache to the disk + if( cache != null && cacheDirty ) + { + writeCachedDependencies( dependencyMap ); + } + } + + /** + * Read the dependencies from cache file + * + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + private Hashtable readCachedDependencies() + throws IOException + { + Hashtable dependencyMap = new Hashtable(); + + if( cache != null ) + { + File depFile = new File( cache, CACHE_FILE_NAME ); + BufferedReader in = null; + if( depFile.exists() ) + { + try + { + in = new BufferedReader( new FileReader( depFile ) ); + String line = null; + Vector dependencyList = null; + String className = null; + int prependLength = CLASSNAME_PREPEND.length(); + while( ( line = in.readLine() ) != null ) + { + if( line.startsWith( CLASSNAME_PREPEND ) ) + { + dependencyList = new Vector(); + className = line.substring( prependLength ); + dependencyMap.put( className, dependencyList ); + } + else + { + dependencyList.addElement( line ); + } + } + } + finally + { + if( in != null ) + { + in.close(); + } + } + } + } + + return dependencyMap; + } + + /** + * Write the dependencies to cache file + * + * @param dependencyMap Description of Parameter + * @exception IOException Description of Exception + */ + private void writeCachedDependencies( Hashtable dependencyMap ) + throws IOException + { + if( cache != null ) + { + PrintWriter pw = null; + try + { + cache.mkdirs(); + File depFile = new File( cache, CACHE_FILE_NAME ); + + pw = new PrintWriter( new FileWriter( depFile ) ); + for( Enumeration deps = dependencyMap.keys(); deps.hasMoreElements(); ) + { + String className = ( String )deps.nextElement(); + + pw.println( CLASSNAME_PREPEND + className ); + + Vector dependencyList = ( Vector )dependencyMap.get( className ); + int size = dependencyList.size(); + for( int x = 0; x < size; x++ ) + { + pw.println( dependencyList.elementAt( x ) ); + } + } + } + finally + { + if( pw != null ) + { + pw.close(); + } + } + } + } + + /** + * A class (struct) user to manage information about a class + * + * @author RT + */ + private static class ClassFileInfo + { + /** + * The file where the class file is stored in the file system + */ + public File absoluteFile; + + /** + * The Java class name of this class + */ + public String className; + + /** + * The location of the file relative to its base directory - the root of + * the package namespace + */ + public String relativeName; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java new file mode 100644 index 000000000..e003411d4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Stack; +import java.util.Vector; + +/** + * An iterator which iterates through the contents of a java directory. The + * iterator should be created with the directory at the root of the Java + * namespace. + * + * @author Conor MacNeill + */ +public class DirectoryIterator implements ClassFileIterator +{ + + /** + * The length of the root directory. This is used to remove the root + * directory from full paths. + */ + int rootLength; + + /** + * The current directory iterator. As directories encounter lower level + * directories, the current iterator is pushed onto the iterator stack and a + * new iterator over the sub directory becomes the current directory. This + * implements a depth first traversal of the directory namespace. + */ + private Enumeration currentEnum; + + /** + * This is a stack of current iterators supporting the depth first traversal + * of the directory tree. + */ + private Stack enumStack; + + /** + * Creates a directory iterator. The directory iterator is created to scan + * the root directory. If the changeInto flag is given, then the entries + * returned will be relative to this directory and not the current + * directory. + * + * @param rootDirectory the root if the directory namespace which is to be + * iterated over + * @param changeInto if true then the returned entries will be relative to + * the rootDirectory and not the current directory. + * @exception IOException Description of Exception + * @throws IOException if there is a problem reading the directory + * information. + */ + public DirectoryIterator( File rootDirectory, boolean changeInto ) + throws IOException + { + super(); + + enumStack = new Stack(); + + if( rootDirectory.isAbsolute() || changeInto ) + { + rootLength = rootDirectory.getPath().length() + 1; + } + else + { + rootLength = 0; + } + + Vector filesInRoot = getDirectoryEntries( rootDirectory ); + + currentEnum = filesInRoot.elements(); + } + + /** + * Template method to allow subclasses to supply elements for the iteration. + * The directory iterator maintains a stack of iterators covering each level + * in the directory hierarchy. The current iterator covers the current + * directory being scanned. If the next entry in that directory is a + * subdirectory, the current iterator is pushed onto the stack and a new + * iterator is created for the subdirectory. If the entry is a file, it is + * returned as the next element and the iterator remains valid. If there are + * no more entries in the current directory, the topmost iterator on the + * statck is popped off to become the current iterator. + * + * @return the next ClassFile in the iteration. + */ + public ClassFile getNextClassFile() + { + ClassFile nextElement = null; + + try + { + while( nextElement == null ) + { + if( currentEnum.hasMoreElements() ) + { + File element = ( File )currentEnum.nextElement(); + + if( element.isDirectory() ) + { + + // push the current iterator onto the stack and then + // iterate through this directory. + enumStack.push( currentEnum ); + + Vector files = getDirectoryEntries( element ); + + currentEnum = files.elements(); + } + else + { + + // we have a file. create a stream for it + FileInputStream inFileStream = new FileInputStream( element ); + + if( element.getName().endsWith( ".class" ) ) + { + + // create a data input stream from the jar input stream + ClassFile javaClass = new ClassFile(); + + javaClass.read( inFileStream ); + + nextElement = javaClass; + } + } + } + else + { + // this iterator is exhausted. Can we pop one off the stack + if( enumStack.empty() ) + { + break; + } + else + { + currentEnum = ( Enumeration )enumStack.pop(); + } + } + } + } + catch( IOException e ) + { + nextElement = null; + } + + return nextElement; + } + + /** + * Get a vector covering all the entries (files and subdirectories in a + * directory). + * + * @param directory the directory to be scanned. + * @return a vector containing File objects for each entry in the directory. + */ + private Vector getDirectoryEntries( File directory ) + { + Vector files = new Vector(); + + // File[] filesInDir = directory.listFiles(); + String[] filesInDir = directory.list(); + + if( filesInDir != null ) + { + int length = filesInDir.length; + + for( int i = 0; i < length; ++i ) + { + files.addElement( new File( directory, filesInDir[i] ) ); + } + } + + return files; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java new file mode 100644 index 000000000..1715255bf --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * A class file iterator which iterates through the contents of a Java jar file. + * + * @author Conor MacNeill + */ +public class JarFileIterator implements ClassFileIterator +{ + private ZipInputStream jarStream; + + public JarFileIterator( InputStream stream ) + throws IOException + { + super(); + + jarStream = new ZipInputStream( stream ); + } + + public ClassFile getNextClassFile() + { + ZipEntry jarEntry; + ClassFile nextElement = null; + + try + { + jarEntry = jarStream.getNextEntry(); + + while( nextElement == null && jarEntry != null ) + { + String entryName = jarEntry.getName(); + + if( !jarEntry.isDirectory() && entryName.endsWith( ".class" ) ) + { + + // create a data input stream from the jar input stream + ClassFile javaClass = new ClassFile(); + + javaClass.read( jarStream ); + + nextElement = javaClass; + } + else + { + + jarEntry = jarStream.getNextEntry(); + } + } + } + catch( IOException e ) + { + String message = e.getMessage(); + String text = e.getClass().getName(); + + if( message != null ) + { + text += ": " + message; + } + + throw new RuntimeException( "Problem reading JAR file: " + text ); + } + + return nextElement; + } + + private byte[] getEntryBytes( InputStream stream ) + throws IOException + { + byte[] buffer = new byte[8192]; + ByteArrayOutputStream baos = new ByteArrayOutputStream( 2048 ); + int n; + + while( ( n = stream.read( buffer, 0, buffer.length ) ) != -1 ) + { + baos.write( buffer, 0, n ); + } + + return baos.toByteArray(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java new file mode 100644 index 000000000..919486a22 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * The constant pool entry which stores class information. + * + * @author Conor MacNeill + */ +public class ClassCPInfo extends ConstantPoolEntry +{ + + /** + * The class' name. This will be only valid if the entry has been resolved + * against the constant pool. + */ + private String className; + + /** + * The index into the constant pool where this class' name is stored. If the + * class name is changed, this entry is invalid until this entry is + * connected to a constant pool. + */ + private int index; + + /** + * Constructor. Sets the tag value for this entry to type Class + */ + public ClassCPInfo() + { + super( CONSTANT_Class, 1 ); + } + + /** + * Get the class name of this entry. + * + * @return the class' name. + */ + public String getClassName() + { + return className; + } + + /** + * Read the entry from a stream. + * + * @param cpStream the stream containing the constant pool entry to be read. + * @exception IOException thrown if there is a problem reading the entry + * from the stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + index = cpStream.readUnsignedShort(); + className = "unresolved"; + } + + /** + * Resolve this class info against the given constant pool. + * + * @param constantPool the constant pool with which to resolve the class. + */ + public void resolve( ConstantPool constantPool ) + { + className = ( ( Utf8CPInfo )constantPool.getEntry( index ) ).getValue(); + + super.resolve( constantPool ); + } + + /** + * Generate a string readable version of this entry + * + * @return Description of the Returned Value + */ + public String toString() + { + return "Class Constant Pool Entry for " + className + "[" + index + "]"; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java new file mode 100644 index 000000000..f8ba55828 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; + + +/** + * A Constant Pool entry which represents a constant value. + * + * @author Conor MacNeill + */ +public abstract class ConstantCPInfo extends ConstantPoolEntry +{ + + /** + * The entry's untyped value. Each subclass interprets the constant value + * based on the subclass's type. The value here must be compatible. + */ + private Object value; + + /** + * Initialise the constant entry. + * + * @param tagValue the constant pool entry type to be used. + * @param entries the number of constant pool entry slots occupied by this + * entry. + */ + protected ConstantCPInfo( int tagValue, int entries ) + { + super( tagValue, entries ); + } + + /** + * Set the constant value. + * + * @param newValue the new untyped value of this constant. + */ + public void setValue( Object newValue ) + { + value = newValue; + } + + /** + * Get the value of the constant. + * + * @return the value of the constant (untyped). + */ + public Object getValue() + { + return value; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java new file mode 100644 index 000000000..ee3131ec3 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +/** + * The constant pool of a Java class. The constant pool is a collection of + * constants used in a Java class file. It stores strings, constant values, + * class names, method names, field names etc. + * + * @author Conor MacNeill + * @see The Java Virtual + * Machine Specification + */ +public class ConstantPool +{ + + /** + * The entries in the constant pool. + */ + private Vector entries; + + /** + * A Hashtable of UTF8 entries - used to get constant pool indexes of the + * UTF8 values quickly + */ + private Hashtable utf8Indexes; + + /** + * Initialise the constant pool. + */ + public ConstantPool() + { + entries = new Vector(); + + // The zero index is never present in the constant pool itself so + // we add a null entry for it + entries.addElement( null ); + + utf8Indexes = new Hashtable(); + } + + /** + * Get the index of a given CONSTANT_Class entry in the constant pool. + * + * @param className the name of the class for which the class entry index is + * required. + * @return the index at which the given class entry occurs in the constant + * pool or -1 if the value does not occur. + */ + public int getClassEntry( String className ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof ClassCPInfo ) + { + ClassCPInfo classinfo = ( ClassCPInfo )element; + + if( classinfo.getClassName().equals( className ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given constant value entry in the constant pool. + * + * @param constantValue the constant value for which the index is required. + * @return the index at which the given value entry occurs in the constant + * pool or -1 if the value does not occur. + */ + public int getConstantEntry( Object constantValue ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof ConstantCPInfo ) + { + ConstantCPInfo constantEntry = ( ConstantCPInfo )element; + + if( constantEntry.getValue().equals( constantValue ) ) + { + index = i; + } + } + } + + return index; + } + + + /** + * Get an constant pool entry at a particular index. + * + * @param index the index into the constant pool. + * @return the constant pool entry at that index. + */ + public ConstantPoolEntry getEntry( int index ) + { + return ( ConstantPoolEntry )entries.elementAt( index ); + } + + /** + * Get the index of a given CONSTANT_FieldRef entry in the constant pool. + * + * @param fieldClassName the name of the class which contains the field + * being referenced. + * @param fieldName the name of the field being referenced. + * @param fieldType the type descriptor of the field being referenced. + * @return the index at which the given field ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getFieldRefEntry( String fieldClassName, String fieldName, String fieldType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof FieldRefCPInfo ) + { + FieldRefCPInfo fieldRefEntry = ( FieldRefCPInfo )element; + + if( fieldRefEntry.getFieldClassName().equals( fieldClassName ) && fieldRefEntry.getFieldName().equals( fieldName ) + && fieldRefEntry.getFieldType().equals( fieldType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_InterfaceMethodRef entry in the + * constant pool. + * + * @param interfaceMethodClassName the name of the interface which contains + * the method being referenced. + * @param interfaceMethodName the name of the method being referenced. + * @param interfaceMethodType the type descriptor of the metho dbeing + * referenced. + * @return the index at which the given method ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getInterfaceMethodRefEntry( String interfaceMethodClassName, String interfaceMethodName, String interfaceMethodType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof InterfaceMethodRefCPInfo ) + { + InterfaceMethodRefCPInfo interfaceMethodRefEntry = ( InterfaceMethodRefCPInfo )element; + + if( interfaceMethodRefEntry.getInterfaceMethodClassName().equals( interfaceMethodClassName ) + && interfaceMethodRefEntry.getInterfaceMethodName().equals( interfaceMethodName ) + && interfaceMethodRefEntry.getInterfaceMethodType().equals( interfaceMethodType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_MethodRef entry in the constant pool. + * + * @param methodClassName the name of the class which contains the method + * being referenced. + * @param methodName the name of the method being referenced. + * @param methodType the type descriptor of the metho dbeing referenced. + * @return the index at which the given method ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getMethodRefEntry( String methodClassName, String methodName, String methodType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof MethodRefCPInfo ) + { + MethodRefCPInfo methodRefEntry = ( MethodRefCPInfo )element; + + if( methodRefEntry.getMethodClassName().equals( methodClassName ) + && methodRefEntry.getMethodName().equals( methodName ) && methodRefEntry.getMethodType().equals( methodType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_NameAndType entry in the constant pool. + * + * @param name the name + * @param type the type + * @return the index at which the given NameAndType entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getNameAndTypeEntry( String name, String type ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof NameAndTypeCPInfo ) + { + NameAndTypeCPInfo nameAndTypeEntry = ( NameAndTypeCPInfo )element; + + if( nameAndTypeEntry.getName().equals( name ) && nameAndTypeEntry.getType().equals( type ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given UTF8 constant pool entry. + * + * @param value the string value of the UTF8 entry. + * @return the index at which the given string occurs in the constant pool + * or -1 if the value does not occur. + */ + public int getUTF8Entry( String value ) + { + int index = -1; + Integer indexInteger = ( Integer )utf8Indexes.get( value ); + + if( indexInteger != null ) + { + index = indexInteger.intValue(); + } + + return index; + } + + /** + * Add an entry to the constant pool. + * + * @param entry the new entry to be added to the constant pool. + * @return the index into the constant pool at which the entry is stored. + */ + public int addEntry( ConstantPoolEntry entry ) + { + int index = entries.size(); + + entries.addElement( entry ); + + int numSlots = entry.getNumEntries(); + + // add null entries for any additional slots required. + for( int j = 0; j < numSlots - 1; ++j ) + { + entries.addElement( null ); + } + + if( entry instanceof Utf8CPInfo ) + { + Utf8CPInfo utf8Info = ( Utf8CPInfo )entry; + + utf8Indexes.put( utf8Info.getValue(), new Integer( index ) ); + } + + return index; + } + + /** + * Read the constant pool from a class input stream. + * + * @param classStream the DataInputStream of a class file. + * @throws IOException if there is a problem reading the constant pool from + * the stream + */ + public void read( DataInputStream classStream ) + throws IOException + { + int numEntries = classStream.readUnsignedShort(); + + for( int i = 1; i < numEntries; ) + { + ConstantPoolEntry nextEntry = ConstantPoolEntry.readEntry( classStream ); + + i += nextEntry.getNumEntries(); + + addEntry( nextEntry ); + } + } + + /** + * Resolve the entries in the constant pool. Resolution of the constant pool + * involves transforming indexes to other constant pool entries into the + * actual data for that entry. + */ + public void resolve() + { + for( Enumeration i = entries.elements(); i.hasMoreElements(); ) + { + ConstantPoolEntry poolInfo = ( ConstantPoolEntry )i.nextElement(); + + if( poolInfo != null && !poolInfo.isResolved() ) + { + poolInfo.resolve( this ); + } + } + } + + /** + * Get the size of the constant pool. + * + * @return Description of the Returned Value + */ + public int size() + { + return entries.size(); + } + + /** + * Dump the constant pool to a string. + * + * @return the constant pool entries as strings + */ + public String toString() + { + StringBuffer sb = new StringBuffer( "\n" ); + int size = entries.size(); + + for( int i = 0; i < size; ++i ) + { + sb.append( "[" + i + "] = " + getEntry( i ) + "\n" ); + } + + return sb.toString(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java new file mode 100644 index 000000000..7e1a89c31 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * An entry in the constant pool. This class contains a represenation of the + * constant pool entries. It is an abstract base class for all the different + * forms of constant pool entry. + * + * @author Conor MacNeill + * @see ConstantPool + */ +public abstract class ConstantPoolEntry +{ + + /** + * Tag value for UTF8 entries. + */ + public final static int CONSTANT_Utf8 = 1; + + /** + * Tag value for Integer entries. + */ + public final static int CONSTANT_Integer = 3; + + /** + * Tag value for Float entries. + */ + public final static int CONSTANT_Float = 4; + + /** + * Tag value for Long entries. + */ + public final static int CONSTANT_Long = 5; + + /** + * Tag value for Double entries. + */ + public final static int CONSTANT_Double = 6; + + /** + * Tag value for Class entries. + */ + public final static int CONSTANT_Class = 7; + + /** + * Tag value for String entries. + */ + public final static int CONSTANT_String = 8; + + /** + * Tag value for Field Reference entries. + */ + public final static int CONSTANT_FieldRef = 9; + + /** + * Tag value for Method Reference entries. + */ + public final static int CONSTANT_MethodRef = 10; + + /** + * Tag value for Interface Method Reference entries. + */ + public final static int CONSTANT_InterfaceMethodRef = 11; + + /** + * Tag value for Name and Type entries. + */ + public final static int CONSTANT_NameAndType = 12; + + /** + * The number of slots in the constant pool, occupied by this entry. + */ + private int numEntries; + + /** + * A flag which indiciates if this entry has been resolved or not. + */ + private boolean resolved; + + /** + * This entry's tag which identifies the type of this constant pool entry. + */ + private int tag; + + /** + * Initialse the constant pool entry. + * + * @param tagValue the tag value which identifies which type of constant + * pool entry this is. + * @param entries the number of constant pool entry slots this entry + * occupies. + */ + public ConstantPoolEntry( int tagValue, int entries ) + { + tag = tagValue; + numEntries = entries; + resolved = false; + } + + /** + * Read a constant pool entry from a stream. This is a factory method which + * reads a constant pool entry form a stream and returns the appropriate + * subclass for the entry. + * + * @param cpStream the stream from which the constant pool entry is to be + * read. + * @return Description of the Returned Value + * @exception IOException Description of Exception + * @returns the appropriate ConstantPoolEntry subclass representing the + * constant pool entry from the stream. + * @throws IOExcception if there is a problem reading the entry from the + * stream. + */ + public static ConstantPoolEntry readEntry( DataInputStream cpStream ) + throws IOException + { + ConstantPoolEntry cpInfo = null; + int cpTag = cpStream.readUnsignedByte(); + + switch ( cpTag ) + { + + case CONSTANT_Utf8: + cpInfo = new Utf8CPInfo(); + + break; + case CONSTANT_Integer: + cpInfo = new IntegerCPInfo(); + + break; + case CONSTANT_Float: + cpInfo = new FloatCPInfo(); + + break; + case CONSTANT_Long: + cpInfo = new LongCPInfo(); + + break; + case CONSTANT_Double: + cpInfo = new DoubleCPInfo(); + + break; + case CONSTANT_Class: + cpInfo = new ClassCPInfo(); + + break; + case CONSTANT_String: + cpInfo = new StringCPInfo(); + + break; + case CONSTANT_FieldRef: + cpInfo = new FieldRefCPInfo(); + + break; + case CONSTANT_MethodRef: + cpInfo = new MethodRefCPInfo(); + + break; + case CONSTANT_InterfaceMethodRef: + cpInfo = new InterfaceMethodRefCPInfo(); + + break; + case CONSTANT_NameAndType: + cpInfo = new NameAndTypeCPInfo(); + + break; + default: + throw new ClassFormatError( "Invalid Constant Pool entry Type " + cpTag ); + } + + cpInfo.read( cpStream ); + + return cpInfo; + } + + /** + * Get the number of Constant Pool Entry slots within the constant pool + * occupied by this entry. + * + * @return the number of slots used. + */ + public final int getNumEntries() + { + return numEntries; + } + + /** + * Get the Entry's type tag. + * + * @return The Tag value of this entry + */ + public int getTag() + { + return tag; + } + + /** + * Indicates whether this entry has been resolved. In general a constant + * pool entry can reference another constant pool entry by its index value. + * Resolution involves replacing this index value with the constant pool + * entry at that index. + * + * @return true if this entry has been resolved. + */ + public boolean isResolved() + { + return resolved; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public abstract void read( DataInputStream cpStream ) + throws IOException; + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + resolved = true; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java new file mode 100644 index 000000000..94122a060 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * The constant pool entry subclass used to represent double constant values. + * + * @author Conor MacNeill + */ +public class DoubleCPInfo extends ConstantCPInfo +{ + public DoubleCPInfo() + { + super( CONSTANT_Double, 2 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Double( cpStream.readDouble() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Double Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java new file mode 100644 index 000000000..bf56aee85 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A FieldRef CP Info + * + * @author Conor MacNeill + */ +public class FieldRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String fieldClassName; + private String fieldName; + private String fieldType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public FieldRefCPInfo() + { + super( CONSTANT_FieldRef, 1 ); + } + + public String getFieldClassName() + { + return fieldClassName; + } + + public String getFieldName() + { + return fieldName; + } + + public String getFieldType() + { + return fieldType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo fieldClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + fieldClass.resolve( constantPool ); + + fieldClassName = fieldClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + fieldName = nt.getName(); + fieldType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Field : Class = " + fieldClassName + ", name = " + fieldName + ", type = " + fieldType; + } + else + { + value = "Field : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java new file mode 100644 index 000000000..affdeefc1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * A Float CP Info + * + * @author Conor MacNeill + */ +public class FloatCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public FloatCPInfo() + { + super( CONSTANT_Float, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Float( cpStream.readFloat() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Float Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java new file mode 100644 index 000000000..ce7acbc52 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * An Integer CP Info + * + * @author Conor MacNeill + */ +public class IntegerCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public IntegerCPInfo() + { + super( CONSTANT_Integer, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Integer( cpStream.readInt() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Integer Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java new file mode 100644 index 000000000..bc2077fed --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A InterfaceMethodRef CP Info + * + * @author Conor MacNeill + */ +public class InterfaceMethodRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String interfaceMethodClassName; + private String interfaceMethodName; + private String interfaceMethodType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public InterfaceMethodRefCPInfo() + { + super( CONSTANT_InterfaceMethodRef, 1 ); + } + + public String getInterfaceMethodClassName() + { + return interfaceMethodClassName; + } + + public String getInterfaceMethodName() + { + return interfaceMethodName; + } + + public String getInterfaceMethodType() + { + return interfaceMethodType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo interfaceMethodClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + interfaceMethodClass.resolve( constantPool ); + + interfaceMethodClassName = interfaceMethodClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + interfaceMethodName = nt.getName(); + interfaceMethodType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "InterfaceMethod : Class = " + interfaceMethodClassName + ", name = " + interfaceMethodName + ", type = " + + interfaceMethodType; + } + else + { + value = "InterfaceMethod : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java new file mode 100644 index 000000000..12f981faf --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * A Long CP Info + * + * @author Conor MacNeill + */ +public class LongCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public LongCPInfo() + { + super( CONSTANT_Long, 2 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Long( cpStream.readLong() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Long Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java new file mode 100644 index 000000000..a284adcf9 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A MethodRef CP Info + * + * @author Conor MacNeill + */ +public class MethodRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String methodClassName; + private String methodName; + private String methodType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public MethodRefCPInfo() + { + super( CONSTANT_MethodRef, 1 ); + } + + public String getMethodClassName() + { + return methodClassName; + } + + public String getMethodName() + { + return methodName; + } + + public String getMethodType() + { + return methodType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo methodClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + methodClass.resolve( constantPool ); + + methodClassName = methodClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + methodName = nt.getName(); + methodType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Method : Class = " + methodClassName + ", name = " + methodName + ", type = " + methodType; + } + else + { + value = "Method : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java new file mode 100644 index 000000000..8b769629b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A NameAndType CP Info + * + * @author Conor MacNeill + */ +public class NameAndTypeCPInfo extends ConstantPoolEntry +{ + private int descriptorIndex; + + private String name; + private int nameIndex; + private String type; + + /** + * Constructor. + */ + public NameAndTypeCPInfo() + { + super( CONSTANT_NameAndType, 1 ); + } + + public String getName() + { + return name; + } + + public String getType() + { + return type; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + nameIndex = cpStream.readUnsignedShort(); + descriptorIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + name = ( ( Utf8CPInfo )constantPool.getEntry( nameIndex ) ).getValue(); + type = ( ( Utf8CPInfo )constantPool.getEntry( descriptorIndex ) ).getValue(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Name = " + name + ", type = " + type; + } + else + { + value = "Name index = " + nameIndex + ", descriptor index = " + descriptorIndex; + } + + return value; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java new file mode 100644 index 000000000..f65ad291b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A String Constant Pool Entry. The String info contains an index into the + * constant pool where a UTF8 string is stored. + * + * @author Conor MacNeill + */ +public class StringCPInfo extends ConstantCPInfo +{ + + private int index; + + /** + * Constructor. + */ + public StringCPInfo() + { + super( CONSTANT_String, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + index = cpStream.readUnsignedShort(); + + setValue( "unresolved" ); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + setValue( ( ( Utf8CPInfo )constantPool.getEntry( index ) ).getValue() ); + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "String Constant Pool Entry for " + getValue() + "[" + index + "]"; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java new file mode 100644 index 000000000..dc42d1984 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A UTF8 Constant Pool Entry. + * + * @author Conor MacNeill + */ +public class Utf8CPInfo extends ConstantPoolEntry +{ + private String value; + + /** + * Constructor. + */ + public Utf8CPInfo() + { + super( CONSTANT_Utf8, 1 ); + } + + public String getValue() + { + return value; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + value = cpStream.readUTF(); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "UTF8 Value = " + value; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java new file mode 100644 index 000000000..371264a4a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java @@ -0,0 +1,965 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet; +import java.io.File;// ==================================================================== +// imports +// ==================================================================== +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.types.Path; + + +// ==================================================================== +/** + * This task compiles CSharp source into executables or modules. The task will + * only work on win2K until other platforms support csc.exe or an equivalent. + * CSC.exe must be on the execute path too.

              + * + * All parameters are optional: <csc/> should suffice to produce a debug + * build of all *.cs files. References to external files do require explicit + * enumeration, so are one of the first attributes to consider adding.

              + * + * The task is a directory based task, so attributes like includes="*.cs" + * and excludes="broken.cs" can be used to control the files pulled in. + * By default, all *.cs files from the project folder down are included in the + * command. When this happens the output file -if not specified- is taken as the + * first file in the list, which may be somewhat hard to control. Specifying the + * output file with 'outfile' seems prudent.

              + * + *

              + * + * TODO + *

                + *
              1. is incremental build still broken in beta-1? + *
              2. is Win32Icon broken? + *
              3. all the missing options + *
              + *

              + * + * History + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
              + * 0.3 + * + * Beta 1 edition + * + * To avoid having to remember which assemblies to include, the task + * automatically refers to the main dotnet libraries in Beta1. + *
              + * 0.2 + * + * Slightly different + * + * Split command execution to a separate class; + *
              + * 0.1 + * + * "I can't believe it's so rudimentary" + * + * First pass; minimal builds only support; + *
              + * + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.3 + */ + +public class CSharp + extends org.apache.tools.ant.taskdefs.MatchingTask +{ + + /** + * name of the executable. the .exe suffix is deliberately not included in + * anticipation of the unix version + */ + protected final static String csc_exe_name = "csc"; + + /** + * what is the file extension we search on? + */ + protected final static String csc_file_ext = "cs"; + + /** + * derive the search pattern from the extension + */ + protected final static String csc_file_pattern = "**/*." + csc_file_ext; + + /** + * Fix C# reference inclusion. C# is really dumb in how it handles + * inclusion. You have to list every 'assembly' -read DLL that is imported. + * So already you are making a platform assumption -shared libraries have a + * .dll;"+ extension and the poor developer has to know every library which + * is included why the compiler cant find classes on the path or in a + * directory, is a mystery. To reduce the need to be explicit, here is a + * long list of the core libraries used in Beta-1 of .NET ommitting the + * blatantly non portable (MS.win32.interop) and the .designer libraries. + * (ripping out Com was tempting) Casing is chosen to match that of the file + * system exactly so may work on a unix box too. + */ + + protected final static String DEFAULT_REFERENCE_LIST = + "Accessibility.dll;" + + "cscompmgd.dll;" + + "CustomMarshalers.dll;" + + "IEExecRemote.dll;" + + "IEHost.dll;" + + "IIEHost.dll;" + + "ISymWrapper.dll;" + + "Microsoft.JScript.dll;" + + "Microsoft.VisualBasic.dll;" + + "Microsoft.VisualC.dll;" + + "Microsoft.Vsa.dll;" + + "Mscorcfg.dll;" + + "RegCode.dll;" + + "System.Configuration.Install.dll;" + + "System.Data.dll;" + + "System.Design.dll;" + + "System.DirectoryServices.dll;" + + "System.EnterpriseServices.dll;" + + "System.dll;" + + "System.Drawing.Design.dll;" + + "System.Drawing.dll;" + + "System.Management.dll;" + + "System.Messaging.dll;" + + "System.Runtime.Remoting.dll;" + + "System.Runtime.Serialization.Formatters.Soap.dll;" + + "System.Security.dll;" + + "System.ServiceProcess.dll;" + + "System.Web.dll;" + + "System.Web.RegularExpressions.dll;" + + "System.Web.Services.dll;" + + "System.Windows.Forms.dll;" + + "System.XML.dll;"; + + /** + * utf out flag + */ + + protected boolean _utf8output = false; + + protected boolean _noconfig = false; + + // /fullpaths + protected boolean _fullpaths = false; + + /** + * debug flag. Controls generation of debug information. + */ + protected boolean _debug; + + /** + * output XML documentation flag + */ + protected File _docFile; + + /** + * any extra command options? + */ + protected String _extraOptions; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * flag to enable automatic reference inclusion + */ + protected boolean _includeDefaultReferences; + + /** + * incremental build flag + */ + protected boolean _incremental; + + /** + * main class (or null for automatic choice) + */ + protected String _mainClass; + + /** + * optimise flag + */ + protected boolean _optimize; + + /** + * output file. If not supplied this is derived from the source file + */ + protected File _outputFile; + + /** + * using the path approach didnt work as it could not handle the implicit + * execution path. Perhaps that could be extracted from the runtime and then + * the path approach would be viable + */ + protected Path _referenceFiles; + + /** + * list of reference classes. (pretty much a classpath equivalent) + */ + protected String _references; + + /** + * type of target. Should be one of exe|library|module|winexe|(null) default + * is exe; the actual value (if not null) is fed to the command line.
              + * See /target + */ + protected String _targetType; + + /** + * enable unsafe code flag. Clearly set to false by default + */ + protected boolean _unsafe; + + /** + * icon for incorporation into apps + */ + protected File _win32icon; + /** + * icon for incorporation into apps + */ + protected File _win32res; + + /** + * list of extra modules to refer to + */ + String _additionalModules; + + /** + * defines list something like 'RELEASE;WIN32;NO_SANITY_CHECKS;;SOMETHING_ELSE' + */ + String _definitions; + + /** + * destination directory (null means use the source directory) NB: this is + * currently not used + */ + private File _destDir; + + /** + * source directory upon which the search pattern is applied + */ + private File _srcDir; + + /** + * warning level: 0-4, with 4 being most verbose + */ + private int _warnLevel; + + /** + * constructor inits everything and set up the search pattern + */ + + public CSharp() + { + Clear(); + setIncludes( csc_file_pattern ); + } + + /** + * Set the definitions + * + * @param params The new AdditionalModules value + */ + public void setAdditionalModules( String params ) + { + _additionalModules = params; + } + + /** + * set the debug flag on or off + * + * @param f on/off flag + */ + public void setDebug( boolean f ) + { + _debug = f; + } + + /** + * Set the definitions + * + * @param params The new Definitions value + */ + public void setDefinitions( String params ) + { + _definitions = params; + } + + /** + * Set the destination dir to find the files to be compiled + * + * @param dirName The new DestDir value + */ + public void setDestDir( File dirName ) + { + _destDir = dirName; + } + + /** + * file for generated XML documentation + * + * @param f output file + */ + public void setDocFile( File f ) + { + _docFile = f; + } + + /** + * Sets the ExtraOptions attribute + * + * @param extraOptions The new ExtraOptions value + */ + public void setExtraOptions( String extraOptions ) + { + this._extraOptions = extraOptions; + } + + /** + * set fail on error flag + * + * @param b The new FailOnError value + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + public void setFullPaths( boolean enabled ) + { + _fullpaths = enabled; + } + + /** + * set the automatic reference inclusion flag on or off this flag controls + * the string of references and the /nostdlib option in CSC + * + * @param f on/off flag + */ + public void setIncludeDefaultReferences( boolean f ) + { + _includeDefaultReferences = f; + } + + /** + * set the incremental compilation flag on or off + * + * @param f on/off flag + */ + public void setIncremental( boolean f ) + { + _incremental = f; + } + + /** + * Sets the MainClass attribute + * + * @param mainClass The new MainClass value + */ + public void setMainClass( String mainClass ) + { + this._mainClass = mainClass; + } + + /** + * set the optimise flag on or off + * + * @param f on/off flag + */ + public void setOptimize( boolean f ) + { + _optimize = f; + } + + /** + * Set the definitions + * + * @param params The new OutputFile value + */ + public void setOutputFile( File params ) + { + _outputFile = params; + } + + /** + * add another path to the reference file path list + * + * @param path another path to append + */ + public void setReferenceFiles( Path path ) + { + //demand create pathlist + if( _referenceFiles == null ) + _referenceFiles = new Path( this.project ); + _referenceFiles.append( path ); + } + + /** + * Set the reference list to be used for this compilation. + * + * @param s The new References value + */ + public void setReferences( String s ) + { + _references = s; + } + + /** + * Set the source dir to find the files to be compiled + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + _srcDir = srcDirName; + } + + /** + * define the target + * + * @param targetType The new TargetType value + * @exception BuildException if target is not one of + * exe|library|module|winexe + */ + public void setTargetType( String targetType ) + throws BuildException + { + targetType = targetType.toLowerCase(); + if( targetType.equals( "exe" ) || targetType.equals( "library" ) || + targetType.equals( "module" ) || targetType.equals( "winexe" ) ) + { + _targetType = targetType; + } + else + throw new BuildException( "targetType " + targetType + " is not a valid type" ); + } + + /** + * Sets the Unsafe attribute + * + * @param unsafe The new Unsafe value + */ + public void setUnsafe( boolean unsafe ) + { + this._unsafe = unsafe; + } + + /** + * enable generation of utf8 output from the compiler. + * + * @param enabled The new Utf8Output value + */ + public void setUtf8Output( boolean enabled ) + { + _utf8output = enabled; + } + + /** + * set warn level (no range checking) + * + * @param warnLevel warn level -see .net docs for valid range (probably 0-4) + */ + public void setWarnLevel( int warnLevel ) + { + this._warnLevel = warnLevel; + } + + /** + * Set the win32 icon + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setWin32Icon( File fileName ) + { + _win32icon = fileName; + } + + /** + * Set the win32 icon + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setWin32Res( File fileName ) + { + _win32res = fileName; + } + + /** + * query the debug flag + * + * @return true if debug is turned on + */ + public boolean getDebug() + { + return _debug; + } + + /** + * Gets the ExtraOptions attribute + * + * @return The ExtraOptions value + */ + public String getExtraOptions() + { + return this._extraOptions; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * query the optimise flag + * + * @return true if optimise is turned on + */ + public boolean getIncludeDefaultReferences() + { + return _includeDefaultReferences; + } + + /** + * query the incrementalflag + * + * @return true iff incremental compilation is turned on + */ + public boolean getIncremental() + { + return _incremental; + } + + /** + * Gets the MainClass attribute + * + * @return The MainClass value + */ + public String getMainClass() + { + return this._mainClass; + } + + /** + * query the optimise flag + * + * @return true if optimise is turned on + */ + public boolean getOptimize() + { + return _optimize; + } + + /** + * Gets the TargetType attribute + * + * @return The TargetType value + */ + public String getTargetType() + { + return _targetType; + } + + /** + * query the Unsafe attribute + * + * @return The Unsafe value + */ + public boolean getUnsafe() + { + return this._unsafe; + } + + /** + * query warn level + * + * @return current value + */ + public int getWarnLevel() + { + return _warnLevel; + } + + /** + * reset all contents. + */ + public void Clear() + { + _targetType = null; + _win32icon = null; + _srcDir = null; + _destDir = null; + _mainClass = null; + _unsafe = false; + _warnLevel = 3; + _docFile = null; + _incremental = false; + _optimize = false; + _debug = true; + _references = null; + _failOnError = true; + _definitions = null; + _additionalModules = null; + _includeDefaultReferences = true; + _extraOptions = null; + _fullpaths = true; + } + + /** + * do the work by building the command line and then calling it + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( _srcDir == null ) + _srcDir = project.resolveFile( "." ); + + NetCommand command = new NetCommand( this, "CSC", csc_exe_name ); + command.setFailOnError( getFailFailOnError() ); + //DEBUG helper + command.setTraceCommandLine( true ); + //fill in args + command.addArgument( "/nologo" ); + command.addArgument( getAdditionalModulesParameter() ); + command.addArgument( getDefinitionsParameter() ); + command.addArgument( getDebugParameter() ); + command.addArgument( getDocFileParameter() ); + command.addArgument( getIncrementalParameter() ); + command.addArgument( getMainClassParameter() ); + command.addArgument( getOptimizeParameter() ); + command.addArgument( getReferencesParameter() ); + command.addArgument( getTargetTypeParameter() ); + command.addArgument( getUnsafeParameter() ); + command.addArgument( getWarnLevelParameter() ); + command.addArgument( getWin32IconParameter() ); + command.addArgument( getOutputFileParameter() ); + command.addArgument( getIncludeDefaultReferencesParameter() ); + command.addArgument( getDefaultReferenceParameter() ); + command.addArgument( getWin32ResParameter() ); + command.addArgument( getUtf8OutpuParameter() ); + command.addArgument( getNoConfigParameter() ); + command.addArgument( getFullPathsParameter() ); + command.addArgument( getExtraOptionsParameter() ); + + //get dependencies list. + DirectoryScanner scanner = super.getDirectoryScanner( _srcDir ); + String[] dependencies = scanner.getIncludedFiles(); + log( "compiling " + dependencies.length + " file" + ( ( dependencies.length == 1 ) ? "" : "s" ) ); + String baseDir = scanner.getBasedir().toString(); + //add to the command + for( int i = 0; i < dependencies.length; i++ ) + { + String targetFile = dependencies[i]; + targetFile = baseDir + File.separator + targetFile; + command.addArgument( targetFile ); + } + + //now run the command of exe + settings + files + command.runCommand(); + } + + protected void setNoConfig( boolean enabled ) + { + _noconfig = enabled; + } + + /** + * get the argument or null for no argument needed + * + * @return The AdditionalModules Parameter to CSC + */ + protected String getAdditionalModulesParameter() + { + if( notEmpty( _additionalModules ) ) + return "/addmodule:" + _additionalModules; + else + return null; + } + + /** + * get the debug switch argument + * + * @return The Debug Parameter to CSC + */ + protected String getDebugParameter() + { + return "/debug" + ( _debug ? "+" : "-" ); + } + + + /** + * get default reference list + * + * @return null or a string of references. + */ + protected String getDefaultReferenceParameter() + { + if( _includeDefaultReferences ) + { + StringBuffer s = new StringBuffer( "/reference:" ); + s.append( DEFAULT_REFERENCE_LIST ); + return new String( s ); + } + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Definitions Parameter to CSC + */ + protected String getDefinitionsParameter() + { + if( notEmpty( _definitions ) ) + return "/define:" + _definitions; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The DocFile Parameter to CSC + */ + protected String getDocFileParameter() + { + if( _docFile != null ) + return "/doc:" + _docFile.toString(); + else + return null; + } + + /** + * get any extra options or null for no argument needed + * + * @return The ExtraOptions Parameter to CSC + */ + protected String getExtraOptionsParameter() + { + if( _extraOptions != null && _extraOptions.length() != 0 ) + return _extraOptions; + else + return null; + } + + protected String getFullPathsParameter() + { + return _fullpaths ? "/fullpaths" : null; + } + + /** + * get the include default references flag or null for no argument needed + * + * @return The Parameter to CSC + */ + protected String getIncludeDefaultReferencesParameter() + { + return "/nostdlib" + ( _includeDefaultReferences ? "-" : "+" ); + } + + /** + * get the incremental build argument + * + * @return The Incremental Parameter to CSC + */ + protected String getIncrementalParameter() + { + return "/incremental" + ( _incremental ? "+" : "-" ); + } + + /** + * get the /main argument or null for no argument needed + * + * @return The MainClass Parameter to CSC + */ + protected String getMainClassParameter() + { + if( _mainClass != null && _mainClass.length() != 0 ) + return "/main:" + _mainClass; + else + return null; + } + + protected String getNoConfigParameter() + { + return _noconfig ? "/noconfig" : null; + } + + /** + * get the optimise flag or null for no argument needed + * + * @return The Optimize Parameter to CSC + */ + protected String getOptimizeParameter() + { + return "/optimize" + ( _optimize ? "+" : "-" ); + } + + /** + * get the argument or null for no argument needed + * + * @return The OutputFile Parameter to CSC + */ + protected String getOutputFileParameter() + { + if( _outputFile != null ) + { + File f = _outputFile; + return "/out:" + f.toString(); + } + else + return null; + } + + /** + * turn the path list into a list of files and a /references argument + * + * @return null or a string of references. + */ + protected String getReferenceFilesParameter() + { + //bail on no references + if( _references == null ) + return null; + //iterate through the ref list & generate an entry for each + //or just rely on the fact that the toString operator does this, but + //noting that the separator is ';' on windows, ':' on unix + String refpath = _references.toString(); + + //bail on no references listed + if( refpath.length() == 0 ) + return null; + + StringBuffer s = new StringBuffer( "/reference:" ); + s.append( refpath ); + return new String( s ); + } + + /** + * get the reference string or null for no argument needed + * + * @return The References Parameter to CSC + */ + protected String getReferencesParameter() + { + //bail on no references + if( notEmpty( _references ) ) + return "/reference:" + _references; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The TargetType Parameter to CSC + */ + protected String getTargetTypeParameter() + { + if( notEmpty( _targetType ) ) + return "/target:" + _targetType; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Unsafe Parameter to CSC + */ + protected String getUnsafeParameter() + { + return _unsafe ? "/unsafe" : null; + } + + protected String getUtf8OutpuParameter() + { + return _utf8output ? "/utf8output" : null; + } + + /** + * get the warn level switch + * + * @return The WarnLevel Parameter to CSC + */ + protected String getWarnLevelParameter() + { + return "/warn:" + _warnLevel; + } + + /** + * get the argument or null for no argument needed + * + * @return The Win32Icon Parameter to CSC + */ + protected String getWin32IconParameter() + { + if( _win32icon != null ) + return "/win32icon:" + _win32icon.toString(); + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Win32Icon Parameter to CSC + */ + protected String getWin32ResParameter() + { + if( _win32res != null ) + return "/win32res:" + _win32res.toString(); + else + return null; + } + + /** + * test for a string containing something useful + * + * @param s string in + * @return true if the argument is not null or empty + */ + protected boolean notEmpty( String s ) + { + return s != null && s.length() != 0; + }// end execute + +}//end class diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java new file mode 100644 index 000000000..b1440d32f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet;// ==================================================================== +// imports +// ==================================================================== +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; + + +/** + * Task to assemble .net 'Intermediate Language' files. The task will only work + * on win2K until other platforms support csc.exe or an equivalent. ilasm.exe + * must be on the execute path too.

              + * + *

              + * + * All parameters are optional: <il/> should suffice to produce a debug + * build of all *.il files. The option set is roughly compatible with the CSharp + * class; even though the command line options are only vaguely equivalent. [The + * low level commands take things like /OUT=file, csc wants /out:file ... + * /verbose is used some places; /quiet here in ildasm... etc.] It would be nice + * if someone made all the command line tools consistent (and not as brittle as + * the java cmdline tools)

              + * + * The task is a directory based task, so attributes like includes="*.il" + * and excludes="broken.il" can be used to control the files pulled in. + * Each file is built on its own, producing an appropriately named output file + * unless manually specified with outfile + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.2 + */ + +public class Ilasm + extends org.apache.tools.ant.taskdefs.MatchingTask +{ + + /** + * name of the executable. the .exe suffix is deliberately not included in + * anticipation of the unix version + */ + protected final static String exe_name = "ilasm"; + + /** + * what is the file extension we search on? + */ + protected final static String file_ext = "il"; + + /** + * and now derive the search pattern from the extension + */ + protected final static String file_pattern = "**/*." + file_ext; + + /** + * title of task for external presentation + */ + protected final static String exe_title = "ilasm"; + + /** + * debug flag. Controls generation of debug information. + */ + protected boolean _debug; + + /** + * any extra command options? + */ + protected String _extraOptions; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * listing flag + */ + + protected boolean _listing; + + /** + * output file. If not supplied this is derived from the source file + */ + protected File _outputFile; + + /** + * resource file (.res format) to include in the app. + */ + protected File _resourceFile; + + /** + * type of target. Should be one of exe|library|module|winexe|(null) default + * is exe; the actual value (if not null) is fed to the command line.
              + * See /target + */ + protected String _targetType; + + /** + * verbose flag + */ + protected boolean _verbose; + + /** + * file containing private key + */ + + private File _keyfile; + + /** + * source directory upon which the search pattern is applied + */ + private File _srcDir; + + /** + * constructor inits everything and set up the search pattern + */ + public Ilasm() + { + Clear(); + setIncludes( file_pattern ); + } + + /** + * set the debug flag on or off + * + * @param f on/off flag + */ + public void setDebug( boolean f ) + { + _debug = f; + } + + /** + * Sets the ExtraOptions attribute + * + * @param extraOptions The new ExtraOptions value + */ + public void setExtraOptions( String extraOptions ) + { + this._extraOptions = extraOptions; + } + + /** + * set fail on error flag + * + * @param b The new FailOnError value + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + public void setKeyfile( File keyfile ) + { + this._keyfile = keyfile; + } + + /** + * enable/disable listing + * + * @param b flag set to true for listing on + */ + public void setListing( boolean b ) + { + _listing = b; + } + + /** + * Set the definitions + * + * @param params The new OutputFile value + */ + public void setOutputFile( File params ) + { + _outputFile = params; + } + + + /** + * Sets the Owner attribute + * + * @param s The new Owner value + */ + + public void setOwner( String s ) + { + log( "This option is not supported by ILASM as of Beta-2, and will be ignored", Project.MSG_WARN ); + } + + /** + * Set the resource file + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setResourceFile( File fileName ) + { + _resourceFile = fileName; + } + + /** + * Set the source dir to find the files to be compiled + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + _srcDir = srcDirName; + } + + /** + * define the target + * + * @param targetType one of exe|library| + * @exception BuildException if target is not one of + * exe|library|module|winexe + */ + + public void setTargetType( String targetType ) + throws BuildException + { + targetType = targetType.toLowerCase(); + if( targetType.equals( "exe" ) || targetType.equals( "library" ) ) + { + _targetType = targetType; + } + else + throw new BuildException( "targetType " + targetType + " is not a valid type" ); + } + + /** + * enable/disable verbose ILASM output + * + * @param b flag set to true for verbose on + */ + public void setVerbose( boolean b ) + { + _verbose = b; + } + + /** + * query the debug flag + * + * @return true if debug is turned on + */ + public boolean getDebug() + { + return _debug; + } + + /** + * Gets the ExtraOptions attribute + * + * @return The ExtraOptions value + */ + public String getExtraOptions() + { + return this._extraOptions; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * accessor method for target type + * + * @return the current target option + */ + public String getTargetType() + { + return _targetType; + } + + /** + * reset all contents. + */ + public void Clear() + { + _targetType = null; + _srcDir = null; + _listing = false; + _verbose = false; + _debug = true; + _outputFile = null; + _failOnError = true; + _resourceFile = null; + _extraOptions = null; + } + + + /** + * This is the execution entry point. Build a list of files and call ilasm + * on each of them. + * + * @throws BuildException if the assembly failed and FailOnError is true + */ + public void execute() + throws BuildException + { + if( _srcDir == null ) + _srcDir = project.resolveFile( "." ); + + //get dependencies list. + DirectoryScanner scanner = super.getDirectoryScanner( _srcDir ); + String[] dependencies = scanner.getIncludedFiles(); + log( "assembling " + dependencies.length + " file" + ( ( dependencies.length == 1 ) ? "" : "s" ) ); + String baseDir = scanner.getBasedir().toString(); + //add to the command + for( int i = 0; i < dependencies.length; i++ ) + { + String targetFile = dependencies[i]; + targetFile = baseDir + File.separator + targetFile; + executeOneFile( targetFile ); + } + + }// end execute + + + /** + * do the work for one file by building the command line then calling it + * + * @param targetFile name of the the file to assemble + * @throws BuildException if the assembly failed and FailOnError is true + */ + public void executeOneFile( String targetFile ) + throws BuildException + { + NetCommand command = new NetCommand( this, exe_title, exe_name ); + command.setFailOnError( getFailFailOnError() ); + //DEBUG helper + command.setTraceCommandLine( true ); + //fill in args + command.addArgument( getDebugParameter() ); + command.addArgument( getTargetTypeParameter() ); + command.addArgument( getListingParameter() ); + command.addArgument( getOutputFileParameter() ); + command.addArgument( getResourceFileParameter() ); + command.addArgument( getVerboseParameter() ); + command.addArgument( getKeyfileParameter() ); + command.addArgument( getExtraOptionsParameter() ); + + /* + * space for more argumentativeness + * command.addArgument(); + * command.addArgument(); + */ + command.addArgument( targetFile ); + //now run the command of exe + settings + file + command.runCommand(); + } + + /** + * get the argument or null for no argument needed + * + * @return The DebugParameter value + */ + protected String getDebugParameter() + { + return _debug ? "/debug" : null; + } + + /** + * get any extra options or null for no argument needed + * + * @return The ExtraOptions Parameter to CSC + */ + protected String getExtraOptionsParameter() + { + if( _extraOptions != null && _extraOptions.length() != 0 ) + return _extraOptions; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The KeyfileParameter value + */ + protected String getKeyfileParameter() + { + if( _keyfile != null ) + return "/keyfile:" + _keyfile.toString(); + else + return null; + } + + /** + * turn the listing flag into a parameter for ILASM + * + * @return the appropriate string from the state of the listing flag + */ + protected String getListingParameter() + { + return _listing ? "/listing" : "/nolisting"; + } + + /** + * get the output file + * + * @return the argument string or null for no argument + */ + protected String getOutputFileParameter() + { + if( _outputFile == null || _outputFile.length() == 0 ) + return null; + File f = _outputFile; + return "/output=" + f.toString(); + } + + protected String getResourceFileParameter() + { + if( _resourceFile != null ) + { + return "/resource=" + _resourceFile.toString(); + } + else + { + return null; + } + } + + /** + * g get the target type or null for no argument needed + * + * @return The TargetTypeParameter value + */ + + protected String getTargetTypeParameter() + { + if( !notEmpty( _targetType ) ) + return null; + if( _targetType.equals( "exe" ) ) + return "/exe"; + else + if( _targetType.equals( "library" ) ) + return "/dll"; + else + return null; + } + + /** + * turn the verbose flag into a parameter for ILASM + * + * @return null or the appropriate command line string + */ + protected String getVerboseParameter() + { + return _verbose ? null : "/quiet"; + } + + /** + * test for a string containing something useful + * + * @param s Description of Parameter + * @return Description of the Returned Value + * @returns true if the argument is not null or empty + */ + protected boolean notEmpty( String s ) + { + return s != null && s.length() != 0; + }// end executeOneFile +}//class diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java new file mode 100644 index 000000000..4f28dffe7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet;// imports +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * This is a helper class to spawn net commands out. In its initial form it + * contains no .net specifics, just contains all the command line/exe + * construction stuff. However, it may be handy in future to have a means of + * setting the path to point to the dotnet bin directory; in which case the + * shared code should go in here. + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.3 + * @created 2000-11-01 + */ + +public class NetCommand +{ + + /** + * trace flag + */ + protected boolean _traceCommandLine = false; + + /** + * what is the command line + */ + protected Commandline _commandLine; + + /** + * executabe + */ + protected Execute _exe; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * owner project + */ + protected Task _owner; + + /** + * actual program to invoke + */ + protected String _program; + + /** + * title of the command + */ + protected String _title; + + /** + * constructor + * + * @param title (for logging/errors) + * @param owner Description of Parameter + * @param program Description of Parameter + */ + + public NetCommand( Task owner, String title, String program ) + { + _owner = owner; + _title = title; + _program = program; + _commandLine = new Commandline(); + _commandLine.setExecutable( _program ); + prepareExecutor(); + } + + /** + * set fail on error flag + * + * @param b fail flag -set to true to cause an exception to be raised if the + * return value != 0 + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + /** + * turn tracing on or off + * + * @param b trace flag + */ + public void setTraceCommandLine( boolean b ) + { + _traceCommandLine = b; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * add an argument to a command line; do nothing if the arg is null or empty + * string + * + * @param argument The feature to be added to the Argument attribute + */ + public void addArgument( String argument ) + { + if( argument != null && argument.length() != 0 ) + { + _commandLine.createArgument().setValue( argument ); + } + } + + /** + * Run the command using the given Execute instance. + * + * @exception BuildException Description of Exception + * @throws an exception of something goes wrong and the failOnError flag is + * true + */ + public void runCommand() + throws BuildException + { + int err = -1;// assume the worst + try + { + if( _traceCommandLine ) + { + _owner.log( _commandLine.toString() ); + } + else + { + //in verbose mode we always log stuff + logVerbose( _commandLine.toString() ); + } + _exe.setCommandline( _commandLine.getCommandline() ); + err = _exe.execute(); + if( err != 0 ) + { + if( _failOnError ) + { + throw new BuildException( _title + " returned: " + err, _owner.getLocation() ); + } + else + { + _owner.log( _title + " Result: " + err, Project.MSG_ERR ); + } + } + } + catch( IOException e ) + { + throw new BuildException( _title + " failed: " + e, e, _owner.getLocation() ); + } + } + + + /** + * error text log + * + * @param msg message to display as an error + */ + protected void logError( String msg ) + { + _owner.getProject().log( msg, Project.MSG_ERR ); + } + + /** + * verbose text log + * + * @param msg string to add to log iff verbose is defined for the build + */ + protected void logVerbose( String msg ) + { + _owner.getProject().log( msg, Project.MSG_VERBOSE ); + } + + /** + * set up the command sequence.. + */ + protected void prepareExecutor() + { + // default directory to the project's base directory + File dir = _owner.getProject().getBaseDir(); + ExecuteStreamHandler handler = new LogStreamHandler( _owner, + Project.MSG_INFO, Project.MSG_WARN ); + _exe = new Execute( handler, null ); + _exe.setAntRun( _owner.getProject() ); + _exe.setWorkingDirectory( dir ); + } +}//class diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java new file mode 100644 index 000000000..ff7a72719 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + + +/** + * BorlandDeploymentTool is dedicated to the Borland Application Server 4.5 and + * 4.5.1 This task generates and compiles the stubs and skeletons for all ejb + * described into the Deployement Descriptor, builds the jar file including the + * support files and verify whether the produced jar is valid or not. The + * supported options are: + *

                + *
              • debug (boolean) : turn on the debug mode for generation of stubs and + * skeletons (default:false)
              • + *
              • verify (boolean) : turn on the verification at the end of the jar + * production (default:true)
              • + *
              • verifyargs (String) : add optional argument to verify command (see vbj + * com.inprise.ejb.util.Verify)
              • + *
              • basdtd (String) : location of the BAS DTD
              • + *
              • generateclient (boolean) : turn on the client jar file generation + *
              • + *
              + *
              + *
              + *      <ejbjar srcdir="${build.classes}"  basejarname="vsmp"  descriptordir="${rsc.dir}/hrmanager">
              + *        <borland destdir="tstlib">
              + *          <classpath refid="classpath" />
              + *        </borland>
              + *        <include name="**\ejb-jar.xml"/>
              + *        <support dir="${build.classes}">
              + *          <include name="demo\smp\*.class"/>
              + *          <include name="demo\helper\*.class"/>
              + *         </support>
              + *     </ejbjar>
              + *
              + * + * @author Benoit Moussaud + */ +public class BorlandDeploymentTool extends GenericDeploymentTool implements ExecuteStreamHandler +{ + public final static String PUBLICID_BORLAND_EJB + = "-//Inprise Corporation//DTD Enterprise JavaBeans 1.1//EN"; + + protected final static String DEFAULT_BAS45_EJB11_DTD_LOCATION + = "/com/inprise/j2ee/xml/dtds/ejb-jar.dtd"; + + protected final static String DEFAULT_BAS_DTD_LOCATION + = "/com/inprise/j2ee/xml/dtds/ejb-inprise.dtd"; + + protected final static String BAS_DD = "ejb-inprise.xml"; + + /** + * Java2iiop executable * + */ + protected final static String JAVA2IIOP = "java2iiop"; + + /** + * Verify class + */ + protected final static String VERIFY = "com.inprise.ejb.util.Verify"; + + /** + * Instance variable that stores the suffix for the borland jarfile. + */ + private String jarSuffix = "-ejb.jar"; + + /** + * Instance variable that determines whether the debug mode is on + */ + private boolean java2iiopdebug = false; + + /** + * Instance variable that determines whetger the client jar file is + * generated + */ + private boolean generateclient = false; + /** + * Instance variable that determines whether it is necessary to verify the + * produced jar + */ + private boolean verify = true; + private String verifyArgs = ""; + + private Hashtable _genfiles = new Hashtable(); + + /** + * Instance variable that stores the location of the borland DTD file. + */ + private String borlandDTD; + + /** + * Setter used to store the location of the borland DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setBASdtd( String inString ) + { + this.borlandDTD = inString; + } + + /** + * set the debug mode for java2iiop (default false) + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.java2iiopdebug = debug; + } + + + /** + * setter used to store whether the task will include the generate client + * task. (see : BorlandGenerateClient task) + * + * @param b The new Generateclient value + */ + public void setGenerateclient( boolean b ) + { + this.generateclient = b; + } + + /** + * @param is The new ProcessErrorStream value + * @exception IOException Description of Exception + */ + public void setProcessErrorStream( InputStream is ) + throws IOException + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String s = reader.readLine(); + if( s != null ) + { + log( "[java2iiop] " + s, Project.MSG_DEBUG ); + }// end of if () + } + + public void setProcessInputStream( OutputStream param1 ) + throws IOException { } + + /** + * @param is + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + try + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String javafile; + while( ( javafile = reader.readLine() ) != null ) + { + log( "buffer:" + javafile, Project.MSG_DEBUG ); + if( javafile.endsWith( ".java" ) ) + { + String classfile = toClassFile( javafile ); + String key = classfile.substring( getConfig().srcDir.getAbsolutePath().length() + 1 ); + log( " generated : " + classfile, Project.MSG_DEBUG ); + log( " key : " + key, Project.MSG_DEBUG ); + _genfiles.put( key, new File( classfile ) ); + }// end of if () + }// end of while () + reader.close(); + } + catch( Exception e ) + { + String msg = "Exception while parsing java2iiop output. Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + + + /** + * Setter used to store the suffix for the generated borland jar file. + * + * @param inString the string to use as the suffix. + */ + public void setSuffix( String inString ) + { + this.jarSuffix = inString; + } + + /** + * set the verify mode for the produced jar (default true) + * + * @param verify The new Verify value + */ + public void setVerify( boolean verify ) + { + this.verify = verify; + } + + + /** + * sets some additional args to send to verify command + * + * @param args addtions command line parameters + */ + public void setVerifyArgs( String args ) + { + this.verifyArgs = args; + } + + // implementation of org.apache.tools.ant.taskdefs.ExecuteStreamHandler interface + + public void start() + throws IOException { } + + public void stop() { } + + + protected DescriptorHandler getBorlandDescriptorHandler( final File srcDir ) + { + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + protected void processElement() + { + if( currentElement.equals( "type-storage" ) ) + { + // Get the filename of vendor specific descriptor + String fileNameWithMETA = currentText; + //trim the META_INF\ off of the file name + String fileName = fileNameWithMETA.substring( META_DIR.length(), + fileNameWithMETA.length() ); + File descriptorFile = new File( srcDir, fileName ); + + ejbFiles.put( fileNameWithMETA, descriptorFile ); + } + } + }; + handler.registerDTD( PUBLICID_BORLAND_EJB, + borlandDTD == null ? DEFAULT_BAS_DTD_LOCATION : borlandDTD ); + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + + File borlandDD = new File( getConfig().descriptorDir, ddPrefix + BAS_DD ); + if( borlandDD.exists() ) + { + log( "Borland specific file found " + borlandDD, Project.MSG_VERBOSE ); + ejbFiles.put( META_DIR + BAS_DD, borlandDD ); + } + else + { + log( "Unable to locate borland deployment descriptor. It was expected to be in " + + borlandDD.getPath(), Project.MSG_WARN ); + return; + } + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarFile, Hashtable files, String publicId ) + throws BuildException + { + //build the home classes list. + Vector homes = new Vector(); + Iterator it = files.keySet().iterator(); + while( it.hasNext() ) + { + String clazz = ( String )it.next(); + if( clazz.endsWith( "Home.class" ) ) + { + //remove .class extension + String home = toClass( clazz ); + homes.add( home ); + log( " Home " + home, Project.MSG_VERBOSE ); + }// end of if () + }// end of while () + + buildBorlandStubs( homes.iterator(), files ); + + //add the gen files to the collection + files.putAll( _genfiles ); + + super.writeJar( baseName, jarFile, files, publicId ); + + if( verify ) + { + verifyBorlandJar( jarFile ); + }// end of if () + + if( generateclient ) + { + generateClient( jarFile ); + }// end of if () + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } + + /** + * Generate stubs & sketelton for each home found into the DD Add all the + * generate class file into the ejb files + * + * @param ithomes : iterator on home class + * @param files : file list , updated by the adding generated files + */ + private void buildBorlandStubs( Iterator ithomes, Hashtable files ) + { + Execute execTask = null; + + execTask = new Execute( this ); + Project project = getTask().getProject(); + execTask.setAntRun( project ); + execTask.setWorkingDirectory( project.getBaseDir() ); + + Commandline commandline = new Commandline(); + commandline.setExecutable( JAVA2IIOP ); + //debug ? + if( java2iiopdebug ) + { + commandline.createArgument().setValue( "-VBJdebug" ); + }// end of if () + //set the classpath + commandline.createArgument().setValue( "-VBJclasspath" ); + commandline.createArgument().setPath( getCombinedClasspath() ); + //list file + commandline.createArgument().setValue( "-list_files" ); + //no TIE classes + commandline.createArgument().setValue( "-no_tie" ); + //root dir + commandline.createArgument().setValue( "-root_dir" ); + commandline.createArgument().setValue( getConfig().srcDir.getAbsolutePath() ); + //compiling order + commandline.createArgument().setValue( "-compile" ); + //add the home class + while( ithomes.hasNext() ) + { + commandline.createArgument().setValue( ithomes.next().toString() ); + }// end of while () + + try + { + log( "Calling java2iiop", Project.MSG_VERBOSE ); + log( commandline.toString(), Project.MSG_DEBUG ); + execTask.setCommandline( commandline.getCommandline() ); + int result = execTask.execute(); + if( result != 0 ) + { + String msg = "Failed executing java2iiop (ret code is " + result + ")"; + throw new BuildException( msg, getTask().getLocation() ); + } + } + catch( java.io.IOException e ) + { + log( "java2iiop exception :" + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( e ); + } + } + + /** + * Generate the client jar corresponding to the jar file passed as paremeter + * the method uses the BorlandGenerateClient task. + * + * @param sourceJar java.io.File representing the produced jar file + */ + private void generateClient( File sourceJar ) + { + getTask().getProject().addTaskDefinition( "internal_bas_generateclient", + org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient.class ); + + org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient gentask = null; + log( "generate client for " + sourceJar, Project.MSG_INFO ); + try + { + String args = verifyArgs; + args += " " + sourceJar.getPath(); + + gentask = ( BorlandGenerateClient )getTask().getProject().createTask( "internal_bas_generateclient" ); + gentask.setEjbjar( sourceJar ); + gentask.setDebug( java2iiopdebug ); + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + gentask.setClasspath( classpath ); + } + gentask.setTaskName( "generate client" ); + gentask.execute(); + } + catch( Exception e ) + { + //TO DO : delete the file if it is not a valid file. + String msg = "Exception while calling " + VERIFY + " Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + + /** + * convert a class file name : A/B/C/toto.class into a class name: + * A.B.C.toto + * + * @param filename Description of Parameter + * @return Description of the Returned Value + */ + private String toClass( String filename ) + { + //remove the .class + String classname = filename.substring( 0, filename.lastIndexOf( ".class" ) ); + classname = classname.replace( '\\', '.' ); + return classname; + } + + /** + * convert a file name : A/B/C/toto.java into a class name: A/B/C/toto.class + * + * @param filename Description of Parameter + * @return Description of the Returned Value + */ + private String toClassFile( String filename ) + { + //remove the .class + String classfile = filename.substring( 0, filename.lastIndexOf( ".java" ) ); + classfile = classfile + ".class"; + return classfile; + } + + /** + * Verify the produced jar file by invoking the Borland verify tool + * + * @param sourceJar java.io.File representing the produced jar file + */ + private void verifyBorlandJar( File sourceJar ) + { + org.apache.tools.ant.taskdefs.Java javaTask = null; + log( "verify " + sourceJar, Project.MSG_INFO ); + try + { + + String args = verifyArgs; + args += " " + sourceJar.getPath(); + + javaTask = ( Java )getTask().getProject().createTask( "java" ); + javaTask.setTaskName( "verify" ); + javaTask.setClassname( VERIFY ); + Commandline.Argument arguments = javaTask.createArg(); + arguments.setLine( args ); + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + javaTask.setClasspath( classpath ); + javaTask.setFork( true ); + } + + log( "Calling " + VERIFY + " for " + sourceJar.toString(), Project.MSG_VERBOSE ); + javaTask.execute(); + } + catch( Exception e ) + { + //TO DO : delete the file if it is not a valid file. + String msg = "Exception while calling " + VERIFY + " Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java new file mode 100644 index 000000000..1dab7dd20 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + + +/** + * BorlandGenerateClient is dedicated to the Borland Application Server 4.5 This + * task generates the client jar using as input the ejb jar file. Two mode are + * available: java mode (default) and fork mode. With the fork mode, it is + * impossible to add classpath to the commmand line. + * + * @author Benoit Moussaud + */ +public class BorlandGenerateClient extends Task +{ + final static String JAVA_MODE = "java"; + final static String FORK_MODE = "fork"; + + /** + * debug the generateclient task + */ + boolean debug = false; + + /** + * hold the ejbjar file name + */ + File ejbjarfile = null; + + /** + * hold the client jar file name + */ + File clientjarfile = null; + + /** + * hold the mode (java|fork) + */ + String mode = JAVA_MODE; + + /** + * hold the classpath + */ + Path classpath; + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setClientjar( File clientjar ) + { + clientjarfile = clientjar; + } + + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + public void setEjbjar( File ejbfile ) + { + ejbjarfile = ejbfile; + } + + public void setMode( String s ) + { + mode = s; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a java task. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( ejbjarfile == null || + ejbjarfile.isDirectory() ) + { + throw new BuildException( "invalid ejb jar file." ); + }// end of if () + + if( clientjarfile == null || + clientjarfile.isDirectory() ) + { + log( "invalid or missing client jar file.", Project.MSG_VERBOSE ); + String ejbjarname = ejbjarfile.getAbsolutePath(); + //clientname = ejbjarfile+client.jar + String clientname = ejbjarname.substring( 0, ejbjarname.lastIndexOf( "." ) ); + clientname = clientname + "client.jar"; + clientjarfile = new File( clientname ); + + }// end of if () + + if( mode == null ) + { + log( "mode is null default mode is java" ); + setMode( JAVA_MODE ); + }// end of if () + + log( "client jar file is " + clientjarfile ); + + if( mode.equalsIgnoreCase( FORK_MODE ) ) + { + executeFork(); + }// end of if () + else + { + executeJava(); + }// end of else + } + + /** + * launch the generate client using system api + * + * @exception BuildException Description of Exception + */ + protected void executeFork() + throws BuildException + { + try + { + log( "mode : fork" ); + + org.apache.tools.ant.taskdefs.ExecTask execTask = null; + execTask = ( ExecTask )getProject().createTask( "exec" ); + + execTask.setDir( new File( "." ) ); + execTask.setExecutable( "iastool" ); + execTask.createArg().setValue( "generateclient" ); + if( debug ) + { + execTask.createArg().setValue( "-trace" ); + }// end of if () + + // + execTask.createArg().setValue( "-short" ); + execTask.createArg().setValue( "-jarfile" ); + // ejb jar file + execTask.createArg().setValue( ejbjarfile.getAbsolutePath() ); + //client jar file + execTask.createArg().setValue( "-single" ); + execTask.createArg().setValue( "-clientjarfile" ); + execTask.createArg().setValue( clientjarfile.getAbsolutePath() ); + + log( "Calling java2iiop", Project.MSG_VERBOSE ); + execTask.execute(); + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling generateclient Details: " + e.toString(); + throw new BuildException( msg, e ); + } + + } + + /** + * launch the generate client using java api + * + * @exception BuildException Description of Exception + */ + protected void executeJava() + throws BuildException + { + try + { + log( "mode : java" ); + + org.apache.tools.ant.taskdefs.Java execTask = null; + execTask = ( Java )getProject().createTask( "java" ); + + execTask.setDir( new File( "." ) ); + execTask.setClassname( "com.inprise.server.commandline.EJBUtilities" ); + //classpath + //add at the end of the classpath + //the system classpath in order to find the tools.jar file + execTask.setClasspath( classpath.concatSystemClasspath() ); + + execTask.setFork( true ); + execTask.createArg().setValue( "generateclient" ); + if( debug ) + { + execTask.createArg().setValue( "-trace" ); + }// end of if () + + // + execTask.createArg().setValue( "-short" ); + execTask.createArg().setValue( "-jarfile" ); + // ejb jar file + execTask.createArg().setValue( ejbjarfile.getAbsolutePath() ); + //client jar file + execTask.createArg().setValue( "-single" ); + execTask.createArg().setValue( "-clientjarfile" ); + execTask.createArg().setValue( clientjarfile.getAbsolutePath() ); + + log( "Calling EJBUtilities", Project.MSG_VERBOSE ); + execTask.execute(); + + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling generateclient Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java new file mode 100644 index 000000000..25218e180 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * Build a serialised deployment descriptor given a text file description of the + * descriptor in the format supported by WebLogic. This ant task is a front end + * for the weblogic DDCreator tool. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class DDCreator extends MatchingTask +{ + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes necessary fro DDCreator and the implementation + * classes of the home and remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the textual deployment + * desciptors. The actual deployment descriptor files are selected using + * include and exclude constructs on the EJBC task, as supported by the + * MatchingTask superclass. + */ + private File descriptorDirectory; + + /** + * The directory where generated serialised deployment descriptors are + * placed. + */ + private File generatedFilesDirectory; + + /** + * Set the classpath to be used for this compilation. + * + * @param s the classpath to use for the ddcreator tool. + */ + public void setClasspath( String s ) + { + this.classpath = project.translatePath( s ); + } + + /** + * Set the directory from where the text descriptions of the deployment + * descriptors are to be read. + * + * @param dirName the name of the directory containing the text deployment + * descriptor files. + */ + public void setDescriptors( String dirName ) + { + descriptorDirectory = new File( dirName ); + } + + /** + * Set the directory into which the serialised deployment descriptors are to + * be written. + * + * @param dirName the name of the directory into which the serialised + * deployment descriptors are written. + */ + public void setDest( String dirName ) + { + generatedFilesDirectory = new File( dirName ); + } + + /** + * Do the work. The work is actually done by creating a helper task. This + * approach allows the classpath of the helper task to be set. Since the + * weblogic tools require the class files of the project's home and remote + * interfaces to be available in the classpath, this also avoids having to + * start ant with the class path of the project it is building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( descriptorDirectory == null || + !descriptorDirectory.isDirectory() ) + { + throw new BuildException( "descriptors directory " + descriptorDirectory.getPath() + + " is not valid" ); + } + if( generatedFilesDirectory == null || + !generatedFilesDirectory.isDirectory() ) + { + throw new BuildException( "dest directory " + generatedFilesDirectory.getPath() + + " is not valid" ); + } + + String args = descriptorDirectory + " " + generatedFilesDirectory; + + // get all the files in the descriptor directory + DirectoryScanner ds = super.getDirectoryScanner( descriptorDirectory ); + + String[] files = ds.getIncludedFiles(); + + for( int i = 0; i < files.length; ++i ) + { + args += " " + files[i]; + } + + String systemClassPath = System.getProperty( "java.class.path" ); + String execClassPath = project.translatePath( systemClassPath + ":" + classpath ); + Java ddCreatorTask = ( Java )project.createTask( "java" ); + ddCreatorTask.setTaskName( getTaskName() ); + ddCreatorTask.setFork( true ); + ddCreatorTask.setClassname( "org.apache.tools.ant.taskdefs.optional.ejb.DDCreatorHelper" ); + Commandline.Argument arguments = ddCreatorTask.createArg(); + arguments.setLine( args ); + ddCreatorTask.setClasspath( new Path( project, execClassPath ) ); + if( ddCreatorTask.executeJava() != 0 ) + { + throw new BuildException( "Execution of ddcreator helper failed" ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java new file mode 100644 index 000000000..f59a3288c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import javax.ejb.deployment.DeploymentDescriptor; + +/** + * A helper class which performs the actual work of the ddcreator task. This + * class is run with a classpath which includes the weblogic tools and the home + * and remote interface class files referenced in the deployment descriptors + * being built. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class DDCreatorHelper +{ + + /** + * The descriptor text files for which a serialised descriptor is to be + * created. + */ + String[] descriptors; + /** + * The root directory of the tree containing the textual deployment + * desciptors. + */ + private File descriptorDirectory; + + /** + * The directory where generated serialised desployment descriptors are + * written. + */ + private File generatedFilesDirectory; + + /** + * Initialise the helper with the command arguments. + * + * @param args Description of Parameter + */ + private DDCreatorHelper( String[] args ) + { + int index = 0; + descriptorDirectory = new File( args[index++] ); + generatedFilesDirectory = new File( args[index++] ); + + descriptors = new String[args.length - index]; + for( int i = 0; index < args.length; ++i ) + { + descriptors[i] = args[index++]; + } + } + + /** + * The main method. The main method creates an instance of the + * DDCreatorHelper, passing it the args which it then processes. + * + * @param args The command line arguments + * @exception Exception Description of Exception + */ + public static void main( String[] args ) + throws Exception + { + DDCreatorHelper helper = new DDCreatorHelper( args ); + helper.process(); + } + + /** + * Do the actual work. The work proceeds by examining each descriptor given. + * If the serialised file does not exist or is older than the text + * description, the weblogic DDCreator tool is invoked directly to build the + * serialised descriptor. + * + * @exception Exception Description of Exception + */ + private void process() + throws Exception + { + for( int i = 0; i < descriptors.length; ++i ) + { + String descriptorName = descriptors[i]; + File descriptorFile = new File( descriptorDirectory, descriptorName ); + + int extIndex = descriptorName.lastIndexOf( "." ); + String serName = null; + if( extIndex != -1 ) + { + serName = descriptorName.substring( 0, extIndex ) + ".ser"; + } + else + { + serName = descriptorName + ".ser"; + } + File serFile = new File( generatedFilesDirectory, serName ); + + // do we need to regenerate the file + if( !serFile.exists() || serFile.lastModified() < descriptorFile.lastModified() + || regenerateSerializedFile( serFile ) ) + { + + String[] args = {"-noexit", + "-d", serFile.getParent(), + "-outputfile", serFile.getName(), + descriptorFile.getPath()}; + try + { + weblogic.ejb.utils.DDCreator.main( args ); + } + catch( Exception e ) + { + // there was an exception - run with no exit to get proper error + String[] newArgs = {"-d", generatedFilesDirectory.getPath(), + "-outputfile", serFile.getName(), + descriptorFile.getPath()}; + weblogic.ejb.utils.DDCreator.main( newArgs ); + } + } + } + } + + /** + * EJBC will fail if the serialized descriptor file does not match the bean + * classes. You can test for this by trying to load the deployment + * descriptor. If it fails, the serialized file needs to be regenerated + * because the associated class files don't match. + * + * @param serFile Description of Parameter + * @return Description of the Returned Value + */ + private boolean regenerateSerializedFile( File serFile ) + { + try + { + + FileInputStream fis = new FileInputStream( serFile ); + ObjectInputStream ois = new ObjectInputStream( fis ); + DeploymentDescriptor dd = ( DeploymentDescriptor )ois.readObject(); + fis.close(); + + // Since the descriptor read properly, everything should be o.k. + return false; + } + catch( Exception e ) + { + + // Weblogic will throw an error if the deployment descriptor does + // not match the class files. + return true; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java new file mode 100644 index 000000000..4f0a226cc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Hashtable; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.xml.sax.AttributeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Inner class used by EjbJar to facilitate the parsing of deployment + * descriptors and the capture of appropriate information. Extends HandlerBase + * so it only implements the methods needed. During parsing creates a hashtable + * consisting of entries mapping the name it should be inserted into an EJB jar + * as to a File representing the file on disk. This list can then be accessed + * through the getFiles() method. + * + * @author RT + */ +public class DescriptorHandler extends org.xml.sax.HandlerBase +{ + private final static int STATE_LOOKING_EJBJAR = 1; + private final static int STATE_IN_EJBJAR = 2; + private final static int STATE_IN_BEANS = 3; + private final static int STATE_IN_SESSION = 4; + private final static int STATE_IN_ENTITY = 5; + private final static int STATE_IN_MESSAGE = 6; + + /** + * Bunch of constants used for storing entries in a hashtable, and for + * constructing the filenames of various parts of the ejb jar. + */ + private final static String EJB_REF = "ejb-ref"; + private final static String HOME_INTERFACE = "home"; + private final static String REMOTE_INTERFACE = "remote"; + private final static String LOCAL_HOME_INTERFACE = "local-home"; + private final static String LOCAL_INTERFACE = "local"; + private final static String BEAN_CLASS = "ejb-class"; + private final static String PK_CLASS = "prim-key-class"; + private final static String EJB_NAME = "ejb-name"; + private final static String EJB_JAR = "ejb-jar"; + private final static String ENTERPRISE_BEANS = "enterprise-beans"; + private final static String ENTITY_BEAN = "entity"; + private final static String SESSION_BEAN = "session"; + private final static String MESSAGE_BEAN = "message-driven"; + + private String publicId = null; + + /** + * The state of the parsing + */ + private int parseState = STATE_LOOKING_EJBJAR; + + /** + * Instance variable used to store the name of the current element being + * processed by the SAX parser. Accessed by the SAX parser call-back methods + * startElement() and endElement(). + */ + protected String currentElement = null; + + /** + * The text of the current element + */ + protected String currentText = null; + + /** + * Instance variable that stores the names of the files as they will be put + * into the jar file, mapped to File objects Accessed by the SAX parser + * call-back method characters(). + */ + protected Hashtable ejbFiles = null; + + /** + * Instance variable that stores the value found in the <ejb-name> + * element + */ + protected String ejbName = null; + + private Hashtable fileDTDs = new Hashtable(); + + private Hashtable resourceDTDs = new Hashtable(); + + private boolean inEJBRef = false; + + private Hashtable urlDTDs = new Hashtable(); + + private Task owningTask; + + /** + * The directory containing the bean classes and interfaces. This is used + * for performing dependency file lookups. + */ + private File srcDir; + + public DescriptorHandler( Task task, File srcDir ) + { + this.owningTask = task; + this.srcDir = srcDir; + } + + /** + * Getter method that returns the value of the <ejb-name> element. + * + * @return The EjbName value + */ + public String getEjbName() + { + return ejbName; + } + + /** + * Getter method that returns the set of files to include in the EJB jar. + * + * @return The Files value + */ + public Hashtable getFiles() + { + return ( ejbFiles == null ) ? new Hashtable() : ejbFiles; + } + + /** + * Get the publicId of the DTD + * + * @return The PublicId value + */ + public String getPublicId() + { + return publicId; + } + + /** + * SAX parser call-back method invoked whenever characters are located + * within an element. currentAttribute (modified by startElement and + * endElement) tells us whether we are in an interesting element (one of the + * up to four classes of an EJB). If so then converts the classname from the + * format org.apache.tools.ant.Parser to the convention for storing such a + * class, org/apache/tools/ant/Parser.class. This is then resolved into a + * file object under the srcdir which is stored in a Hashtable. + * + * @param ch A character array containing all the characters in the element, + * and maybe others that should be ignored. + * @param start An integer marking the position in the char array to start + * reading from. + * @param length An integer representing an offset into the char array where + * the current data terminates. + * @exception SAXException Description of Exception + */ + public void characters( char[] ch, int start, int length ) + throws SAXException + { + + currentText += new String( ch, start, length ); + } + + + /** + * SAX parser call-back method that is invoked when an element is exited. + * Used to blank out (set to the empty string, not nullify) the name of the + * currentAttribute. A better method would be to use a stack as an instance + * variable, however since we are only interested in leaf-node data this is + * a simpler and workable solution. + * + * @param name The name of the attribute being exited. Ignored in this + * implementation. + * @exception SAXException Description of Exception + */ + public void endElement( String name ) + throws SAXException + { + processElement(); + currentText = ""; + this.currentElement = ""; + if( name.equals( EJB_REF ) ) + { + inEJBRef = false; + } + else if( parseState == STATE_IN_ENTITY && name.equals( ENTITY_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_SESSION && name.equals( SESSION_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_MESSAGE && name.equals( MESSAGE_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_BEANS && name.equals( ENTERPRISE_BEANS ) ) + { + parseState = STATE_IN_EJBJAR; + } + else if( parseState == STATE_IN_EJBJAR && name.equals( EJB_JAR ) ) + { + parseState = STATE_LOOKING_EJBJAR; + } + } + + public void registerDTD( String publicId, String location ) + { + if( location == null ) + { + return; + } + + File fileDTD = new File( location ); + if( fileDTD.exists() ) + { + if( publicId != null ) + { + fileDTDs.put( publicId, fileDTD ); + owningTask.log( "Mapped publicId " + publicId + " to file " + fileDTD, Project.MSG_VERBOSE ); + } + return; + } + + if( getClass().getResource( location ) != null ) + { + if( publicId != null ) + { + resourceDTDs.put( publicId, location ); + owningTask.log( "Mapped publicId " + publicId + " to resource " + location, Project.MSG_VERBOSE ); + } + } + + try + { + if( publicId != null ) + { + URL urldtd = new URL( location ); + urlDTDs.put( publicId, urldtd ); + } + } + catch( java.net.MalformedURLException e ) + { + //ignored + } + + } + + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + this.publicId = publicId; + + File dtdFile = ( File )fileDTDs.get( publicId ); + if( dtdFile != null ) + { + try + { + owningTask.log( "Resolved " + publicId + " to local file " + dtdFile, Project.MSG_VERBOSE ); + return new InputSource( new FileInputStream( dtdFile ) ); + } + catch( FileNotFoundException ex ) + { + // ignore + } + } + + String dtdResourceName = ( String )resourceDTDs.get( publicId ); + if( dtdResourceName != null ) + { + InputStream is = this.getClass().getResourceAsStream( dtdResourceName ); + if( is != null ) + { + owningTask.log( "Resolved " + publicId + " to local resource " + dtdResourceName, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + } + + URL dtdUrl = ( URL )urlDTDs.get( publicId ); + if( dtdUrl != null ) + { + try + { + InputStream is = dtdUrl.openStream(); + owningTask.log( "Resolved " + publicId + " to url " + dtdUrl, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + catch( IOException ioe ) + { + //ignore + } + } + + owningTask.log( "Could not resolve ( publicId: " + publicId + ", systemId: " + systemId + ") to a local entity", + Project.MSG_INFO ); + + return null; + } + + /** + * SAX parser call-back method that is used to initialize the values of some + * instance variables to ensure safe operation. + * + * @exception SAXException Description of Exception + */ + public void startDocument() + throws SAXException + { + this.ejbFiles = new Hashtable( 10, 1 ); + this.currentElement = null; + inEJBRef = false; + } + + + /** + * SAX parser call-back method that is invoked when a new element is entered + * into. Used to store the context (attribute name) in the currentAttribute + * instance variable. + * + * @param name The name of the element being entered. + * @param attrs Attributes associated to the element. + * @exception SAXException Description of Exception + */ + public void startElement( String name, AttributeList attrs ) + throws SAXException + { + this.currentElement = name; + currentText = ""; + if( name.equals( EJB_REF ) ) + { + inEJBRef = true; + } + else if( parseState == STATE_LOOKING_EJBJAR && name.equals( EJB_JAR ) ) + { + parseState = STATE_IN_EJBJAR; + } + else if( parseState == STATE_IN_EJBJAR && name.equals( ENTERPRISE_BEANS ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_BEANS && name.equals( SESSION_BEAN ) ) + { + parseState = STATE_IN_SESSION; + } + else if( parseState == STATE_IN_BEANS && name.equals( ENTITY_BEAN ) ) + { + parseState = STATE_IN_ENTITY; + } + else if( parseState == STATE_IN_BEANS && name.equals( MESSAGE_BEAN ) ) + { + parseState = STATE_IN_MESSAGE; + } + } + + + protected void processElement() + { + if( inEJBRef || + ( parseState != STATE_IN_ENTITY && parseState != STATE_IN_SESSION && parseState != STATE_IN_MESSAGE ) ) + { + return; + } + + if( currentElement.equals( HOME_INTERFACE ) || + currentElement.equals( REMOTE_INTERFACE ) || + currentElement.equals( LOCAL_INTERFACE ) || + currentElement.equals( LOCAL_HOME_INTERFACE ) || + currentElement.equals( BEAN_CLASS ) || + currentElement.equals( PK_CLASS ) ) + { + + // Get the filename into a String object + File classFile = null; + String className = currentText.trim(); + + // If it's a primitive wrapper then we shouldn't try and put + // it into the jar, so ignore it. + if( !className.startsWith( "java." ) && + !className.startsWith( "javax." ) ) + { + // Translate periods into path separators, add .class to the + // name, create the File object and add it to the Hashtable. + className = className.replace( '.', File.separatorChar ); + className += ".class"; + classFile = new File( srcDir, className ); + ejbFiles.put( className, classFile ); + } + } + + // Get the value of the tag. Only the first occurence. + if( currentElement.equals( EJB_NAME ) ) + { + if( ejbName == null ) + { + ejbName = currentText.trim(); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java new file mode 100644 index 000000000..147faed55 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import javax.xml.parsers.SAXParser; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +public interface EJBDeploymentTool +{ + /** + * Process a deployment descriptor, generating the necessary vendor specific + * deployment files. + * + * @param descriptorFilename the name of the deployment descriptor + * @param saxParser a SAX parser which can be used to parse the deployment + * descriptor. + * @exception BuildException Description of Exception + */ + void processDescriptor( String descriptorFilename, SAXParser saxParser ) + throws BuildException; + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + void validateConfigured() + throws BuildException; + + /** + * Set the task which owns this tool + * + * @param task The new Task value + */ + void setTask( Task task ); + + /** + * Configure this tool for use in the ejbjar task. + * + * @param config Description of Parameter + */ + void configure( EjbJar.Config config ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java new file mode 100644 index 000000000..df7aa90bb --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java @@ -0,0 +1,567 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb;// Standard java imports +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.xml.parsers.ParserConfigurationException;// XML imports +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory;// Apache/Ant imports +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.xml.sax.SAXException; + +/** + *

              + * + * Provides automated ejb jar file creation for ant. Extends the MatchingTask + * class provided in the default ant distribution to provide a directory + * scanning EJB jarfile generator.

              + * + * The task works by taking the deployment descriptors one at a time and parsing + * them to locate the names of the classes which should be placed in the jar. + * The classnames are translated to java.io.Files by replacing periods with + * File.separatorChar and resolving the generated filename as a relative path + * under the srcDir attribute. All necessary files are then assembled into a + * jarfile. One jarfile is constructed for each deployment descriptor found. + *

              + * + * Functionality is currently provided for standard EJB1.1 jars and Weblogic 5.1 + * jars. The weblogic deployment descriptors, used in constructing the Weblogic + * jar, are located based on a simple naming convention. The name of the + * standard deployment descriptor is taken upto the first instance of a String, + * specified by the attribute baseNameTerminator, and then the regular Weblogic + * descriptor name is appended. For example if baseNameTerminator is set to '-', + * its default value, and a standard descriptor is called Foo-ejb-jar.xml then + * the files Foo-weblogic-ejb-jar.xml and Foo-weblogic-cmp-rdbms-jar.xml will be + * looked for, and if found, included in the jarfile.

              + * + * Attributes and setter methods are provided to support optional generation of + * Weblogic5.1 jars, optional deletion of generic jar files, setting alternate + * values for baseNameTerminator, and setting the strings to append to the names + * of the generated jarfiles.

              + * + * @author Tim Fennell + */ +public class EjbJar extends MatchingTask +{ + + private Config config = new Config(); + + /** + * Instance variable that stores the suffix for the generated jarfile. + */ + private String genericJarSuffix = "-generic.jar"; + + /** + * The list of deployment tools we are going to run. + */ + private ArrayList deploymentTools = new ArrayList(); + + /** + * Stores a handle to the directory to put the Jar files in. This is only + * used by the generic deployment descriptor tool which is created if no + * other deployment descriptor tools are provided. Normally each deployment + * tool will specify the desitination dir itself. + */ + private File destDir; + + /** + * Set the base name of the EJB jar that is to be created if it is not to be + * determined from the name of the deployment descriptor files. + * + * @param inValue the basename that will be used when writing the jar file + * containing the EJB + */ + public void setBasejarname( String inValue ) + { + config.baseJarName = inValue; + if( config.namingScheme == null ) + { + config.namingScheme = new NamingScheme(); + config.namingScheme.setValue( NamingScheme.BASEJARNAME ); + } + else if( !config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) ) + { + throw new BuildException( "The basejarname attribute is not compatible with the " + + config.namingScheme.getValue() + " naming scheme" ); + } + } + + /** + * Set the baseNameTerminator. The basename terminator is the string which + * terminates the bean name. The convention used by this task is that bean + * descriptors are named as the BeanName with some suffix. The + * baseNameTerminator string separates the bean name and the suffix and is + * used to determine the bean name. + * + * @param inValue a string which marks the end of the basename. + */ + public void setBasenameterminator( String inValue ) + { + config.baseNameTerminator = inValue; + } + + /** + * Set the classpath to use when resolving classes for inclusion in the jar. + * + * @param classpath the classpath to use. + */ + public void setClasspath( Path classpath ) + { + config.classpath = classpath; + } + + /** + * Set the descriptor directory. The descriptor directory contains the EJB + * deployment descriptors. These are XML files that declare the properties + * of a bean in a particular deployment scenario. Such properties include, + * for example, the transactional nature of the bean and the security access + * control to the bean's methods. + * + * @param inDir the directory containing the deployment descriptors. + */ + public void setDescriptordir( File inDir ) + { + config.descriptorDir = inDir; + } + + + /** + * Set the destination directory. The EJB jar files will be written into + * this directory. The jar files that exist in this directory are also used + * when determining if the contents of the jar file have changed. Note that + * this parameter is only used if no deployment tools are specified. + * Typically each deployment tool will specify its own destination + * directory. + * + * @param inDir The new Destdir value + */ + public void setDestdir( File inDir ) + { + this.destDir = inDir; + } + + /** + * Set the flat dest dir flag. This flag controls whether the destination + * jars are written out in the destination directory with the same + * hierarchal structure from which the deployment descriptors have been + * read. If this is set to true the generated EJB jars are written into the + * root of the destination directory, otherwise they are written out in the + * same relative position as the deployment descriptors in the descriptor + * directory. + * + * @param inValue the new value of the flatdestdir flag. + */ + public void setFlatdestdir( boolean inValue ) + { + config.flatDestDir = inValue; + } + + /** + * Set the suffix for the generated jar file. When generic jars are + * generated, they have a suffix which is appended to the the bean name to + * create the name of the jar file. Note that this suffix includes the + * extension fo te jar file and should therefore end with an appropriate + * extension such as .jar or .ear + * + * @param inString the string to use as the suffix. + */ + public void setGenericjarsuffix( String inString ) + { + this.genericJarSuffix = inString; + } + + + /** + * Set the Manifest file to use when jarring. As of EJB 1.1, manifest files + * are no longer used to configure the EJB. However, they still have a vital + * importance if the EJB is intended to be packaged in an EAR file. By + * adding "Class-Path" settings to a Manifest file, the EJB can look for + * classes inside the EAR file itself, allowing for easier deployment. This + * is outlined in the J2EE specification, and all J2EE components are meant + * to support it. + * + * @param manifest The new Manifest value + */ + public void setManifest( File manifest ) + { + config.manifest = manifest; + } + + /** + * Set the naming scheme used to determine the name of the generated jars + * from the deployment descriptor + * + * @param namingScheme The new Naming value + */ + public void setNaming( NamingScheme namingScheme ) + { + config.namingScheme = namingScheme; + if( !config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) && + config.baseJarName != null ) + { + throw new BuildException( "The basejarname attribute is not compatible with the " + + config.namingScheme.getValue() + " naming scheme" ); + } + } + + /** + * Set the srcdir attribute. The source directory is the directory that + * contains the classes that will be added to the EJB jar. Typically this + * will include the home and remote interfaces and the bean class. + * + * @param inDir the source directory. + */ + public void setSrcdir( File inDir ) + { + config.srcDir = inDir; + } + + /** + * Create a Borland nested element used to configure a deployment tool for + * Borland server. + * + * @return the deployment tool instance to be configured. + */ + public BorlandDeploymentTool createBorland() + { + log( "Borland deployment tools", Project.MSG_VERBOSE ); + + BorlandDeploymentTool tool = new BorlandDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * creates a nested classpath element. This classpath is used to locate the + * super classes and interfaces of the classes that will make up the EJB + * jar. + * + * @return the path to be configured. + */ + public Path createClasspath() + { + if( config.classpath == null ) + { + config.classpath = new Path( project ); + } + return config.classpath.createPath(); + } + + /** + * Create a DTD location record. This stores the location of a DTD. The DTD + * is identified by its public Id. The location may either be a file + * location or a resource location. + * + * @return Description of the Returned Value + */ + public DTDLocation createDTD() + { + DTDLocation dtdLocation = new DTDLocation(); + config.dtdLocations.add( dtdLocation ); + + return dtdLocation; + } + + /** + * Create a nested element used to configure a deployment tool for iPlanet + * Application Server. + * + * @return the deployment tool instance to be configured. + */ + public IPlanetDeploymentTool createIplanet() + { + log( "iPlanet Application Server deployment tools", Project.MSG_VERBOSE ); + + IPlanetDeploymentTool tool = new IPlanetDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a jboss nested element used to configure a deployment tool for + * Jboss server. + * + * @return the deployment tool instance to be configured. + */ + public JbossDeploymentTool createJboss() + { + JbossDeploymentTool tool = new JbossDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a file set for support elements + * + * @return a fileset which can be populated with support files. + */ + public FileSet createSupport() + { + FileSet supportFileSet = new FileSet(); + config.supportFileSets.add( supportFileSet ); + return supportFileSet; + } + + /** + * Create a weblogic nested element used to configure a deployment tool for + * Weblogic server. + * + * @return the deployment tool instance to be configured. + */ + public WeblogicDeploymentTool createWeblogic() + { + WeblogicDeploymentTool tool = new WeblogicDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a nested element for weblogic when using the Toplink Object- + * Relational mapping. + * + * @return the deployment tool instance to be configured. + */ + public WeblogicTOPLinkDeploymentTool createWeblogictoplink() + { + WeblogicTOPLinkDeploymentTool tool = new WeblogicTOPLinkDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a websphere nested element used to configure a deployment tool for + * Websphere 4.0 server. + * + * @return the deployment tool instance to be configured. + */ + public WebsphereDeploymentTool createWebsphere() + { + WebsphereDeploymentTool tool = new WebsphereDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Invoked by Ant after the task is prepared, when it is ready to execute + * this task. This will configure all of the nested deployment tools to + * allow them to process the jar. If no deployment tools have been + * configured a generic tool is created to handle the jar. A parser is + * configured and then each descriptor found is passed to all the deployment + * tool elements for processing. + * + * @exception BuildException thrown whenever a problem is encountered that + * cannot be recovered from, to signal to ant that a major problem + * occurred within this task. + */ + public void execute() + throws BuildException + { + validateConfig(); + + if( deploymentTools.size() == 0 ) + { + GenericDeploymentTool genericTool = new GenericDeploymentTool(); + genericTool.setTask( this ); + genericTool.setDestdir( destDir ); + genericTool.setGenericJarSuffix( genericJarSuffix ); + deploymentTools.add( genericTool ); + } + + for( Iterator i = deploymentTools.iterator(); i.hasNext(); ) + { + EJBDeploymentTool tool = ( EJBDeploymentTool )i.next(); + tool.configure( config ); + tool.validateConfigured(); + } + + try + { + // Create the parser using whatever parser the system dictates + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + SAXParser saxParser = saxParserFactory.newSAXParser(); + + DirectoryScanner ds = getDirectoryScanner( config.descriptorDir ); + ds.scan(); + String[] files = ds.getIncludedFiles(); + + log( files.length + " deployment descriptors located.", + Project.MSG_VERBOSE ); + + // Loop through the files. Each file represents one deployment + // descriptor, and hence one bean in our model. + for( int index = 0; index < files.length; ++index ) + { + // process the deployment descriptor in each tool + for( Iterator i = deploymentTools.iterator(); i.hasNext(); ) + { + EJBDeploymentTool tool = ( EJBDeploymentTool )i.next(); + tool.processDescriptor( files[index], saxParser ); + } + } + } + catch( SAXException se ) + { + String msg = "SAXException while creating parser." + + " Details: " + + se.getMessage(); + throw new BuildException( msg, se ); + } + catch( ParserConfigurationException pce ) + { + String msg = "ParserConfigurationException while creating parser. " + + "Details: " + pce.getMessage(); + throw new BuildException( msg, pce ); + } + } + + private void validateConfig() + { + if( config.srcDir == null ) + { + throw new BuildException( "The srcDir attribute must be specified" ); + } + + if( config.descriptorDir == null ) + { + config.descriptorDir = config.srcDir; + } + + if( config.namingScheme == null ) + { + config.namingScheme = new NamingScheme(); + config.namingScheme.setValue( NamingScheme.DESCRIPTOR ); + } + else if( config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) && + config.baseJarName == null ) + { + throw new BuildException( "The basejarname attribute must be specified " + + "with the basejarname naming scheme" ); + } + } + + public static class DTDLocation + { + private String publicId = null; + private String location = null; + + public void setLocation( String location ) + { + this.location = location; + } + + public void setPublicId( String publicId ) + { + this.publicId = publicId; + } + + public String getLocation() + { + return location; + } + + public String getPublicId() + { + return publicId; + } + } + + public static class NamingScheme extends EnumeratedAttribute + { + public final static String EJB_NAME = "ejb-name"; + public final static String DIRECTORY = "directory"; + public final static String DESCRIPTOR = "descriptor"; + public final static String BASEJARNAME = "basejarname"; + + public String[] getValues() + { + return new String[]{EJB_NAME, DIRECTORY, DESCRIPTOR, BASEJARNAME}; + } + } + + /** + * A class which contains the configuration state of the ejbjar task. This + * state is passed to the deployment tools for configuration + * + * @author RT + */ + static class Config + { + + /** + * Instance variable that marks the end of the 'basename' + */ + public String baseNameTerminator = "-"; + + /** + * Instance variable that determines whether to use a package structure + * of a flat directory as the destination for the jar files. + */ + public boolean flatDestDir = false; + + /** + * A Fileset of support classes + */ + public List supportFileSets = new ArrayList(); + + /** + * The list of configured DTD locations + */ + public ArrayList dtdLocations = new ArrayList(); + + /** + * Stores a handle to the destination EJB Jar file + */ + public String baseJarName; + + /** + * The classpath to use when loading classes + */ + public Path classpath; + + /** + * Stores a handle to the directory under which to search for deployment + * descriptors + */ + public File descriptorDir; + + /** + * The Manifest file + */ + public File manifest; + + /** + * The naming scheme used to determine the generated jar name from the + * descriptor information + */ + public NamingScheme namingScheme; + /** + * Stores a handle to the directory under which to search for class + * files + */ + public File srcDir; + }// end of execute() +} + + + + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java new file mode 100644 index 000000000..0041c3756 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * Build EJB support classes using Weblogic's ejbc tool from a directory + * containing a set of deployment descriptors. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class Ejbc extends MatchingTask +{ + + public boolean keepgenerated; + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the serialised deployment + * desciptors. The actual deployment descriptor files are selected using + * include and exclude constructs on the ejbc task provided by the + * MatchingTask superclass. + */ + private File descriptorDirectory; + + /** + * The directory where generated files are placed. + */ + private File generatedFilesDirectory; + + /** + * The name of the manifest file generated for the EJB jar. + */ + private File generatedManifestFile; + + /** + * The source directory for the home and remote interfaces. This is used to + * determine if the generated deployment classes are out of date. + */ + private File sourceDirectory; + + /** + * Set the classpath to be used for this compilation. + * + * @param s The new Classpath value + */ + public void setClasspath( String s ) + { + this.classpath = project.translatePath( s ); + } + + /** + * Set the directory from where the serialised deployment descriptors are to + * be read. + * + * @param dirName the name of the directory containing the serialised + * deployment descriptors. + */ + public void setDescriptors( String dirName ) + { + descriptorDirectory = new File( dirName ); + } + + /** + * Set the directory into which the support classes, RMI stubs, etc are to + * be written + * + * @param dirName the name of the directory into which code is generated + */ + public void setDest( String dirName ) + { + generatedFilesDirectory = new File( dirName ); + } + + public void setKeepgenerated( String newKeepgenerated ) + { + keepgenerated = Boolean.valueOf( newKeepgenerated.trim() ).booleanValue(); + + } + + /** + * Set the generated manifest file. For each EJB that is processed an entry + * is created in this file. This can then be used to create a jar file for + * dploying the beans. + * + * @param manifestFilename The new Manifest value + */ + public void setManifest( String manifestFilename ) + { + generatedManifestFile = new File( manifestFilename ); + } + + /** + * Set the directory containing the source code for the home interface, + * remote interface and public key class definitions. + * + * @param dirName the directory containg the source tree for the EJB's + * interface classes. + */ + public void setSrc( String dirName ) + { + sourceDirectory = new File( dirName ); + } + + public boolean getKeepgenerated() + { + return keepgenerated; + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a helper task. This approach allows the classpath of the helper task to + * be set. Since the weblogic tools require the class files of the project's + * home and remote interfaces to be available in the classpath, this also + * avoids having to start ant with the class path of the project it is + * building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( descriptorDirectory == null || + !descriptorDirectory.isDirectory() ) + { + throw new BuildException( "descriptors directory " + descriptorDirectory.getPath() + + " is not valid" ); + } + if( generatedFilesDirectory == null || + !generatedFilesDirectory.isDirectory() ) + { + throw new BuildException( "dest directory " + generatedFilesDirectory.getPath() + + " is not valid" ); + } + + if( sourceDirectory == null || + !sourceDirectory.isDirectory() ) + { + throw new BuildException( "src directory " + sourceDirectory.getPath() + + " is not valid" ); + } + + String systemClassPath = System.getProperty( "java.class.path" ); + String execClassPath = project.translatePath( systemClassPath + ":" + classpath + + ":" + generatedFilesDirectory ); + // get all the files in the descriptor directory + DirectoryScanner ds = super.getDirectoryScanner( descriptorDirectory ); + + String[] files = ds.getIncludedFiles(); + + Java helperTask = ( Java )project.createTask( "java" ); + helperTask.setTaskName( getTaskName() ); + helperTask.setFork( true ); + helperTask.setClassname( "org.apache.tools.ant.taskdefs.optional.ejb.EjbcHelper" ); + String args = ""; + args += " " + descriptorDirectory; + args += " " + generatedFilesDirectory; + args += " " + sourceDirectory; + args += " " + generatedManifestFile; + args += " " + keepgenerated; + + for( int i = 0; i < files.length; ++i ) + { + args += " " + files[i]; + } + + Commandline.Argument arguments = helperTask.createArg(); + arguments.setLine( args ); + helperTask.setClasspath( new Path( project, execClassPath ) ); + if( helperTask.executeJava() != 0 ) + { + throw new BuildException( "Execution of ejbc helper failed" ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java new file mode 100644 index 000000000..73b2ed414 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.PrintWriter; +import java.util.Vector; +import javax.ejb.deployment.DeploymentDescriptor; +import javax.ejb.deployment.EntityDescriptor; + + +/** + * A helper class which performs the actual work of the ejbc task. This class is + * run with a classpath which includes the weblogic tools and the home and + * remote interface class files referenced in the deployment descriptors being + * processed. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class EjbcHelper +{ + + /** + * The names of the serialised deployment descriptors + */ + String[] descriptors; + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the serialised deployment + * desciptors. + */ + private File descriptorDirectory; + + /** + * The directory where generated files are placed. + */ + private File generatedFilesDirectory; + + private boolean keepGenerated; + + /** + * The name of the manifest file generated for the EJB jar. + */ + private File manifestFile; + + /** + * The source directory for the home and remote interfaces. This is used to + * determine if the generated deployment classes are out of date. + */ + private File sourceDirectory; + + /** + * Initialise the EjbcHelper by reading the command arguments. + * + * @param args Description of Parameter + */ + private EjbcHelper( String[] args ) + { + int index = 0; + descriptorDirectory = new File( args[index++] ); + generatedFilesDirectory = new File( args[index++] ); + sourceDirectory = new File( args[index++] ); + manifestFile = new File( args[index++] ); + keepGenerated = Boolean.valueOf( args[index++] ).booleanValue(); + + descriptors = new String[args.length - index]; + for( int i = 0; index < args.length; ++i ) + { + descriptors[i] = args[index++]; + } + } + + /** + * Command line interface for the ejbc helper task. + * + * @param args The command line arguments + * @exception Exception Description of Exception + */ + public static void main( String[] args ) + throws Exception + { + EjbcHelper helper = new EjbcHelper( args ); + helper.process(); + } + + private String[] getCommandLine( boolean debug, File descriptorFile ) + { + Vector v = new Vector(); + if( !debug ) + { + v.addElement( "-noexit" ); + } + if( keepGenerated ) + { + v.addElement( "-keepgenerated" ); + } + v.addElement( "-d" ); + v.addElement( generatedFilesDirectory.getPath() ); + v.addElement( descriptorFile.getPath() ); + + String[] args = new String[v.size()]; + v.copyInto( args ); + return args; + } + + /** + * Determine if the weblogic EJB support classes need to be regenerated for + * a given deployment descriptor. This process attempts to determine if the + * support classes need to be rebuilt. It does this by examining only some + * of the support classes which are typically generated. If the ejbc task is + * interrupted generating the support classes for a bean, all of the support + * classes should be removed to force regeneration of the support classes. + * + * @param descriptorFile the serialised deployment descriptor + * @return true if the support classes need to be regenerated. + * @throws IOException if the descriptor file cannot be closed. + */ + private boolean isRegenRequired( File descriptorFile ) + throws IOException + { + // read in the descriptor. Under weblogic, the descriptor is a weblogic + // specific subclass which has references to the implementation classes. + // These classes must, therefore, be in the classpath when the deployment + // descriptor is loaded from the .ser file + FileInputStream fis = null; + try + { + fis = new FileInputStream( descriptorFile ); + ObjectInputStream ois = new ObjectInputStream( fis ); + DeploymentDescriptor dd = ( DeploymentDescriptor )ois.readObject(); + fis.close(); + + String homeInterfacePath = dd.getHomeInterfaceClassName().replace( '.', '/' ) + ".java"; + String remoteInterfacePath = dd.getRemoteInterfaceClassName().replace( '.', '/' ) + ".java"; + String primaryKeyClassPath = null; + if( dd instanceof EntityDescriptor ) + { + primaryKeyClassPath = ( ( EntityDescriptor )dd ).getPrimaryKeyClassName().replace( '.', '/' ) + ".java"; + ; + } + + File homeInterfaceSource = new File( sourceDirectory, homeInterfacePath ); + File remoteInterfaceSource = new File( sourceDirectory, remoteInterfacePath ); + File primaryKeyClassSource = null; + if( primaryKeyClassPath != null ) + { + primaryKeyClassSource = new File( sourceDirectory, remoteInterfacePath ); + } + + // are any of the above out of date. + // we find the implementation classes and see if they are older than any + // of the above or the .ser file itself. + String beanClassBase = dd.getEnterpriseBeanClassName().replace( '.', '/' ); + File ejbImplentationClass + = new File( generatedFilesDirectory, beanClassBase + "EOImpl.class" ); + File homeImplementationClass + = new File( generatedFilesDirectory, beanClassBase + "HomeImpl.class" ); + File beanStubClass + = new File( generatedFilesDirectory, beanClassBase + "EOImpl_WLStub.class" ); + + // if the implementation classes don;t exist regenerate + if( !ejbImplentationClass.exists() || !homeImplementationClass.exists() || + !beanStubClass.exists() ) + { + return true; + } + + // Is the ser file or any of the source files newer then the class files. + // firstly find the oldest of the two class files. + long classModificationTime = ejbImplentationClass.lastModified(); + if( homeImplementationClass.lastModified() < classModificationTime ) + { + classModificationTime = homeImplementationClass.lastModified(); + } + if( beanStubClass.lastModified() < classModificationTime ) + { + classModificationTime = beanStubClass.lastModified(); + } + + if( descriptorFile.lastModified() > classModificationTime || + homeInterfaceSource.lastModified() > classModificationTime || + remoteInterfaceSource.lastModified() > classModificationTime ) + { + return true; + } + + if( primaryKeyClassSource != null && + primaryKeyClassSource.lastModified() > classModificationTime ) + { + return true; + } + } + catch( Throwable descriptorLoadException ) + { + System.out.println( "Exception occurred reading " + descriptorFile.getName() + " - continuing" ); + // any problems - just regenerate + return true; + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + + return false; + } + + /** + * Process the descriptors in turn generating support classes for each and a + * manifest file for all of the beans. + * + * @exception Exception Description of Exception + */ + private void process() + throws Exception + { + String manifest = "Manifest-Version: 1.0\n\n"; + for( int i = 0; i < descriptors.length; ++i ) + { + String descriptorName = descriptors[i]; + File descriptorFile = new File( descriptorDirectory, descriptorName ); + + if( isRegenRequired( descriptorFile ) ) + { + System.out.println( "Running ejbc for " + descriptorFile.getName() ); + regenerateSupportClasses( descriptorFile ); + } + else + { + System.out.println( descriptorFile.getName() + " is up to date" ); + } + manifest += "Name: " + descriptorName.replace( '\\', '/' ) + "\nEnterprise-Bean: True\n\n"; + } + + FileWriter fw = new FileWriter( manifestFile ); + PrintWriter pw = new PrintWriter( fw ); + pw.print( manifest ); + fw.flush(); + fw.close(); + } + + /** + * Perform the weblogic.ejbc call to regenerate the support classes. Note + * that this method relies on an undocumented -noexit option to the ejbc + * tool to stop the ejbc tool exiting the VM altogether. + * + * @param descriptorFile Description of Parameter + * @exception Exception Description of Exception + */ + private void regenerateSupportClasses( File descriptorFile ) + throws Exception + { + // create a Java task to do the rebuild + + + String[] args = getCommandLine( false, descriptorFile ); + + try + { + weblogic.ejbc.main( args ); + } + catch( Exception e ) + { + // run with no exit for better reporting + String[] newArgs = getCommandLine( true, descriptorFile ); + weblogic.ejbc.main( newArgs ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java new file mode 100644 index 000000000..29a49f9aa --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java @@ -0,0 +1,970 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import javax.xml.parsers.SAXParser; +import org.apache.bcel.*; +import org.apache.bcel.classfile.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.*; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.depend.Dependencies; +import org.apache.tools.ant.util.depend.Filter; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + + +/** + * A deployment tool which creates generic EJB jars. Generic jars contains only + * those classes and META-INF entries specified in the EJB 1.1 standard This + * class is also used as a framework for the creation of vendor specific + * deployment tools. A number of template methods are provided through which the + * vendor specific tool can hook into the EJB creation process. + * + * @author RT + */ +public class GenericDeploymentTool implements EJBDeploymentTool +{ + /** + * Private constants that are used when constructing the standard jarfile + */ + protected final static String META_DIR = "META-INF/"; + protected final static String EJB_DD = "ejb-jar.xml"; + + /** + * Instance variable that stores the suffix for the generated jarfile. + */ + private String genericJarSuffix = "-generic.jar"; + + /** + * The classloader generated from the given classpath to load the super + * classes and super interfaces. + */ + private ClassLoader classpathLoader = null; + + /** + * List of files have been loaded into the EJB jar + */ + private List addedfiles; + + /** + * The classpath to use with this deployment tool. This is appended to any + * paths from the ejbjar task itself. + */ + private Path classpath; + + /** + * The configuration from the containing task. This config combined with the + * settings of the individual attributes here constitues the complete config + * for this deployment tool. + */ + private EjbJar.Config config; + + /** + * Stores a handle to the directory to put the Jar files in + */ + private File destDir; + + /** + * Handler used to parse the EJB XML descriptor + */ + private DescriptorHandler handler; + + /** + * The task to which this tool belongs. This is used to access services + * provided by the ant core, such as logging. + */ + private Task task; + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + this.classpath = classpath; + } + + /** + * Setter used to store the value of destination directory prior to + * execute() being called. + * + * @param inDir the destination directory. + */ + public void setDestdir( File inDir ) + { + this.destDir = inDir; + } + + /** + * Setter used to store the suffix for the generated jar file. + * + * @param inString the string to use as the suffix. + */ + public void setGenericJarSuffix( String inString ) + { + this.genericJarSuffix = inString; + } + + + /** + * Set the task which owns this tool + * + * @param task The new Task value + */ + public void setTask( Task task ) + { + this.task = task; + } + + /** + * Get the prefix for vendor deployment descriptors. This will contain the + * path and the start of the descriptor name, depending on the naming scheme + * + * @param baseName Description of Parameter + * @param descriptorFileName Description of Parameter + * @return The VendorDDPrefix value + */ + public String getVendorDDPrefix( String baseName, String descriptorFileName ) + { + String ddPrefix = null; + + if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DESCRIPTOR ) ) + { + ddPrefix = baseName + config.baseNameTerminator; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.BASEJARNAME ) || + config.namingScheme.getValue().equals( EjbJar.NamingScheme.EJB_NAME ) || + config.namingScheme.getValue().equals( EjbJar.NamingScheme.DIRECTORY ) ) + { + String canonicalDescriptor = descriptorFileName.replace( '\\', '/' ); + int index = canonicalDescriptor.lastIndexOf( '/' ); + if( index == -1 ) + { + ddPrefix = ""; + } + else + { + ddPrefix = descriptorFileName.substring( 0, index + 1 ); + } + } + return ddPrefix; + } + + + /** + * Configure this tool for use in the ejbjar task. + * + * @param config Description of Parameter + */ + public void configure( EjbJar.Config config ) + { + this.config = config; + + classpathLoader = null; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( task.getProject() ); + } + return classpath.createPath(); + } + + public void processDescriptor( String descriptorFileName, SAXParser saxParser ) + { + + checkConfiguration( descriptorFileName, saxParser ); + + try + { + handler = getDescriptorHandler( config.srcDir ); + + // Retrive the files to be added to JAR from EJB descriptor + Hashtable ejbFiles = parseEjbFiles( descriptorFileName, saxParser ); + + // Add any support classes specified in the build file + addSupportClasses( ejbFiles ); + + // Determine the JAR filename (without filename extension) + String baseName = getJarBaseName( descriptorFileName ); + + String ddPrefix = getVendorDDPrefix( baseName, descriptorFileName ); + + // First the regular deployment descriptor + ejbFiles.put( META_DIR + EJB_DD, + new File( config.descriptorDir, descriptorFileName ) ); + + // now the vendor specific files, if any + addVendorFiles( ejbFiles, ddPrefix ); + + // add any dependent files + checkAndAddDependants( ejbFiles ); + + // Lastly create File object for the Jar files. If we are using + // a flat destination dir, then we need to redefine baseName! + if( config.flatDestDir && baseName.length() != 0 ) + { + int startName = baseName.lastIndexOf( File.separator ); + if( startName == -1 ) + { + startName = 0; + } + + int endName = baseName.length(); + baseName = baseName.substring( startName, endName ); + } + + File jarFile = getVendorOutputJarFile( baseName ); + + // Check to see if we need a build and start doing the work! + if( needToRebuild( ejbFiles, jarFile ) ) + { + // Log that we are going to build... + log( "building " + + jarFile.getName() + + " with " + + String.valueOf( ejbFiles.size() ) + + " files", + Project.MSG_INFO ); + + // Use helper method to write the jarfile + String publicId = getPublicId(); + writeJar( baseName, jarFile, ejbFiles, publicId ); + + } + else + { + // Log that the file is up to date... + log( jarFile.toString() + " is up to date.", + Project.MSG_VERBOSE ); + } + + } + catch( SAXException se ) + { + String msg = "SAXException while parsing '" + + descriptorFileName.toString() + + "'. This probably indicates badly-formed XML." + + " Details: " + + se.getMessage(); + throw new BuildException( msg, se ); + } + catch( IOException ioe ) + { + String msg = "IOException while parsing'" + + descriptorFileName.toString() + + "'. This probably indicates that the descriptor" + + " doesn't exist. Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @throws BuildException If the Deployment Tool's configuration isn't valid + */ + public void validateConfigured() + throws BuildException + { + if( ( destDir == null ) || ( !destDir.isDirectory() ) ) + { + String msg = "A valid destination directory must be specified " + + "using the \"destdir\" attribute."; + throw new BuildException( msg, getLocation() ); + } + } + + + /** + * Returns a Classloader object which parses the passed in generic EjbJar + * classpath. The loader is used to dynamically load classes from + * javax.ejb.* and the classes being added to the jar. + * + * @return The ClassLoaderForBuild value + */ + protected ClassLoader getClassLoaderForBuild() + { + if( classpathLoader != null ) + { + return classpathLoader; + } + + Path combinedClasspath = getCombinedClasspath(); + + // only generate a new ClassLoader if we have a classpath + if( combinedClasspath == null ) + { + classpathLoader = getClass().getClassLoader(); + } + else + { + classpathLoader = new AntClassLoader( getTask().getProject(), combinedClasspath ); + } + + return classpathLoader; + } + + /** + * Get the classpath by combining the one from the surrounding task, if any + * and the one from this tool. + * + * @return The CombinedClasspath value + */ + protected Path getCombinedClasspath() + { + Path combinedPath = classpath; + if( config.classpath != null ) + { + if( combinedPath == null ) + { + combinedPath = config.classpath; + } + else + { + combinedPath.append( config.classpath ); + } + } + + return combinedPath; + } + + /** + * Get the basename terminator. + * + * @return The Config value + */ + protected EjbJar.Config getConfig() + { + return config; + } + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + DescriptorHandler handler = new DescriptorHandler( getTask(), srcDir ); + + registerKnownDTDs( handler ); + + // register any DTDs supplied by the user + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + /** + * Get the desitination directory. + * + * @return The DestDir value + */ + protected File getDestDir() + { + return destDir; + } + + + /** + * Using the EJB descriptor file name passed from the ejbjar + * task, this method returns the "basename" which will be used to name the + * completed JAR file. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @return The "basename" which will be used to name the completed JAR file + */ + protected String getJarBaseName( String descriptorFileName ) + { + + String baseName = ""; + + // Work out what the base name is + if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.BASEJARNAME ) ) + { + String canonicalDescriptor = descriptorFileName.replace( '\\', '/' ); + int index = canonicalDescriptor.lastIndexOf( '/' ); + if( index != -1 ) + { + baseName = descriptorFileName.substring( 0, index + 1 ); + } + baseName += config.baseJarName; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DESCRIPTOR ) ) + { + int lastSeparatorIndex = descriptorFileName.lastIndexOf( File.separator ); + int endBaseName = -1; + if( lastSeparatorIndex != -1 ) + { + endBaseName = descriptorFileName.indexOf( config.baseNameTerminator, + lastSeparatorIndex ); + } + else + { + endBaseName = descriptorFileName.indexOf( config.baseNameTerminator ); + } + + if( endBaseName != -1 ) + { + baseName = descriptorFileName.substring( 0, endBaseName ); + } + baseName = descriptorFileName.substring( 0, endBaseName ); + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DIRECTORY ) ) + { + int lastSeparatorIndex = descriptorFileName.lastIndexOf( File.separator ); + String dirName = descriptorFileName.substring( 0, lastSeparatorIndex ); + int dirSeparatorIndex = dirName.lastIndexOf( File.separator ); + if( dirSeparatorIndex != -1 ) + { + dirName = dirName.substring( dirSeparatorIndex + 1 ); + } + + baseName = dirName; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.EJB_NAME ) ) + { + baseName = handler.getEjbName(); + } + return baseName; + } + + protected Location getLocation() + { + return getTask().getLocation(); + } + + /** + * Returns the Public ID of the DTD specified in the EJB descriptor. Not + * every vendor-specific DeploymentTool will need to reference + * this value or may want to determine this value in a vendor-specific way. + * + * @return Public ID of the DTD specified in the EJB descriptor. + */ + protected String getPublicId() + { + return handler.getPublicId(); + } + + /** + * Get the task for this tool. + * + * @return The Task value + */ + protected Task getTask() + { + return task; + } + + /** + * Utility method that encapsulates the logic of adding a file entry to a + * .jar file. Used by execute() to add entries to the jar file as it is + * constructed. + * + * @param jStream A JarOutputStream into which to write the jar entry. + * @param inputFile A File from which to read the contents the file being + * added. + * @param logicalFilename A String representing the name, including all + * relevant path information, that should be stored for the entry being + * added. + * @exception BuildException Description of Exception + */ + protected void addFileToJar( JarOutputStream jStream, + File inputFile, + String logicalFilename ) + throws BuildException + { + FileInputStream iStream = null; + try + { + if( !addedfiles.contains( logicalFilename ) ) + { + iStream = new FileInputStream( inputFile ); + // Create the zip entry and add it to the jar file + ZipEntry zipEntry = new ZipEntry( logicalFilename.replace( '\\', '/' ) ); + jStream.putNextEntry( zipEntry ); + + // Create the file input stream, and buffer everything over + // to the jar output stream + byte[] byteBuffer = new byte[2 * 1024]; + int count = 0; + do + { + jStream.write( byteBuffer, 0, count ); + count = iStream.read( byteBuffer, 0, byteBuffer.length ); + }while ( count != -1 ); + + //add it to list of files in jar + addedfiles.add( logicalFilename ); + } + } + catch( IOException ioe ) + { + log( "WARNING: IOException while adding entry " + + logicalFilename + " to jarfile from " + inputFile.getPath() + " " + + ioe.getClass().getName() + "-" + ioe.getMessage(), Project.MSG_WARN ); + } + finally + { + // Close up the file input stream for the class file + if( iStream != null ) + { + try + { + iStream.close(); + } + catch( IOException closeException ) + {} + } + } + } + + /** + * Adds any classes the user specifies using support nested elements + * to the ejbFiles Hashtable. + * + * @param ejbFiles Hashtable of EJB classes (and other) files that will be + * added to the completed JAR file + */ + protected void addSupportClasses( Hashtable ejbFiles ) + { + // add in support classes if any + Project project = task.getProject(); + for( Iterator i = config.supportFileSets.iterator(); i.hasNext(); ) + { + FileSet supportFileSet = ( FileSet )i.next(); + File supportBaseDir = supportFileSet.getDir( project ); + DirectoryScanner supportScanner = supportFileSet.getDirectoryScanner( project ); + supportScanner.scan(); + String[] supportFiles = supportScanner.getIncludedFiles(); + for( int j = 0; j < supportFiles.length; ++j ) + { + ejbFiles.put( supportFiles[j], new File( supportBaseDir, supportFiles[j] ) ); + } + } + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + // nothing to add for generic tool. + }// end of writeJar + + + /** + * Add all available classes, that depend on Remote, Home, Bean, PK + * + * @param checkEntries files, that are extracted from the deployment + * descriptor + * @exception BuildException Description of Exception + */ + protected void checkAndAddDependants( Hashtable checkEntries ) + throws BuildException + { + Dependencies visitor = new Dependencies(); + Set set = new TreeSet(); + Set newSet = new HashSet(); + final String base = config.srcDir.getAbsolutePath() + File.separator; + + Iterator i = checkEntries.keySet().iterator(); + while( i.hasNext() ) + { + String entryName = ( String )i.next(); + if( entryName.endsWith( ".class" ) ) + newSet.add( entryName.substring( 0, entryName.length() - ".class".length() ).replace( File.separatorChar, '/' ) ); + } + set.addAll( newSet ); + + do + { + i = newSet.iterator(); + while( i.hasNext() ) + { + String fileName = base + ( ( String )i.next() ).replace( '/', File.separatorChar ) + ".class"; + + try + { + JavaClass javaClass = new ClassParser( fileName ).parse(); + javaClass.accept( visitor ); + } + catch( IOException e ) + { + log( "exception: " + e.getMessage(), Project.MSG_INFO ); + } + } + newSet.clear(); + newSet.addAll( visitor.getDependencies() ); + visitor.clearDependencies(); + + Dependencies.applyFilter( newSet, + new Filter() + { + public boolean accept( Object object ) + { + String fileName = base + ( ( String )object ).replace( '/', File.separatorChar ) + ".class"; + return new File( fileName ).exists(); + } + } ); + newSet.removeAll( set ); + set.addAll( newSet ); + }while ( newSet.size() > 0 ); + + i = set.iterator(); + while( i.hasNext() ) + { + String next = ( ( String )i.next() ).replace( '/', File.separatorChar ); + checkEntries.put( next + ".class", new File( base + next + ".class" ) ); + log( "dependent class: " + next + ".class" + " - " + base + next + ".class", Project.MSG_VERBOSE ); + } + } + + /** + * This method is called as the first step in the processDescriptor method + * to allow vendor-specific subclasses to validate the task configuration + * prior to processing the descriptor. If the configuration is invalid, a + * BuildException should be thrown. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @exception BuildException Description of Exception + * @thows BuildException Thrown if the configuration is invalid + */ + protected void checkConfiguration( String descriptorFileName, + SAXParser saxParser ) + throws BuildException + { + + /* + * For the GenericDeploymentTool, do nothing. Vendor specific + * subclasses should throw a BuildException if the configuration is + * invalid for their server. + */ + } + + protected void log( String message, int level ) + { + getTask().log( message, level ); + } + + /** + * This method checks the timestamp on each file listed in the + * ejbFiles and compares them to the timestamp on the jarFile + * . If the jarFile's timestamp is more recent than each + * EJB file, true is returned. Otherwise, false + * is returned. TODO: find a way to check the manifest-file, that is + * found by naming convention + * + * @param ejbFiles Hashtable of EJB classes (and other) files that will be + * added to the completed JAR file + * @param jarFile JAR file which will contain all of the EJB classes (and + * other) files + * @return boolean indicating whether or not the jarFile is up + * to date + */ + protected boolean needToRebuild( Hashtable ejbFiles, File jarFile ) + { + if( jarFile.exists() ) + { + long lastBuild = jarFile.lastModified(); + + if( config.manifest != null && config.manifest.exists() && + config.manifest.lastModified() > lastBuild ) + { + log( "Build needed because manifest " + config.manifest + " is out of date", + Project.MSG_VERBOSE ); + return true; + } + + Iterator fileIter = ejbFiles.values().iterator(); + + // Loop through the files seeing if any has been touched + // more recently than the destination jar. + while( fileIter.hasNext() ) + { + File currentFile = ( File )fileIter.next(); + if( lastBuild < currentFile.lastModified() ) + { + log( "Build needed because " + currentFile.getPath() + " is out of date", + Project.MSG_VERBOSE ); + return true; + } + } + return false; + } + + return true; + } + + /** + * This method returns a list of EJB files found when the specified EJB + * descriptor is parsed and processed. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @return Hashtable of EJB class (and other) files to be added to the + * completed JAR file + * @throws SAXException Any SAX exception, possibly wrapping another + * exception + * @throws IOException An IOException from the parser, possibly from a the + * byte stream or character stream + */ + protected Hashtable parseEjbFiles( String descriptorFileName, SAXParser saxParser ) + throws IOException, SAXException + { + FileInputStream descriptorStream = null; + Hashtable ejbFiles = null; + + try + { + + /* + * Parse the ejb deployment descriptor. While it may not + * look like much, we use a SAXParser and an inner class to + * get hold of all the classfile names for the descriptor. + */ + descriptorStream = new FileInputStream( new File( config.descriptorDir, descriptorFileName ) ); + saxParser.parse( new InputSource( descriptorStream ), handler ); + + ejbFiles = handler.getFiles(); + + } + finally + { + if( descriptorStream != null ) + { + try + { + descriptorStream.close(); + } + catch( IOException closeException ) + {} + } + } + + return ejbFiles; + } + + /** + * Register the locations of all known DTDs. vendor-specific subclasses + * should override this method to define the vendor-specific locations of + * the EJB DTDs + * + * @param handler Description of Parameter + */ + protected void registerKnownDTDs( DescriptorHandler handler ) + { + // none to register for generic + } + + /** + * Returns true, if the meta-inf dir is being explicitly set, false + * otherwise. + * + * @return Description of the Returned Value + */ + protected boolean usingBaseJarName() + { + return config.baseJarName != null; + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarfile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarfile, Hashtable files, + String publicId ) + throws BuildException + { + + JarOutputStream jarStream = null; + try + { + // clean the addedfiles Vector + addedfiles = new ArrayList(); + + /* + * If the jarfile already exists then whack it and recreate it. + * Should probably think of a more elegant way to handle this + * so that in case of errors we don't leave people worse off + * than when we started =) + */ + if( jarfile.exists() ) + { + jarfile.delete(); + } + jarfile.getParentFile().mkdirs(); + jarfile.createNewFile(); + + InputStream in = null; + Manifest manifest = null; + try + { + File manifestFile = new File( getConfig().descriptorDir, baseName + "-manifest.mf" ); + if( manifestFile.exists() ) + { + in = new FileInputStream( manifestFile ); + } + else if( config.manifest != null ) + { + in = new FileInputStream( config.manifest ); + if( in == null ) + { + throw new BuildException( "Could not find manifest file: " + config.manifest, + getLocation() ); + } + } + else + { + String defaultManifest = "/org/apache/tools/ant/defaultManifest.mf"; + in = this.getClass().getResourceAsStream( defaultManifest ); + if( in == null ) + { + throw new BuildException( "Could not find default manifest: " + defaultManifest, + getLocation() ); + } + } + + manifest = new Manifest( in ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest", e, getLocation() ); + } + finally + { + if( in != null ) + { + in.close(); + } + } + + // Create the streams necessary to write the jarfile + + jarStream = new JarOutputStream( new FileOutputStream( jarfile ), manifest ); + jarStream.setMethod( JarOutputStream.DEFLATED ); + + // Loop through all the class files found and add them to the jar + for( Iterator entryIterator = files.keySet().iterator(); entryIterator.hasNext(); ) + { + String entryName = ( String )entryIterator.next(); + File entryFile = ( File )files.get( entryName ); + + log( "adding file '" + entryName + "'", + Project.MSG_VERBOSE ); + + addFileToJar( jarStream, entryFile, entryName ); + + // See if there are any inner classes for this class and add them in if there are + InnerClassFilenameFilter flt = new InnerClassFilenameFilter( entryFile.getName() ); + File entryDir = entryFile.getParentFile(); + String[] innerfiles = entryDir.list( flt ); + for( int i = 0, n = innerfiles.length; i < n; i++ ) + { + + //get and clean up innerclass name + int entryIndex = entryName.lastIndexOf( entryFile.getName() ) - 1; + if( entryIndex < 0 ) + { + entryName = innerfiles[i]; + } + else + { + entryName = entryName.substring( 0, entryIndex ) + File.separatorChar + innerfiles[i]; + } + // link the file + entryFile = new File( config.srcDir, entryName ); + + log( "adding innerclass file '" + entryName + "'", + Project.MSG_VERBOSE ); + + addFileToJar( jarStream, entryFile, entryName ); + + } + } + } + catch( IOException ioe ) + { + String msg = "IOException while processing ejb-jar file '" + + jarfile.toString() + + "'. Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + finally + { + if( jarStream != null ) + { + try + { + jarStream.close(); + } + catch( IOException closeException ) + {} + } + } + } + + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( destDir, baseName + genericJarSuffix ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java new file mode 100644 index 000000000..0dfdd0041 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.IOException; +import java.util.Hashtable; +import javax.xml.parsers.SAXParser; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.xml.sax.SAXException; + +/** + * This class is used to generate iPlanet Application Server (iAS) 6.0 stubs and + * skeletons and build an EJB Jar file. It is designed to be used with the Ant + * ejbjar task. If only stubs and skeletons need to be generated + * (in other words, if no JAR file needs to be created), refer to the iplanet-ejbc + * task and the IPlanetEjbcTask class.

              + * + * The following attributes may be specified by the user: + *

                + *
              • destdir -- The base directory into which the generated JAR + * files will be written. Each JAR file is written in directories which + * correspond to their location within the "descriptordir" namespace. This is + * a required attribute. + *
              • classpath -- The classpath used when generating EJB stubs and + * skeletons. This is an optional attribute (if omitted, the classpath + * specified in the "ejbjar" parent task will be used). If specified, the + * classpath elements will be prepended to the classpath specified in the + * parent "ejbjar" task. Note that nested "classpath" elements may also be + * used. + *
              • keepgenerated -- Indicates whether or not the Java source files + * which are generated by ejbc will be saved or automatically deleted. If + * "yes", the source files will be retained. This is an optional attribute (if + * omitted, it defaults to "no"). + *
              • debug -- Indicates whether or not the ejbc utility should log + * additional debugging statements to the standard output. If "yes", the + * additional debugging statements will be generated (if omitted, it defaults + * to "no"). + *
              • iashome -- May be used to specify the "home" directory for this + * iPlanet Application server installation. This is used to find the ejbc + * utility if it isn't included in the user's system path. This is an optional + * attribute (if specified, it should refer to the [install-location]/iplanet/ias6/ias + * directory). If omitted, the ejbc utility + * must be on the user's system path. + *
              • suffix -- String value appended to the JAR filename when + * creating each JAR. This attribute is not required (if omitted, it defaults + * to ".jar"). + *
              + *

              + * + * For each EJB descriptor found in the "ejbjar" parent task, this deployment + * tool will locate the three classes that comprise the EJB. If these class + * files cannot be located in the specified srcdir directory, the + * task will fail. The task will also attempt to locate the EJB stubs and + * skeletons in this directory. If found, the timestamps on the stubs and + * skeletons will be checked to ensure they are up to date. Only if these files + * cannot be found or if they are out of date will ejbc be called. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetEjbc + */ +public class IPlanetDeploymentTool extends GenericDeploymentTool +{ + + /* + * Regardless of the name of the iAS-specific EJB descriptor file, it will + * written in the completed JAR file as "ias-ejb-jar.xml". This is the + * naming convention implemented by iAS. + */ + private final static String IAS_DD = "ias-ejb-jar.xml"; + private String jarSuffix = ".jar"; + private boolean keepgenerated = false; + private boolean debug = false; + + /* + * Filenames of the standard EJB descriptor (which is passed to this class + * from the parent "ejbjar" task) and the iAS-specific EJB descriptor + * (whose name is determined by this class). Both filenames are relative + * to the directory specified by the "srcdir" attribute in the ejbjar task. + */ + private String descriptorName; + + /* + * The displayName variable stores the value of the "display-name" element + * from the standard EJB descriptor. As a future enhancement to this task, + * we may determine the name of the EJB JAR file using this display-name, + * but this has not be implemented yet. + */ + private String displayName; + private String iasDescriptorName; + + /* + * Attributes set by the Ant build file + */ + private File iashome; + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debug A boolean indicating if debugging output should be generated + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Since iAS doesn't generate a "generic" JAR as part of its processing, + * this attribute is ignored and a warning message is displayed to the user. + * + * @param inString the string to use as the suffix. This parameter is + * ignored. + */ + public void setGenericJarSuffix( String inString ) + { + log( "Since a generic JAR file is not created during processing, the " + + "iPlanet Deployment Tool does not support the " + + "\"genericjarsuffix\" attribute. It will be ignored.", + Project.MSG_WARN ); + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iashome The home directory for the user's iAS installation. + */ + public void setIashome( File iashome ) + { + this.iashome = iashome; + } + + /** + * Setter method used to specify whether the Java source files generated by + * the ejbc utility should be saved or automatically deleted. + * + * @param keepgenerated boolean which, if true, indicates that + * Java source files generated by ejbc for the stubs and skeletons + * should be kept. + */ + public void setKeepgenerated( boolean keepgenerated ) + { + this.keepgenerated = keepgenerated; + } + + /** + * Setter method used to specify the filename suffix (for example, ".jar") + * for the JAR files to be created. + * + * @param jarSuffix The string to use as the JAR filename suffix. + */ + public void setSuffix( String jarSuffix ) + { + this.jarSuffix = jarSuffix; + } + + public void processDescriptor( String descriptorName, SAXParser saxParser ) + { + this.descriptorName = descriptorName; + + log( "iPlanet Deployment Tool processing: " + descriptorName + " (and " + + getIasDescriptorName() + ")", Project.MSG_VERBOSE ); + + super.processDescriptor( descriptorName, saxParser ); + } + + /** + * The iAS ejbc utility doesn't require the Public ID of the descriptor's + * DTD for it to process correctly--this method always returns null + * . + * + * @return null. + */ + protected String getPublicId() + { + return null; + } + + /** + * Add the iAS-specific EJB descriptor to the list of files which will be + * written to the JAR file. + * + * @param ejbFiles Hashtable of EJB class (and other) files to be added to + * the completed JAR file. + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + ejbFiles.put( META_DIR + IAS_DD, new File( getConfig().descriptorDir, + getIasDescriptorName() ) ); + } + + /** + * Verifies that the user selections are valid. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @throws BuildException If the user selections are invalid. + */ + protected void checkConfiguration( String descriptorFileName, + SAXParser saxParser ) + throws BuildException + { + + int startOfName = descriptorFileName.lastIndexOf( File.separatorChar ) + 1; + String stdXml = descriptorFileName.substring( startOfName ); + if( stdXml.equals( EJB_DD ) && ( getConfig().baseJarName == null ) ) + { + String msg = "No name specified for the completed JAR file. The EJB" + + " descriptor should be prepended with the JAR " + + "name or it should be specified using the " + + "attribute \"basejarname\" in the \"ejbjar\" task."; + throw new BuildException( msg, getLocation() ); + } + + File iasDescriptor = new File( getConfig().descriptorDir, + getIasDescriptorName() ); + if( ( !iasDescriptor.exists() ) || ( !iasDescriptor.isFile() ) ) + { + String msg = "The iAS-specific EJB descriptor (" + + iasDescriptor + ") was not found."; + throw new BuildException( msg, getLocation() ); + } + + if( ( iashome != null ) && ( !iashome.isDirectory() ) ) + { + String msg = "If \"iashome\" is specified, it must be a valid " + + "directory (it was set to " + iashome + ")."; + throw new BuildException( msg, getLocation() ); + } + } + + /** + * This method returns a list of EJB files found when the specified EJB + * descriptor is parsed and processed. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @return Hashtable of EJB class (and other) files to be added to the + * completed JAR file + * @throws IOException An IOException from the parser, possibly from the + * byte stream or character stream + * @throws SAXException Any SAX exception, possibly wrapping another + * exception + */ + protected Hashtable parseEjbFiles( String descriptorFileName, + SAXParser saxParser ) + throws IOException, SAXException + { + + Hashtable files; + + /* + * Build and populate an instance of the ejbc utility + */ + IPlanetEjbc ejbc = new IPlanetEjbc( + new File( getConfig().descriptorDir, + descriptorFileName ), + new File( getConfig().descriptorDir, + getIasDescriptorName() ), + getConfig().srcDir, + getCombinedClasspath().toString(), + saxParser ); + ejbc.setRetainSource( keepgenerated ); + ejbc.setDebugOutput( debug ); + if( iashome != null ) + { + ejbc.setIasHomeDir( iashome ); + } + + /* + * Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed + */ + try + { + ejbc.execute(); + } + catch( IPlanetEjbc.EjbcException e ) + { + throw new BuildException( "An error has occurred while trying to " + + "execute the iAS ejbc utility", e, getLocation() ); + } + + displayName = ejbc.getDisplayName(); + files = ejbc.getEjbFiles(); + + /* + * Add CMP descriptors to the list of EJB files + */ + String[] cmpDescriptors = ejbc.getCmpDescriptors(); + if( cmpDescriptors.length > 0 ) + { + File baseDir = getConfig().descriptorDir; + + int endOfPath = descriptorFileName.lastIndexOf( File.separator ); + String relativePath = descriptorFileName.substring( 0, endOfPath + 1 ); + + for( int i = 0; i < cmpDescriptors.length; i++ ) + { + int endOfCmp = cmpDescriptors[i].lastIndexOf( '/' ); + String cmpDescriptor = cmpDescriptors[i].substring( endOfCmp + 1 ); + + File cmpFile = new File( baseDir, relativePath + cmpDescriptor ); + if( !cmpFile.exists() ) + { + throw new BuildException( "The CMP descriptor file (" + + cmpFile + ") could not be found.", getLocation() ); + } + files.put( cmpDescriptors[i], cmpFile ); + } + } + + return files; + } + + /** + * Get the name of the Jar that will be written. The modification date of + * this jar will be checked against the dependent bean classes. + * + * @param baseName String name of the EJB JAR file to be written (without a + * filename extension). + * @return File representing the JAR file which will be written. + */ + File getVendorOutputJarFile( String baseName ) + { + File jarFile = new File( getDestDir(), baseName + jarSuffix ); + log( "JAR file name: " + jarFile.toString(), Project.MSG_VERBOSE ); + return jarFile; + } + + /** + * Determines the name of the iAS-specific EJB descriptor using the + * specified standard EJB descriptor name. In general, the standard + * descriptor will be named "[basename]-ejb-jar.xml", and this method will + * return "[basename]-ias-ejb-jar.xml". + * + * @return The name of the iAS-specific EJB descriptor file. + */ + private String getIasDescriptorName() + { + + /* + * Only calculate the descriptor name once + */ + if( iasDescriptorName != null ) + { + return iasDescriptorName; + } + + String path = "";// Directory path of the EJB descriptor + String basename;// Filename appearing before name terminator + String remainder;// Filename appearing after the name terminator + + /* + * Find the end of the standard descriptor's relative path + */ + int startOfFileName = descriptorName.lastIndexOf( File.separatorChar ); + if( startOfFileName != -1 ) + { + path = descriptorName.substring( 0, startOfFileName + 1 ); + } + + /* + * Check to see if the standard name is used (there's no basename) + */ + if( descriptorName.substring( startOfFileName + 1 ).equals( EJB_DD ) ) + { + basename = ""; + remainder = EJB_DD; + + } + else + { + int endOfBaseName = descriptorName.indexOf( + getConfig().baseNameTerminator, + startOfFileName ); + /* + * Check for the odd case where the terminator and/or filename + * extension aren't found. These will ensure "ias-" appears at the + * end of the name and before the '.' (if present). + */ + if( endOfBaseName < 0 ) + { + endOfBaseName = descriptorName.lastIndexOf( '.' ) - 1; + if( endOfBaseName < 0 ) + { + endOfBaseName = descriptorName.length() - 1; + } + } + + basename = descriptorName.substring( startOfFileName + 1, + endOfBaseName + 1 ); + remainder = descriptorName.substring( endOfBaseName + 1 ); + } + + iasDescriptorName = path + basename + "ias-" + remainder; + return iasDescriptorName; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java new file mode 100644 index 000000000..8c3b18d60 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java @@ -0,0 +1,1690 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.AttributeList; +import org.xml.sax.HandlerBase; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Utility class to compile EJB stubs and skeletons for the iPlanet Application + * Server (iAS). The class will read a standard EJB descriptor (as well as an + * EJB descriptor specific to iPlanet Application Server) to identify one or + * more EJBs to process. It will search for EJB "source" classes (the remote + * interface, home interface, and EJB implementation class) and the EJB stubs + * and skeletons in the specified destination directory. Only if the stubs and + * skeletons cannot be found or if they're out of date will the iPlanet + * Application Server ejbc utility be run.

              + * + * Because this class (and it's assorted inner classes) may be bundled into the + * iPlanet Application Server distribution at some point (and removed from the + * Ant distribution), the class has been written to be independent of all + * Ant-specific classes. It is also for this reason (and to avoid cluttering the + * Apache Ant source files) that this utility has been packaged into a single + * source file.

              + * + * For more information on Ant Tasks for iPlanet Application Server, see the + * IPlanetDeploymentTool and IPlanetEjbcTask classes. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetDeploymentTool + * @see IPlanetEjbcTask + */ +public class IPlanetEjbc +{ + + /* + * Constants used for the "beantype" attribute + */ + private final static String ENTITY_BEAN = "entity"; + private final static String STATELESS_SESSION = "stateless"; + private final static String STATEFUL_SESSION = "stateful"; + + /* + * Options passed to the iAS ejbc + */ + private boolean retainSource = false; + private boolean debugOutput = false; + private EjbcHandler handler = new EjbcHandler(); + + /* + * This Hashtable maintains a list of EJB class files processed by the ejbc + * utility (both "source" class files as well as stubs and skeletons). The + * key for the Hashtable is a String representing the path to the class file + * (relative to the destination directory). The value for the Hashtable is + * a File object which reference the actual class file. + */ + private Hashtable ejbFiles = new Hashtable(); + + /* + * Classpath used when the iAS ejbc is called + */ + private String classpath; + private String[] classpathElements; + + /* + * Directory where "source" EJB files are stored and where stubs and + * skeletons will also be written. + */ + private File destDirectory; + + /* + * Value of the display-name element read from the standard EJB descriptor + */ + private String displayName; + private File iasDescriptor; + + /* + * iAS installation directory (used if ejbc isn't on user's PATH) + */ + private File iasHomeDir; + + /* + * Parser and handler used to process both EJB descriptor files + */ + private SAXParser parser; + + /* + * Filenames of the standard EJB descriptor and the iAS-specific descriptor + */ + private File stdDescriptor; + + /** + * Constructs an instance which may be used to process EJB descriptors and + * generate EJB stubs and skeletons, if needed. + * + * @param stdDescriptor File referencing a standard EJB descriptor. + * @param iasDescriptor File referencing an iAS-specific EJB descriptor. + * @param destDirectory File referencing the base directory where both EJB + * "source" files are found and where stubs and skeletons will be + * written. + * @param classpath String representation of the classpath to be used by the + * iAS ejbc utility. + * @param parser SAXParser to be used to process both of the EJB + * descriptors. + */ + public IPlanetEjbc( File stdDescriptor, + File iasDescriptor, + File destDirectory, + String classpath, + SAXParser parser ) + { + this.stdDescriptor = stdDescriptor; + this.iasDescriptor = iasDescriptor; + this.destDirectory = destDirectory; + this.classpath = classpath; + this.parser = parser; + + /* + * Parse the classpath into it's individual elements and store the + * results in the "classpathElements" instance variable. + */ + List elements = new ArrayList(); + if( classpath != null ) + { + StringTokenizer st = new StringTokenizer( classpath, + File.pathSeparator ); + while( st.hasMoreTokens() ) + { + elements.add( st.nextToken() ); + } + classpathElements + = ( String[] )elements.toArray( new String[elements.size()] ); + } + } + + /** + * Main application method for the iPlanet Application Server ejbc utility. + * If the application is run with no commandline arguments, a usage + * statement is printed for the user. + * + * @param args The commandline arguments passed to the application. + */ + public static void main( String[] args ) + { + File stdDescriptor; + File iasDescriptor; + File destDirectory = null; + String classpath = null; + SAXParser parser = null; + boolean debug = false; + boolean retainSource = false; + IPlanetEjbc ejbc; + + if( ( args.length < 2 ) || ( args.length > 8 ) ) + { + usage(); + return; + } + + stdDescriptor = new File( args[args.length - 2] ); + iasDescriptor = new File( args[args.length - 1] ); + + for( int i = 0; i < args.length - 2; i++ ) + { + if( args[i].equals( "-classpath" ) ) + { + classpath = args[++i]; + } + else if( args[i].equals( "-d" ) ) + { + destDirectory = new File( args[++i] ); + } + else if( args[i].equals( "-debug" ) ) + { + debug = true; + } + else if( args[i].equals( "-keepsource" ) ) + { + retainSource = true; + } + else + { + usage(); + return; + } + } + + /* + * If the -classpath flag isn't specified, use the system classpath + */ + if( classpath == null ) + { + Properties props = System.getProperties(); + classpath = props.getProperty( "java.class.path" ); + } + + /* + * If the -d flag isn't specified, use the working directory as the + * destination directory + */ + if( destDirectory == null ) + { + Properties props = System.getProperties(); + destDirectory = new File( props.getProperty( "user.dir" ) ); + } + + /* + * Construct a SAXParser used to process the descriptors + */ + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + parserFactory.setValidating( true ); + try + { + parser = parserFactory.newSAXParser(); + } + catch( Exception e ) + { + // SAXException or ParserConfigurationException may be thrown + System.out.println( "An exception was generated while trying to " ); + System.out.println( "create a new SAXParser." ); + e.printStackTrace(); + return; + } + + /* + * Build and populate an instance of the ejbc utility + */ + ejbc = new IPlanetEjbc( stdDescriptor, iasDescriptor, destDirectory, + classpath, parser ); + ejbc.setDebugOutput( debug ); + ejbc.setRetainSource( retainSource ); + + /* + * Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed + */ + try + { + ejbc.execute(); + } + catch( IOException e ) + { + System.out.println( "An IOException has occurred while reading the " + + "XML descriptors (" + e.getMessage() + ")." ); + return; + } + catch( SAXException e ) + { + System.out.println( "A SAXException has occurred while reading the " + + "XML descriptors (" + e.getMessage() + ")." ); + return; + } + catch( IPlanetEjbc.EjbcException e ) + { + System.out.println( "An error has occurred while executing the ejbc " + + "utility (" + e.getMessage() + ")." ); + return; + } + } + + /** + * Print a usage statement. + */ + private static void usage() + { + System.out.println( "java org.apache.tools.ant.taskdefs.optional.ejb.IPlanetEjbc \\" ); + System.out.println( " [OPTIONS] [EJB 1.1 descriptor] [iAS EJB descriptor]" ); + System.out.println( "" ); + System.out.println( "Where OPTIONS are:" ); + System.out.println( " -debug -- for additional debugging output" ); + System.out.println( " -keepsource -- to retain Java source files generated" ); + System.out.println( " -classpath [classpath] -- classpath used for compilation" ); + System.out.println( " -d [destination directory] -- directory for compiled classes" ); + System.out.println( "" ); + System.out.println( "If a classpath is not specified, the system classpath" ); + System.out.println( "will be used. If a destination directory is not specified," ); + System.out.println( "the current working directory will be used (classes will" ); + System.out.println( "still be placed in subfolders which correspond to their" ); + System.out.println( "package name)." ); + System.out.println( "" ); + System.out.println( "The EJB home interface, remote interface, and implementation" ); + System.out.println( "class must be found in the destination directory. In" ); + System.out.println( "addition, the destination will look for the stubs and skeletons" ); + System.out.println( "in the destination directory to ensure they are up to date." ); + } + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debugOutput A boolean indicating if debugging output should be + * generated + */ + public void setDebugOutput( boolean debugOutput ) + { + this.debugOutput = debugOutput; + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iasHomeDir The new IasHomeDir value + */ + public void setIasHomeDir( File iasHomeDir ) + { + this.iasHomeDir = iasHomeDir; + } + + /** + * Sets whether or not the Java source files which are generated by the ejbc + * process should be retained or automatically deleted. + * + * @param retainSource The new RetainSource value + */ + public void setRetainSource( boolean retainSource ) + { + this.retainSource = retainSource; + } + + /** + * Returns the list of CMP descriptors referenced in the EJB descriptors. + * + * @return An array of CMP descriptors. + */ + public String[] getCmpDescriptors() + { + List returnList = new ArrayList(); + + EjbInfo[] ejbs = handler.getEjbs(); + + for( int i = 0; i < ejbs.length; i++ ) + { + List descriptors = ( List )ejbs[i].getCmpDescriptors(); + returnList.addAll( descriptors ); + } + + return ( String[] )returnList.toArray( new String[returnList.size()] ); + } + + /** + * Returns the display-name element read from the standard EJB descriptor. + * + * @return The EJB-JAR display name. + */ + public String getDisplayName() + { + return displayName; + } + + /** + * Returns a Hashtable which contains a list of EJB class files processed by + * the ejbc utility (both "source" class files as well as stubs and + * skeletons). The key for the Hashtable is a String representing the path + * to the class file (relative to the destination directory). The value for + * the Hashtable is a File object which reference the actual class file. + * + * @return The list of EJB files processed by the ejbc utility. + */ + public Hashtable getEjbFiles() + { + return ejbFiles; + } + + /** + * Compiles the stub and skeletons for the specified EJBs, if they need to + * be updated. + * + * @throws EjbcException If the ejbc utility cannot be correctly configured + * or if one or more of the EJB "source" classes cannot be found in the + * destination directory + * @throws IOException If the parser encounters a problem reading the XML + * file + * @throws SAXException If the parser encounters a problem processing the + * XML descriptor (it may wrap another exception) + */ + public void execute() + throws EjbcException, IOException, SAXException + { + + checkConfiguration();// Throws EjbcException if unsuccessful + + EjbInfo[] ejbs = getEjbs();// Returns list of EJBs for processing + + for( int i = 0; i < ejbs.length; i++ ) + { + log( "EJBInfo..." ); + log( ejbs[i].toString() ); + } + + for( int i = 0; i < ejbs.length; i++ ) + { + EjbInfo ejb = ejbs[i]; + + ejb.checkConfiguration( destDirectory );// Throws EjbcException + + if( ejb.mustBeRecompiled( destDirectory ) ) + { + log( ejb.getName() + " must be recompiled using ejbc." ); + + String[] arguments = buildArgumentList( ejb ); + callEjbc( arguments ); + + } + else + { + log( ejb.getName() + " is up to date." ); + } + } + } + + /** + * Registers the location of a local DTD file or resource. By registering a + * local DTD, EJB descriptors can be parsed even when the remote servers + * which contain the "public" DTDs cannot be accessed. + * + * @param publicID The public DTD identifier found in an XML document. + * @param location The file or resource name for the appropriate DTD stored + * on the local machine. + */ + public void registerDTD( String publicID, String location ) + { + handler.registerDTD( publicID, location ); + } + + /** + * Verifies that the user selections are valid. + * + * @throws EjbcException If the user selections are invalid. + */ + protected void checkConfiguration() + throws EjbcException + { + + String msg = ""; + + if( stdDescriptor == null ) + { + msg += "A standard XML descriptor file must be specified. "; + } + if( iasDescriptor == null ) + { + msg += "An iAS-specific XML descriptor file must be specified. "; + } + if( classpath == null ) + { + msg += "A classpath must be specified. "; + } + if( parser == null ) + { + msg += "An XML parser must be specified. "; + } + + if( destDirectory == null ) + { + msg += "A destination directory must be specified. "; + } + else if( !destDirectory.exists() ) + { + msg += "The destination directory specified does not exist. "; + } + else if( !destDirectory.isDirectory() ) + { + msg += "The destination specified is not a directory. "; + } + + if( msg.length() > 0 ) + { + throw new EjbcException( msg ); + } + } + + /** + * Parses the EJB descriptors and returns a list of EJBs which may need to + * be compiled. + * + * @return An array of objects which describe the EJBs to be processed. + * @throws IOException If the parser encounters a problem reading the XML + * files + * @throws SAXException If the parser encounters a problem processing the + * XML descriptor (it may wrap another exception) + */ + private EjbInfo[] getEjbs() + throws IOException, SAXException + { + EjbInfo[] ejbs = null; + + /* + * The EJB information is gathered from the standard XML EJB descriptor + * and the iAS-specific XML EJB descriptor using a SAX parser. + */ + parser.parse( stdDescriptor, handler ); + parser.parse( iasDescriptor, handler ); + ejbs = handler.getEjbs(); + + return ejbs; + } + + /** + * Based on this object's instance variables as well as the EJB to be + * processed, the correct flags and parameters are set for the ejbc + * command-line utility. + * + * @param ejb The EJB for which stubs and skeletons will be compiled. + * @return An array of Strings which are the command-line parameters for for + * the ejbc utility. + */ + private String[] buildArgumentList( EjbInfo ejb ) + { + + List arguments = new ArrayList(); + + /* + * OPTIONAL COMMAND LINE PARAMETERS + */ + if( debugOutput ) + { + arguments.add( "-debug" ); + } + + /* + * No beantype flag is needed for an entity bean + */ + if( ejb.getBeantype().equals( STATELESS_SESSION ) ) + { + arguments.add( "-sl" ); + } + else if( ejb.getBeantype().equals( STATEFUL_SESSION ) ) + { + arguments.add( "-sf" ); + } + + if( ejb.getIiop() ) + { + arguments.add( "-iiop" ); + } + + if( ejb.getCmp() ) + { + arguments.add( "-cmp" ); + } + + if( retainSource ) + { + arguments.add( "-gs" ); + } + + if( ejb.getHasession() ) + { + arguments.add( "-fo" ); + } + + /* + * REQUIRED COMMAND LINE PARAMETERS + */ + arguments.add( "-classpath" ); + arguments.add( classpath ); + + arguments.add( "-d" ); + arguments.add( destDirectory.toString() ); + + arguments.add( ejb.getHome().getQualifiedClassName() ); + arguments.add( ejb.getRemote().getQualifiedClassName() ); + arguments.add( ejb.getImplementation().getQualifiedClassName() ); + + /* + * Convert the List into an Array and return it + */ + return ( String[] )arguments.toArray( new String[arguments.size()] ); + } + + /** + * Executes the iPlanet Application Server ejbc command-line utility. + * + * @param arguments Command line arguments to be passed to the ejbc utility. + */ + private void callEjbc( String[] arguments ) + { + + /* + * Concatenate all of the command line arguments into a single String + */ + StringBuffer args = new StringBuffer(); + for( int i = 0; i < arguments.length; i++ ) + { + args.append( arguments[i] ).append( " " ); + } + + /* + * If an iAS home directory is specified, prepend it to the commmand + */ + String command; + if( iasHomeDir == null ) + { + command = ""; + } + else + { + command = iasHomeDir.toString() + File.separator + "bin" + + File.separator; + } + command += "ejbc "; + + log( command + args ); + + /* + * Use the Runtime object to execute an external command. Use the + * RedirectOutput inner class to direct the standard and error output + * from the command to the JRE's standard output + */ + try + { + Process p = Runtime.getRuntime().exec( command + args ); + RedirectOutput output = new RedirectOutput( p.getInputStream() ); + RedirectOutput error = new RedirectOutput( p.getErrorStream() ); + output.start(); + error.start(); + p.waitFor(); + p.destroy(); + } + catch( IOException e ) + { + log( "An IOException has occurred while trying to execute ejbc." ); + e.printStackTrace(); + } + catch( InterruptedException e ) + { + // Do nothing + } + } + + /** + * Convenience method used to print messages to the user if debugging + * messages are enabled. + * + * @param msg The String to print to standard output. + */ + private void log( String msg ) + { + if( debugOutput ) + { + System.out.println( msg ); + } + } + + + /* + * Inner classes follow + */ + + /** + * This inner class is used to signal any problems during the execution of + * the ejbc compiler. + * + * @author Greg Nelson greg@netscape.com + * + */ + public class EjbcException extends Exception + { + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of the exception which has occurred. + */ + public EjbcException( String msg ) + { + super( msg ); + } + }// End of EjbInfo inner class + + /** + * Convenience class used to represent the fully qualified name of a Java + * class. It provides an easy way to retrieve components of the class name + * in a format that is convenient for building iAS stubs and skeletons. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class Classname + {// Name of the package for this class + private String className;// Fully qualified name of the Java class + private String packageName; + private String qualifiedName;// Name of the class without the package + + /** + * This constructor builds an object which represents the name of a Java + * class. + * + * @param qualifiedName String representing the fully qualified class + * name of the Java class. + */ + public Classname( String qualifiedName ) + { + if( qualifiedName == null ) + { + return; + } + + this.qualifiedName = qualifiedName; + + int index = qualifiedName.lastIndexOf( '.' ); + if( index == -1 ) + { + className = qualifiedName; + packageName = ""; + } + else + { + packageName = qualifiedName.substring( 0, index ); + className = qualifiedName.substring( index + 1 ); + } + } + + /** + * Returns a File which references the class relative to the specified + * directory. Note that the class file may or may not exist. + * + * @param directory A File referencing the base directory containing + * class files. + * @return File referencing this class. + */ + public File getClassFile( File directory ) + { + String pathToFile = qualifiedName.replace( '.', File.separatorChar ) + + ".class"; + return new File( directory, pathToFile ); + } + + /** + * Gets the Java class name without the package structure. + * + * @return String representing the name for the class. + */ + public String getClassName() + { + return className; + } + + /** + * Gets the package name for the Java class. + * + * @return String representing the package name for the class. + */ + public String getPackageName() + { + return packageName; + } + + /** + * Gets the fully qualified name of the Java class. + * + * @return String representing the fully qualified class name. + */ + public String getQualifiedClassName() + { + return qualifiedName; + } + + /** + * Gets the fully qualified name of the Java class with underscores + * separating the components of the class name rather than periods. This + * format is used in naming some of the stub and skeleton classes for + * the iPlanet Application Server. + * + * @return String representing the fully qualified class name using + * underscores instead of periods. + */ + public String getQualifiedWithUnderscores() + { + return qualifiedName.replace( '.', '_' ); + } + + /** + * String representation of this class name. It returns the fully + * qualified class name. + * + * @return String representing the fully qualified class name. + */ + public String toString() + { + return getQualifiedClassName(); + } + }// End of EjbcHandler inner class + + + /** + * This inner class represents an EJB that will be compiled using ejbc. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class EjbInfo + {// EJB's implementation class + private String beantype = "entity";// or "stateful" or "stateless" + private boolean cmp = false;// Does this EJB support CMP? + private boolean iiop = false;// Does this EJB support IIOP? + private boolean hasession = false;// Does this EJB require failover? + private List cmpDescriptors = new ArrayList();// EJB's display name + private Classname home;// EJB's remote interface name + private Classname implementation; + private String name;// EJB's home interface name + private Classname remote;// CMP descriptor list + + /** + * Construct a new EJBInfo object with the given name. + * + * @param name The display name for the EJB. + */ + public EjbInfo( String name ) + { + this.name = name; + } + + public void setBeantype( String beantype ) + { + this.beantype = beantype.toLowerCase(); + } + + public void setCmp( boolean cmp ) + { + this.cmp = cmp; + } + + public void setCmp( String cmp ) + { + setCmp( cmp.equals( "Container" ) ); + } + + public void setHasession( boolean hasession ) + { + this.hasession = hasession; + } + + public void setHasession( String hasession ) + { + setHasession( hasession.equals( "true" ) ); + } + + /* + * Below are getter's and setter's for each of the instance variables. + * Note that (in addition to supporting setters with the same type as + * the instance variable) a setter is provided with takes a String + * argument -- this are provided so the XML document handler can set + * the EJB values using the Strings it parses. + */ + public void setHome( String home ) + { + setHome( new Classname( home ) ); + } + + public void setHome( Classname home ) + { + this.home = home; + } + + public void setIiop( boolean iiop ) + { + this.iiop = iiop; + } + + public void setIiop( String iiop ) + { + setIiop( iiop.equals( "true" ) ); + } + + public void setImplementation( String implementation ) + { + setImplementation( new Classname( implementation ) ); + } + + public void setImplementation( Classname implementation ) + { + this.implementation = implementation; + } + + public void setRemote( String remote ) + { + setRemote( new Classname( remote ) ); + } + + public void setRemote( Classname remote ) + { + this.remote = remote; + } + + public String getBeantype() + { + return beantype; + } + + public boolean getCmp() + { + return cmp; + } + + public List getCmpDescriptors() + { + return cmpDescriptors; + } + + public boolean getHasession() + { + return hasession; + } + + public Classname getHome() + { + return home; + } + + public boolean getIiop() + { + return iiop; + } + + public Classname getImplementation() + { + return implementation; + } + + /** + * Returns the display name of the EJB. If a display name has not been + * set, it returns the EJB implementation classname (if the + * implementation class is not set, it returns "[unnamed]"). + * + * @return The display name for the EJB. + */ + public String getName() + { + if( name == null ) + { + if( implementation == null ) + { + return "[unnamed]"; + } + else + { + return implementation.getClassName(); + } + } + return name; + } + + public Classname getRemote() + { + return remote; + } + + public void addCmpDescriptor( String descriptor ) + { + cmpDescriptors.add( descriptor ); + } + + /** + * Determines if the ejbc utility needs to be run or not. If the stubs + * and skeletons can all be found in the destination directory AND all + * of their timestamps are more recent than the EJB source classes + * (home, remote, and implementation classes), the method returns false + * . Otherwise, the method returns true. + * + * @param destDir The directory where the EJB source classes, stubs and + * skeletons are located. + * @return A boolean indicating whether or not the ejbc utility needs to + * be run to bring the stubs and skeletons up to date. + */ + public boolean mustBeRecompiled( File destDir ) + { + + long sourceModified = sourceClassesModified( destDir ); + + long destModified = destClassesModified( destDir ); + + return ( destModified < sourceModified ); + } + + /** + * Convenience method which creates a String representation of all the + * instance variables of an EjbInfo object. + * + * @return A String representing the EjbInfo instance. + */ + public String toString() + { + String s = "EJB name: " + name + + "\n\r home: " + home + + "\n\r remote: " + remote + + "\n\r impl: " + implementation + + "\n\r beantype: " + beantype + + "\n\r cmp: " + cmp + + "\n\r iiop: " + iiop + + "\n\r hasession: " + hasession; + + Iterator i = cmpDescriptors.iterator(); + while( i.hasNext() ) + { + s += "\n\r CMP Descriptor: " + i.next(); + } + + return s; + } + + /** + * Verifies that the EJB is valid--if it is invalid, an exception is + * thrown + * + * @param buildDir The directory where the EJB remote interface, home + * interface, and implementation class must be found. + * @throws EjbcException If the EJB is invalid. + */ + private void checkConfiguration( File buildDir ) + throws EjbcException + { + + /* + * Check that the specified instance variables are valid + */ + if( home == null ) + { + throw new EjbcException( "A home interface was not found " + + "for the " + name + " EJB." ); + } + if( remote == null ) + { + throw new EjbcException( "A remote interface was not found " + + "for the " + name + " EJB." ); + } + if( implementation == null ) + { + throw new EjbcException( "An EJB implementation class was not " + + "found for the " + name + " EJB." ); + } + + if( ( !beantype.equals( ENTITY_BEAN ) ) + && ( !beantype.equals( STATELESS_SESSION ) ) + && ( !beantype.equals( STATEFUL_SESSION ) ) ) + { + throw new EjbcException( "The beantype found (" + beantype + ") " + + "isn't valid in the " + name + " EJB." ); + } + + if( cmp && ( !beantype.equals( ENTITY_BEAN ) ) ) + { + System.out.println( "CMP stubs and skeletons may not be generated" + + " for a Session Bean -- the \"cmp\" attribute will be" + + " ignoredfor the " + name + " EJB." ); + } + + if( hasession && ( !beantype.equals( STATEFUL_SESSION ) ) ) + { + System.out.println( "Highly available stubs and skeletons may " + + "only be generated for a Stateful Session Bean -- the " + + "\"hasession\" attribute will be ignored for the " + + name + " EJB." ); + } + + /* + * Check that the EJB "source" classes all exist + */ + if( !remote.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The remote interface " + + remote.getQualifiedClassName() + " could not be " + + "found." ); + } + if( !home.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The home interface " + + home.getQualifiedClassName() + " could not be " + + "found." ); + } + if( !implementation.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The EJB implementation class " + + implementation.getQualifiedClassName() + " could " + + "not be found." ); + } + } + + /** + * Builds an array of class names which represent the stubs and + * skeletons which need to be generated for a given EJB. The class names + * are fully qualified. Nine classes are generated for all EJBs while an + * additional six classes are generated for beans requiring RMI/IIOP + * access. + * + * @return An array of Strings representing the fully-qualified class + * names for the stubs and skeletons to be generated. + */ + private String[] classesToGenerate() + { + String[] classnames = ( iiop ) ? new String[15] : new String[9]; + + final String remotePkg = remote.getPackageName() + "."; + final String remoteClass = remote.getClassName(); + final String homePkg = home.getPackageName() + "."; + final String homeClass = home.getClassName(); + final String implPkg = implementation.getPackageName() + "."; + final String implFullClass = implementation.getQualifiedWithUnderscores(); + int index = 0; + + String fullPath; + + classnames[index++] = implPkg + "ejb_fac_" + implFullClass; + classnames[index++] = implPkg + "ejb_home_" + implFullClass; + classnames[index++] = implPkg + "ejb_skel_" + implFullClass; + classnames[index++] = remotePkg + "ejb_kcp_skel_" + remoteClass; + classnames[index++] = homePkg + "ejb_kcp_skel_" + homeClass; + classnames[index++] = remotePkg + "ejb_kcp_stub_" + remoteClass; + classnames[index++] = homePkg + "ejb_kcp_stub_" + homeClass; + classnames[index++] = remotePkg + "ejb_stub_" + remoteClass; + classnames[index++] = homePkg + "ejb_stub_" + homeClass; + + if( !iiop ) + { + return classnames; + } + + classnames[index++] = remotePkg + "_" + remoteClass + "_Stub"; + classnames[index++] = homePkg + "_" + homeClass + "_Stub"; + classnames[index++] = remotePkg + "_ejb_RmiCorbaBridge_" + + remoteClass + "_Tie"; + classnames[index++] = homePkg + "_ejb_RmiCorbaBridge_" + homeClass + + "_Tie"; + classnames[index++] = remotePkg + "ejb_RmiCorbaBridge_" + + remoteClass; + classnames[index++] = homePkg + "ejb_RmiCorbaBridge_" + homeClass; + + return classnames; + } + + /** + * Examines each of the EJB stubs and skeletons in the destination + * directory and returns the modification timestamp for the "oldest" + * class. If one of the stubs or skeletons cannot be found, -1 + * is returned. + * + * @param destDir Description of Parameter + * @return The modification timestamp for the "oldest" EJB stub or + * skeleton. If one of the classes cannot be found, -1 + * is returned. + * @throws BuildException If the canonical path of the destination + * directory cannot be found. + */ + private long destClassesModified( File destDir ) + { + String[] classnames = classesToGenerate();// List of all stubs & skels + long destClassesModified = new Date().getTime();// Earliest mod time + boolean allClassesFound = true;// Has each been found? + + /* + * Loop through each stub/skeleton class that must be generated, and + * determine (if all exist) which file has the most recent timestamp + */ + for( int i = 0; i < classnames.length; i++ ) + { + + String pathToClass = + classnames[i].replace( '.', File.separatorChar ) + ".class"; + File classFile = new File( destDir, pathToClass ); + + /* + * Add each stub/skeleton class to the list of EJB files. Note + * that each class is added even if it doesn't exist now. + */ + ejbFiles.put( pathToClass, classFile ); + + allClassesFound = allClassesFound && classFile.exists(); + + if( allClassesFound ) + { + long fileMod = classFile.lastModified(); + + /* + * Keep track of the oldest modification timestamp + */ + destClassesModified = Math.min( destClassesModified, fileMod ); + } + } + + return ( allClassesFound ) ? destClassesModified : -1; + } + + /** + * Examines each of the EJB source classes (home, remote, and + * implementation) and returns the modification timestamp for the + * "oldest" class. + * + * @param buildDir Description of Parameter + * @return The modification timestamp for the "oldest" EJB source class. + * @throws BuildException If one of the EJB source classes cannot be + * found on the classpath. + */ + private long sourceClassesModified( File buildDir ) + { + long latestModified;// The timestamp of the "newest" class + long modified;// Timestamp for a given class + File remoteFile;// File for the remote interface class + File homeFile;// File for the home interface class + File implFile;// File for the EJB implementation class + + /* + * Check the timestamp on the remote interface + */ + remoteFile = remote.getClassFile( buildDir ); + modified = remoteFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + remote.getQualifiedClassName() + " couldn't " + + "be found on the classpath" ); + return -1; + } + latestModified = modified; + + /* + * Check the timestamp on the home interface + */ + homeFile = home.getClassFile( buildDir ); + modified = homeFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + home.getQualifiedClassName() + " couldn't be " + + "found on the classpath" ); + return -1; + } + latestModified = Math.max( latestModified, modified ); + + /* + * Check the timestamp on the EJB implementation class. + * + * Note that if ONLY the implementation class has changed, it's not + * necessary to rebuild the EJB stubs and skeletons. For this + * reason, we ensure the file exists (using lastModified above), but + * we DON'T compare it's timestamp with the timestamps of the home + * and remote interfaces (because it's irrelevant in determining if + * ejbc must be run) + */ + implFile = implementation.getClassFile( buildDir ); + modified = implFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + implementation.getQualifiedClassName() + + " couldn't be found on the classpath" ); + return -1; + } + + String pathToFile = remote.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, remoteFile ); + + pathToFile = home.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, homeFile ); + + pathToFile = implementation.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, implFile ); + + return latestModified; + } + + }// End of EjbcException inner class + + + /** + * This inner class is an XML document handler that can be used to parse EJB + * descriptors (both the standard EJB descriptor as well as the iAS-specific + * descriptor that stores additional values for iAS). Once the descriptors + * have been processed, the list of EJBs found can be obtained by calling + * the getEjbs() method. + * + * @author Greg Nelson greg@netscape.com + * + * @see EjbInfo + */ + private class EjbcHandler extends HandlerBase + { + + /* + * Two Maps are used to track local DTDs that will be used in case the + * remote copies of these DTDs cannot be accessed. The key for the Map + * is the DTDs public ID and the value is the local location for the DTD + */ + private Map resourceDtds = new HashMap(); + private Map fileDtds = new HashMap(); + + private Map ejbs = new HashMap();// One item within the Map + private boolean iasDescriptor = false;// Is doc iAS or EJB descriptor + + private String currentLoc = "";// List of EJBs found in XML + private EjbInfo currentEjb;// Tracks current element + private String currentText;// Tracks current text data + private String ejbType;// "session" or "entity" + + /** + * Constructs a new instance of the handler and registers local copies + * of the standard EJB 1.1 descriptor DTD as well as iAS's EJB + * descriptor DTD. + */ + public EjbcHandler() + { + final String PUBLICID_EJB11 = + "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + final String PUBLICID_IPLANET_EJB_60 = + "-//Sun Microsystems, Inc.//DTD iAS Enterprise JavaBeans 1.0//EN"; + + final String DEFAULT_IAS60_EJB11_DTD_LOCATION = + "ejb-jar_1_1.dtd"; + final String DEFAULT_IAS60_DTD_LOCATION = + "IASEjb_jar_1_0.dtd"; + + registerDTD( PUBLICID_EJB11, DEFAULT_IAS60_EJB11_DTD_LOCATION ); + registerDTD( PUBLICID_IPLANET_EJB_60, DEFAULT_IAS60_DTD_LOCATION ); + } + + /** + * Returns the value of the display-name element found in the standard + * EJB 1.1 descriptor. + * + * @return String display-name value. + */ + public String getDisplayName() + { + return displayName; + } + + /** + * Returns the list of EJB objects found during the processing of the + * standard EJB 1.1 descriptor and iAS-specific EJB descriptor. + * + * @return An array of EJBs which were found during the descriptor + * parsing. + */ + public EjbInfo[] getEjbs() + { + return ( EjbInfo[] )ejbs.values().toArray( new EjbInfo[ejbs.size()] ); + } + + /** + * Receive notification that character data has been found in the XML + * document + * + * @param ch Array of characters which have been found in the document. + * @param start Starting index of the data found in the document. + * @param len The number of characters found in the document. + * @throws SAXException If the parser cannot process the document. + */ + public void characters( char[] ch, int start, int len ) + throws SAXException + { + + currentText += new String( ch ).substring( start, start + len ); + } + + /** + * Receive notification that the end of an XML element has been found. + * + * @param name String name of the element. + * @throws SAXException If the parser cannot process the document. + */ + public void endElement( String name ) + throws SAXException + { + + /* + * If this is a standard EJB 1.1 descriptor, we are looking for one + * set of data, while if this is an iAS-specific descriptor, we're + * looking for different set of data. Hand the processing off to + * the appropriate method. + */ + if( iasDescriptor ) + { + iasCharacters( currentText ); + } + else + { + stdCharacters( currentText ); + } + + /* + * I need to "pop" the element off the String (currentLoc) which + * always represents my current location in the XML document. + */ + int nameLength = name.length() + 1;// Add one for the "\" + int locLength = currentLoc.length(); + + currentLoc = currentLoc.substring( 0, locLength - nameLength ); + } + + /** + * Registers a local DTD that will be used when parsing an EJB + * descriptor. When the DTD's public identifier is found in an XML + * document, the parser will reference the local DTD rather than the + * remote DTD. This enables XML documents to be processed even when the + * public DTD isn't available. + * + * @param publicID The DTD's public identifier. + * @param location The location of the local DTD copy -- the location + * may either be a resource found on the classpath or a local file. + */ + public void registerDTD( String publicID, String location ) + { + log( "Registering: " + location ); + if( ( publicID == null ) || ( location == null ) ) + { + return; + } + + if( ClassLoader.getSystemResource( location ) != null ) + { + log( "Found resource: " + location ); + resourceDtds.put( publicID, location ); + } + else + { + File dtdFile = new File( location ); + if( dtdFile.exists() && dtdFile.isFile() ) + { + log( "Found file: " + location ); + fileDtds.put( publicID, location ); + } + } + } + + /** + * Resolves an external entity found during XML processing. If a public + * ID is found that has been registered with the handler, an + * InputSource will be returned which refers to the local copy. + * If the public ID hasn't been registered or if an error occurs, the + * superclass implementation is used. + * + * @param publicId The DTD's public identifier. + * @param systemId The location of the DTD, as found in the XML + * document. + * @return Description of the Returned Value + * @exception SAXException Description of Exception + */ + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + InputStream inputStream = null; + + try + { + + /* + * Search the resource Map and (if not found) file Map + */ + String location = ( String )resourceDtds.get( publicId ); + if( location != null ) + { + inputStream + = ClassLoader.getSystemResource( location ).openStream(); + } + else + { + location = ( String )fileDtds.get( publicId ); + if( location != null ) + { + inputStream = new FileInputStream( location ); + } + } + } + catch( IOException e ) + { + return super.resolveEntity( publicId, systemId ); + } + + if( inputStream == null ) + { + return super.resolveEntity( publicId, systemId ); + } + else + { + return new InputSource( inputStream ); + } + } + + /** + * Receive notification that the start of an XML element has been found. + * + * @param name String name of the element found. + * @param atts AttributeList of the attributes included with the element + * (if any). + * @throws SAXException If the parser cannot process the document. + */ + public void startElement( String name, AttributeList atts ) + throws SAXException + { + + /* + * I need to "push" the element onto the String (currentLoc) which + * always represents the current location in the XML document. + */ + currentLoc += "\\" + name; + + /* + * A new element has started, so reset the text being captured + */ + currentText = ""; + + if( currentLoc.equals( "\\ejb-jar" ) ) + { + iasDescriptor = false; + } + else if( currentLoc.equals( "\\ias-ejb-jar" ) ) + { + iasDescriptor = true; + } + + if( ( name.equals( "session" ) ) || ( name.equals( "entity" ) ) ) + { + ejbType = name; + } + } + + /** + * Receive notification that character data has been found in an + * iAS-specific descriptor. We're interested in retrieving data + * indicating whether the bean must support RMI/IIOP access, whether the + * bean must provide highly available stubs and skeletons (in the case + * of stateful session beans), and if this bean uses additional CMP XML + * descriptors (in the case of entity beans with CMP). + * + * @param value String data found in the XML document. + */ + private void iasCharacters( String value ) + { + String base = "\\ias-ejb-jar\\enterprise-beans\\" + ejbType; + + if( currentLoc.equals( base + "\\ejb-name" ) ) + { + currentEjb = ( EjbInfo )ejbs.get( value ); + if( currentEjb == null ) + { + currentEjb = new EjbInfo( value ); + ejbs.put( value, currentEjb ); + } + } + else if( currentLoc.equals( base + "\\iiop" ) ) + { + currentEjb.setIiop( value ); + } + else if( currentLoc.equals( base + "\\failover-required" ) ) + { + currentEjb.setHasession( value ); + } + else if( currentLoc.equals( base + "\\persistence-manager" + + "\\properties-file-location" ) ) + { + currentEjb.addCmpDescriptor( value ); + } + } + + /** + * Receive notification that character data has been found in a standard + * EJB 1.1 descriptor. We're interested in retrieving the home + * interface, remote interface, implementation class, the type of bean, + * and if the bean uses CMP. + * + * @param value String data found in the XML document. + */ + private void stdCharacters( String value ) + { + + if( currentLoc.equals( "\\ejb-jar\\display-name" ) ) + { + displayName = value; + return; + } + + String base = "\\ejb-jar\\enterprise-beans\\" + ejbType; + + if( currentLoc.equals( base + "\\ejb-name" ) ) + { + currentEjb = ( EjbInfo )ejbs.get( value ); + if( currentEjb == null ) + { + currentEjb = new EjbInfo( value ); + ejbs.put( value, currentEjb ); + } + } + else if( currentLoc.equals( base + "\\home" ) ) + { + currentEjb.setHome( value ); + } + else if( currentLoc.equals( base + "\\remote" ) ) + { + currentEjb.setRemote( value ); + } + else if( currentLoc.equals( base + "\\ejb-class" ) ) + { + currentEjb.setImplementation( value ); + } + else if( currentLoc.equals( base + "\\session-type" ) ) + { + currentEjb.setBeantype( value ); + } + else if( currentLoc.equals( base + "\\persistence-type" ) ) + { + currentEjb.setCmp( value ); + } + } + }// End of Classname inner class + + + /** + * Thread class used to redirect output from an InputStream to + * the JRE standard output. This class may be used to redirect output from + * an external process to the standard output. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class RedirectOutput extends Thread + { + InputStream stream;// Stream to read and redirect to standard output + + /** + * Constructs a new instance that will redirect output from the + * specified stream to the standard output. + * + * @param stream InputStream which will be read and redirected to the + * standard output. + */ + public RedirectOutput( InputStream stream ) + { + this.stream = stream; + } + + /** + * Reads text from the input stream and redirects it to standard output + * using a separate thread. + */ + public void run() + { + BufferedReader reader = new BufferedReader( + new InputStreamReader( stream ) ); + String text; + try + { + while( ( text = reader.readLine() ) != null ) + { + System.out.println( text ); + } + } + catch( IOException e ) + { + e.printStackTrace(); + } + finally + { + try + { + reader.close(); + } + catch( IOException e ) + { + // Do nothing + } + } + } + }// End of RedirectOutput inner class + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java new file mode 100644 index 000000000..ce5f22396 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.IOException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.xml.sax.SAXException; + +/** + * Task to compile EJB stubs and skeletons for the iPlanet Application Server. + * The EJBs to be processed are specified by the EJB 1.1 standard XML + * descriptor, and additional attributes are obtained from the iPlanet + * Application Server-specific XML descriptor. Since the XML descriptors can + * include multiple EJBs, this is a convenient way of specifying many EJBs in a + * single Ant task. The following attributes are allowed: + *

                + *
              • ejbdescriptor -- Standard EJB 1.1 XML descriptor (typically + * titled "ejb-jar.xml"). This attribute is required. + *
              • iasdescriptor -- EJB XML descriptor for iPlanet Application + * Server (typically titled "ias-ejb-jar.xml). This attribute is required. + * + *
              • dest -- The is the base directory where the RMI stubs and + * skeletons are written. In addition, the class files for each bean (home + * interface, remote interface, and EJB implementation) must be found in this + * directory. This attribute is required. + *
              • classpath -- The classpath used when generating EJB stubs and + * skeletons. This is an optional attribute (if omitted, the classpath + * specified when Ant was started will be used). Nested "classpath" elements + * may also be used. + *
              • keepgenerated -- Indicates whether or not the Java source files + * which are generated by ejbc will be saved or automatically deleted. If + * "yes", the source files will be retained. This is an optional attribute (if + * omitted, it defaults to "no"). + *
              • debug -- Indicates whether or not the ejbc utility should log + * additional debugging statements to the standard output. If "yes", the + * additional debugging statements will be generated (if omitted, it defaults + * to "no"). + *
              • iashome -- May be used to specify the "home" directory for this + * iPlanet Application Server installation. This is used to find the ejbc + * utility if it isn't included in the user's system path. This is an optional + * attribute (if specified, it should refer to the [install-location]/iplanet/ias6/ias + * directory). If omitted, the ejbc utility + * must be on the user's system path. + *
              + *

              + * + * For each EJB specified, this task will locate the three classes that comprise + * the EJB. If these class files cannot be located in the dest + * directory, the task will fail. The task will also attempt to locate the EJB + * stubs and skeletons in this directory. If found, the timestamps on the stubs + * and skeletons will be checked to ensure they are up to date. Only if these + * files cannot be found or if they are out of date will ejbc be called to + * generate new stubs and skeletons. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetEjbc + */ +public class IPlanetEjbcTask extends Task +{ + private boolean keepgenerated = false; + private boolean debug = false; + private Path classpath; + private File dest; + + /* + * Attributes set by the Ant build file + */ + private File ejbdescriptor; + private File iasdescriptor; + private File iashome; + + /** + * Sets the classpath to be used when compiling the EJB stubs and skeletons. + * + * @param classpath The classpath to be used. + */ + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debug A boolean indicating if debugging output should be generated + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Sets the destination directory where the EJB "source" classes must exist + * and where the stubs and skeletons will be written. The destination + * directory must exist before this task is executed. + * + * @param dest The directory where the compiled classes will be written. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Sets the location of the standard XML EJB descriptor. Typically, this + * file is named "ejb-jar.xml". + * + * @param ejbdescriptor The name and location of the EJB descriptor. + */ + public void setEjbdescriptor( File ejbdescriptor ) + { + this.ejbdescriptor = ejbdescriptor; + } + + /** + * Sets the location of the iAS-specific XML EJB descriptor. Typically, this + * file is named "ias-ejb-jar.xml". + * + * @param iasdescriptor The name and location of the iAS-specific EJB + * descriptor. + */ + public void setIasdescriptor( File iasdescriptor ) + { + this.iasdescriptor = iasdescriptor; + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iashome The home directory for the user's iAS installation. + */ + public void setIashome( File iashome ) + { + this.iashome = iashome; + } + + /** + * Sets whether or not the Java source files which are generated by the ejbc + * process should be retained or automatically deleted. + * + * @param keepgenerated A boolean indicating if the Java source files for + * the stubs and skeletons should be retained. + */ + public void setKeepgenerated( boolean keepgenerated ) + { + this.keepgenerated = keepgenerated; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Does the work. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + checkConfiguration(); + + executeEjbc( getParser() ); + } + + /** + * Returns the CLASSPATH to be used when calling EJBc. If no user CLASSPATH + * is specified, the System classpath is returned instead. + * + * @return Path The classpath to be used for EJBc. + */ + private Path getClasspath() + { + if( classpath == null ) + { + classpath = Path.systemClasspath; + } + + return classpath; + } + + /** + * Returns a SAXParser that may be used to process the XML descriptors. + * + * @return Parser which may be used to process the EJB descriptors. + * @throws BuildException If the parser cannot be created or configured. + */ + private SAXParser getParser() + throws BuildException + { + + SAXParser saxParser = null; + try + { + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + saxParser = saxParserFactory.newSAXParser(); + } + catch( SAXException e ) + { + String msg = "Unable to create a SAXParser: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( ParserConfigurationException e ) + { + String msg = "Unable to create a SAXParser: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + + return saxParser; + } + + /** + * Verifies that the user selections are valid. + * + * @throws BuildException If the user selections are invalid. + */ + private void checkConfiguration() + throws BuildException + { + + if( ejbdescriptor == null ) + { + String msg = "The standard EJB descriptor must be specified using " + + "the \"ejbdescriptor\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !ejbdescriptor.exists() ) || ( !ejbdescriptor.isFile() ) ) + { + String msg = "The standard EJB descriptor (" + ejbdescriptor + + ") was not found or isn't a file."; + throw new BuildException( msg, location ); + } + + if( iasdescriptor == null ) + { + String msg = "The iAS-speific XML descriptor must be specified using" + + " the \"iasdescriptor\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !iasdescriptor.exists() ) || ( !iasdescriptor.isFile() ) ) + { + String msg = "The iAS-specific XML descriptor (" + iasdescriptor + + ") was not found or isn't a file."; + throw new BuildException( msg, location ); + } + + if( dest == null ) + { + String msg = "The destination directory must be specified using " + + "the \"dest\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !dest.exists() ) || ( !dest.isDirectory() ) ) + { + String msg = "The destination directory (" + dest + ") was not " + + "found or isn't a directory."; + throw new BuildException( msg, location ); + } + + if( ( iashome != null ) && ( !iashome.isDirectory() ) ) + { + String msg = "If \"iashome\" is specified, it must be a valid " + + "directory (it was set to " + iashome + ")."; + throw new BuildException( msg, getLocation() ); + } + } + + /** + * Executes the EJBc utility using the SAXParser provided. + * + * @param saxParser SAXParser that may be used to process the EJB + * descriptors + * @throws BuildException If there is an error reading or parsing the XML + * descriptors + */ + private void executeEjbc( SAXParser saxParser ) + throws BuildException + { + IPlanetEjbc ejbc = new IPlanetEjbc( ejbdescriptor, + iasdescriptor, + dest, + getClasspath().toString(), + saxParser ); + ejbc.setRetainSource( keepgenerated ); + ejbc.setDebugOutput( debug ); + if( iashome != null ) + { + ejbc.setIasHomeDir( iashome ); + } + + try + { + ejbc.execute(); + } + catch( IOException e ) + { + String msg = "An IOException occurred while trying to read the XML " + + "descriptor file: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( SAXException e ) + { + String msg = "A SAXException occurred while trying to read the XML " + + "descriptor file: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( IPlanetEjbc.EjbcException e ) + { + String msg = "An exception occurred while trying to run the ejbc " + + "utility: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java new file mode 100644 index 000000000..a5a7d70f0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FilenameFilter; + +public class InnerClassFilenameFilter implements FilenameFilter +{ + private String baseClassName; + + InnerClassFilenameFilter( String baseclass ) + { + int extidx = baseclass.lastIndexOf( ".class" ); + if( extidx == -1 ) + { + extidx = baseclass.length() - 1; + } + baseClassName = baseclass.substring( 0, extidx ); + } + + public boolean accept( File Dir, String filename ) + { + if( ( filename.lastIndexOf( "." ) != filename.lastIndexOf( ".class" ) ) + || ( filename.indexOf( baseClassName + "$" ) != 0 ) ) + { + return false; + } + return true; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java new file mode 100644 index 000000000..06fd4ad05 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.util.Hashtable; +import org.apache.tools.ant.Project; + +/** + * The deployment tool to add the jboss specific deployment descriptor to the + * ejb jar file. Jboss only requires one additional file jboss.xml and does not + * require any additional compilation. + * + * @author Paul Austin + * @version 1.0 + * @see EjbJar#createJboss + */ +public class JbossDeploymentTool extends GenericDeploymentTool +{ + protected final static String JBOSS_DD = "jboss.xml"; + protected final static String JBOSS_CMPD = "jaws.xml"; + + /** + * Instance variable that stores the suffix for the jboss jarfile. + */ + private String jarSuffix = ".jar"; + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + File jbossDD = new File( getConfig().descriptorDir, ddPrefix + JBOSS_DD ); + if( jbossDD.exists() ) + { + ejbFiles.put( META_DIR + JBOSS_DD, jbossDD ); + } + else + { + log( "Unable to locate jboss deployment descriptor. It was expected to be in " + jbossDD.getPath(), Project.MSG_WARN ); + return; + } + + File jbossCMPD = new File( getConfig().descriptorDir, ddPrefix + JBOSS_CMPD ); + if( jbossCMPD.exists() ) + { + ejbFiles.put( META_DIR + JBOSS_CMPD, jbossCMPD ); + } + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java new file mode 100644 index 000000000..e4d43714a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java @@ -0,0 +1,424 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; + +/** + * Execute a Weblogic server. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class WLRun extends Task +{ + protected final static String DEFAULT_WL51_POLICY_FILE = "weblogic.policy"; + protected final static String DEFAULT_WL60_POLICY_FILE = "lib/weblogic.policy"; + protected final static String DEFAULT_PROPERTIES_FILE = "weblogic.properties"; + + private String weblogicMainClass = "weblogic.Server"; + + /** + * Addional arguments to pass to the JVM used to run weblogic + */ + private String additionalArgs = ""; + + /** + * The name of the weblogic server - used to select the server's directory + * in the weblogic home directory. + */ + private String weblogicSystemName = "myserver"; + + /** + * The file containing the weblogic properties for this server. + */ + private String weblogicPropertiesFile = null; + + /** + * additional args to pass to the spawned jvm + */ + private String additionalJvmArgs = ""; + + /** + * The location of the BEA Home under which this server is run. WL6 only + */ + private File beaHome = null; + + /** + * The management username + */ + private String managementUsername = "system"; + + /** + * The management password + */ + private String managementPassword = null; + + /** + * The provate key password - used for SSL + */ + private String pkPassword = null; + + /** + * The classpath to be used when running the Java VM. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private Path classpath; + + /** + * The security policy to use when running the weblogic server + */ + private String securityPolicy; + + /** + * The weblogic classpath to the be used when running weblogic. + */ + private Path weblogicClasspath; + + /** + * The weblogic domain + */ + private String weblogicDomainName; + + /** + * The weblogic system home directory + */ + private File weblogicSystemHome; + + public void setArgs( String args ) + { + additionalArgs = args; + } + + /** + * The location of the BEA Home. + * + * @param beaHome the BEA Home directory. + */ + public void setBEAHome( File beaHome ) + { + this.beaHome = beaHome; + } + + + /** + * Set the classpath to be used for this execution. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + this.classpath = classpath; + } + + /** + * Set the Domain to run in + * + * @param domain the domain + */ + public void setDomain( String domain ) + { + this.weblogicDomainName = domain; + } + + /** + * The location where weblogic lives. + * + * @param weblogicHome the home directory of weblogic. + */ + public void setHome( File weblogicHome ) + { + weblogicSystemHome = weblogicHome; + } + + /** + * Set the additional arguments to pass to the weblogic JVM + * + * @param args the arguments to be passed to the JVM + */ + public void setJvmargs( String args ) + { + this.additionalJvmArgs = args; + } + + /** + * Set the name of the server to run + * + * @param serverName The new Name value + */ + public void setName( String serverName ) + { + this.weblogicSystemName = serverName; + } + + /** + * Set the private key password so the server can decrypt the SSL private + * key file. + * + * @param pkpassword the private key password, + */ + public void setPKPassword( String pkpassword ) + { + this.pkPassword = pkpassword; + } + + + /** + * Set the management password of the server + * + * @param password the management pasword of the server. + */ + public void setPassword( String password ) + { + this.managementPassword = password; + } + + /** + * Set the security policy for this invocation of weblogic. + * + * @param securityPolicy the security policy to use. + */ + public void setPolicy( String securityPolicy ) + { + this.securityPolicy = securityPolicy; + } + + /** + * Set the properties file to use. The location of the properties file is + * relative to the weblogi system home + * + * @param propertiesFilename the properties file name + */ + public void setProperties( String propertiesFilename ) + { + this.weblogicPropertiesFile = propertiesFilename; + } + + /** + * Set the management username to run the server + * + * @param username the management username of the server. + */ + public void setUsername( String username ) + { + this.managementUsername = username; + } + + + public void setWeblogicMainClass( String c ) + { + weblogicMainClass = c; + } + + /** + * Set the weblogic classpath. The weblogic classpath is used by weblogic to + * support dynamic class loading. + * + * @param weblogicClasspath the weblogic classpath + */ + public void setWlclasspath( Path weblogicClasspath ) + { + this.weblogicClasspath = weblogicClasspath; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Get the classpath to the weblogic classpaths + * + * @return Description of the Returned Value + */ + public Path createWLClasspath() + { + if( weblogicClasspath == null ) + { + weblogicClasspath = new Path( project ); + } + return weblogicClasspath.createPath(); + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a helper task. This approach allows the classpath of the helper task to + * be set. Since the weblogic tools require the class files of the project's + * home and remote interfaces to be available in the classpath, this also + * avoids having to start ant with the class path of the project it is + * building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( weblogicSystemHome == null ) + { + throw new BuildException( "weblogic home must be set" ); + } + if( !weblogicSystemHome.isDirectory() ) + { + throw new BuildException( "weblogic home directory " + weblogicSystemHome.getPath() + + " is not valid" ); + } + + if( beaHome != null ) + { + executeWLS6(); + } + else + { + executeWLS(); + } + } + + private void executeWLS() + { + File securityPolicyFile = findSecurityPolicyFile( DEFAULT_WL51_POLICY_FILE ); + File propertiesFile = null; + + if( weblogicPropertiesFile == null ) + { + weblogicPropertiesFile = DEFAULT_PROPERTIES_FILE; + } + propertiesFile = new File( weblogicSystemHome, weblogicPropertiesFile ); + if( !propertiesFile.exists() ) + { + // OK, properties file may be absolute + propertiesFile = project.resolveFile( weblogicPropertiesFile ); + if( !propertiesFile.exists() ) + { + throw new BuildException( "Properties file " + weblogicPropertiesFile + + " not found in weblogic home " + weblogicSystemHome + + " or as absolute file" ); + } + } + + Java weblogicServer = ( Java )project.createTask( "java" ); + weblogicServer.setTaskName( getTaskName() ); + weblogicServer.setFork( true ); + weblogicServer.setClassname( weblogicMainClass ); + + String jvmArgs = additionalJvmArgs; + + if( weblogicClasspath != null ) + { + jvmArgs += " -Dweblogic.class.path=" + weblogicClasspath; + } + + jvmArgs += " -Djava.security.manager -Djava.security.policy==" + securityPolicyFile; + jvmArgs += " -Dweblogic.system.home=" + weblogicSystemHome; + jvmArgs += " -Dweblogic.system.name=" + weblogicSystemName; + jvmArgs += " -Dweblogic.system.propertiesFile=" + weblogicPropertiesFile; + + weblogicServer.createJvmarg().setLine( jvmArgs ); + weblogicServer.createArg().setLine( additionalArgs ); + + if( classpath != null ) + { + weblogicServer.setClasspath( classpath ); + } + if( weblogicServer.executeJava() != 0 ) + { + throw new BuildException( "Execution of weblogic server failed" ); + } + } + + private void executeWLS6() + { + File securityPolicyFile = findSecurityPolicyFile( DEFAULT_WL60_POLICY_FILE ); + if( !beaHome.isDirectory() ) + { + throw new BuildException( "BEA home " + beaHome.getPath() + + " is not valid" ); + } + + File configFile = new File( weblogicSystemHome, "config/" + weblogicDomainName + "/config.xml" ); + if( !configFile.exists() ) + { + throw new BuildException( "Server config file " + configFile + " not found." ); + } + + if( managementPassword == null ) + { + throw new BuildException( "You must supply a management password to start the server" ); + } + + Java weblogicServer = ( Java )project.createTask( "java" ); + weblogicServer.setTaskName( getTaskName() ); + weblogicServer.setFork( true ); + weblogicServer.setDir( weblogicSystemHome ); + weblogicServer.setClassname( weblogicMainClass ); + + String jvmArgs = additionalJvmArgs; + + jvmArgs += " -Dweblogic.Domain=" + weblogicDomainName; + jvmArgs += " -Dweblogic.Name=" + weblogicSystemName; + jvmArgs += " -Dweblogic.system.home=" + weblogicSystemHome; + + jvmArgs += " -Dbea.home=" + beaHome; + jvmArgs += " -Djava.security.policy==" + securityPolicyFile; + + jvmArgs += " -Dweblogic.management.username=" + managementUsername; + jvmArgs += " -Dweblogic.management.password=" + managementPassword; + if( pkPassword != null ) + { + jvmArgs += " -Dweblogic.pkpassword=" + pkPassword; + } + + weblogicServer.createJvmarg().setLine( jvmArgs ); + weblogicServer.createArg().setLine( additionalArgs ); + + if( classpath != null ) + { + weblogicServer.setClasspath( classpath ); + } + + if( weblogicServer.executeJava() != 0 ) + { + throw new BuildException( "Execution of weblogic server failed" ); + } + } + + private File findSecurityPolicyFile( String defaultSecurityPolicy ) + { + String securityPolicy = this.securityPolicy; + if( securityPolicy == null ) + { + securityPolicy = defaultSecurityPolicy; + } + File securityPolicyFile = new File( weblogicSystemHome, securityPolicy ); + // If an explicit securityPolicy file was specified, it maybe an + // absolute path. Use the project to resolve it. + if( this.securityPolicy != null && !securityPolicyFile.exists() ) + { + securityPolicyFile = project.resolveFile( securityPolicy ); + } + // If we still can't find it, complain + if( !securityPolicyFile.exists() ) + { + throw new BuildException( "Security policy " + securityPolicy + + " was not found." ); + } + return securityPolicyFile; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java new file mode 100644 index 000000000..076e057ed --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; + +/** + * Shutdown a Weblogic server. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class WLStop extends Task +{ + + /** + * The delay (in seconds) to wait before shutting down. + */ + private int delay = 0; + + /** + * The location of the BEA Home under which this server is run. WL6 only + */ + private File beaHome = null; + /** + * The classpath to be used. It must contains the weblogic.Admin class. + */ + private Path classpath; + + /** + * The password to use to shutdown the weblogic server. + */ + private String password; + + /** + * The URL which the weblogic server is listening on. + */ + private String serverURL; + + /** + * The weblogic username to use to request the shutdown. + */ + private String username; + + /** + * The location of the BEA Home. + * + * @param beaHome the BEA Home directory. + */ + public void setBEAHome( File beaHome ) + { + this.beaHome = beaHome; + } + + /** + * Set the classpath to be used for this compilation. + * + * @param path The new Classpath value + */ + public void setClasspath( Path path ) + { + this.classpath = path; + } + + + /** + * Set the delay (in seconds) before shutting down the server. + * + * @param s the selay. + */ + public void setDelay( String s ) + { + delay = Integer.parseInt( s ); + } + + /** + * Set the password to use to request shutdown of the server. + * + * @param s the password. + */ + public void setPassword( String s ) + { + this.password = s; + } + + /** + * Set the URL to which the weblogic server is listening. + * + * @param s the url. + */ + public void setUrl( String s ) + { + this.serverURL = s; + } + + /** + * Set the username to use to request shutdown of the server. + * + * @param s the username. + */ + public void setUser( String s ) + { + this.username = s; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * the weblogic admin task This approach allows the classpath of the helper + * task to be set. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( username == null || password == null ) + { + throw new BuildException( "weblogic username and password must both be set" ); + } + + if( serverURL == null ) + { + throw new BuildException( "The url of the weblogic server must be provided." ); + } + + Java weblogicAdmin = ( Java )project.createTask( "java" ); + weblogicAdmin.setFork( true ); + weblogicAdmin.setClassname( "weblogic.Admin" ); + String args; + + if( beaHome == null ) + { + args = serverURL + " SHUTDOWN " + username + " " + password + " " + delay; + } + else + { + args = " -url " + serverURL + + " -username " + username + + " -password " + password + + " SHUTDOWN " + " " + delay; + } + + weblogicAdmin.setArgs( args ); + weblogicAdmin.setClasspath( classpath ); + weblogicAdmin.execute(); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java new file mode 100644 index 000000000..cc7d965ad --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java @@ -0,0 +1,852 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; +import org.xml.sax.InputSource; + +public class WeblogicDeploymentTool extends GenericDeploymentTool +{ + public final static String PUBLICID_EJB11 + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + public final static String PUBLICID_EJB20 + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"; + public final static String PUBLICID_WEBLOGIC_EJB510 + = "-//BEA Systems, Inc.//DTD WebLogic 5.1.0 EJB//EN"; + public final static String PUBLICID_WEBLOGIC_EJB600 + = "-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN"; + + protected final static String DEFAULT_WL51_EJB11_DTD_LOCATION + = "/weblogic/ejb/deployment/xml/ejb-jar.dtd"; + protected final static String DEFAULT_WL60_EJB11_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/ejb11-jar.dtd"; + protected final static String DEFAULT_WL60_EJB20_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/ejb20-jar.dtd"; + + protected final static String DEFAULT_WL51_DTD_LOCATION + = "/weblogic/ejb/deployment/xml/weblogic-ejb-jar.dtd"; + protected final static String DEFAULT_WL60_51_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/weblogic510-ejb-jar.dtd"; + protected final static String DEFAULT_WL60_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/weblogic600-ejb-jar.dtd"; + + protected final static String DEFAULT_COMPILER = "default"; + + protected final static String WL_DD = "weblogic-ejb-jar.xml"; + protected final static String WL_CMP_DD = "weblogic-cmp-rdbms-jar.xml"; + + protected final static String COMPILER_EJB11 = "weblogic.ejbc"; + protected final static String COMPILER_EJB20 = "weblogic.ejbc20"; + + /** + * Instance variable that stores the suffix for the weblogic jarfile. + */ + private String jarSuffix = ".jar"; + + /** + * Instance variable that determines whether generic ejb jars are kept. + */ + private boolean keepgenerated = false; + + /** + * Instance variable that stores the fully qualified classname of the + * weblogic EJBC compiler + */ + private String ejbcClass = null; + + private String additionalArgs = ""; + + private boolean keepGeneric = false; + + private String compiler = null; + + private boolean alwaysRebuild = true; + + /** + * controls whether ejbc is run on the generated jar + */ + private boolean noEJBC = false; + + /** + * Indicates if the old CMP location convention is to be used. + */ + private boolean newCMP = false; + + /** + * The classpath to the weblogic classes. + */ + private Path wlClasspath = null; + + /** + * The weblogic.StdoutSeverityLevel to use when running the JVM that + * executes ejbc. Set to 16 to avoid the warnings about EJB Home and Remotes + * being in the classpath + */ + private Integer jvmDebugLevel = null; + + /** + * Instance variable that stores the location of the ejb 1.1 DTD file. + */ + private String ejb11DTD; + + /** + * Instance variable that stores the location of the weblogic DTD file. + */ + private String weblogicDTD; + + /** + * sets some additional args to send to ejbc. + * + * @param args The new Args value + */ + public void setArgs( String args ) + { + this.additionalArgs = args; + } + + /** + * The compiler (switch -compiler) to use + * + * @param compiler The new Compiler value + */ + public void setCompiler( String compiler ) + { + this.compiler = compiler; + } + + /** + * Setter used to store the location of the Sun's Generic EJB DTD. This can + * be a file on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setEJBdtd( String inString ) + { + this.ejb11DTD = inString; + } + + /** + * Set the classname of the ejbc compiler + * + * @param ejbcClass The new EjbcClass value + */ + public void setEjbcClass( String ejbcClass ) + { + this.ejbcClass = ejbcClass; + } + + /** + * Sets the weblogic.StdoutSeverityLevel to use when running the JVM that + * executes ejbc. Set to 16 to avoid the warnings about EJB Home and Remotes + * being in the classpath + * + * @param jvmDebugLevel The new JvmDebugLevel value + */ + public void setJvmDebugLevel( Integer jvmDebugLevel ) + { + this.jvmDebugLevel = jvmDebugLevel; + } + + /** + * Sets whether -keepgenerated is passed to ejbc (that is, the .java source + * files are kept). + * + * @param inValue either 'true' or 'false' + */ + public void setKeepgenerated( String inValue ) + { + this.keepgenerated = Boolean.valueOf( inValue ).booleanValue(); + } + + /** + * Setter used to store the value of keepGeneric + * + * @param inValue a string, either 'true' or 'false'. + */ + public void setKeepgeneric( boolean inValue ) + { + this.keepGeneric = inValue; + } + + /** + * Set the value of the newCMP scheme. The old CMP scheme locates the + * weblogic CMP descriptor based on the naming convention where the weblogic + * CMP file is expected to be named with the bean name as the prefix. Under + * this scheme the name of the CMP descriptor does not match the name + * actually used in the main weblogic EJB descriptor. Also, descriptors + * which contain multiple CMP references could not be used. + * + * @param newCMP The new NewCMP value + */ + public void setNewCMP( boolean newCMP ) + { + this.newCMP = newCMP; + } + + /** + * Do not EJBC the jar after it has been put together. + * + * @param noEJBC The new NoEJBC value + */ + public void setNoEJBC( boolean noEJBC ) + { + this.noEJBC = noEJBC; + } + + /** + * Set the value of the oldCMP scheme. This is an antonym for newCMP + * + * @param oldCMP The new OldCMP value + */ + public void setOldCMP( boolean oldCMP ) + { + this.newCMP = !oldCMP; + } + + /** + * Set the rebuild flag to false to only update changes in the jar rather + * than rerunning ejbc + * + * @param rebuild The new Rebuild value + */ + public void setRebuild( boolean rebuild ) + { + this.alwaysRebuild = rebuild; + } + + + /** + * Setter used to store the suffix for the generated weblogic jar file. + * + * @param inString the string to use as the suffix. + */ + public void setSuffix( String inString ) + { + this.jarSuffix = inString; + } + + public void setWLClasspath( Path wlClasspath ) + { + this.wlClasspath = wlClasspath; + } + + /** + * Setter used to store the location of the weblogic DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setWLdtd( String inString ) + { + this.weblogicDTD = inString; + } + + + /** + * Setter used to store the location of the ejb-jar DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setWeblogicdtd( String inString ) + { + setEJBdtd( inString ); + } + + /** + * Get the ejbc compiler class + * + * @return The EjbcClass value + */ + public String getEjbcClass() + { + return ejbcClass; + } + + public Integer getJvmDebugLevel() + { + return jvmDebugLevel; + } + + /** + * Get the classpath to the weblogic classpaths + * + * @return Description of the Returned Value + */ + public Path createWLClasspath() + { + if( wlClasspath == null ) + { + wlClasspath = new Path( getTask().getProject() ); + } + return wlClasspath.createPath(); + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + public void validateConfigured() + throws BuildException + { + super.validateConfigured(); + } + + /** + * Helper method invoked by isRebuildRequired to get a ClassLoader for a Jar + * File passed to it. + * + * @param classjar java.io.File representing jar file to get classes from. + * @return The ClassLoaderFromJar value + * @exception IOException Description of Exception + */ + protected ClassLoader getClassLoaderFromJar( File classjar ) + throws IOException + { + Path lookupPath = new Path( getTask().getProject() ); + lookupPath.setLocation( classjar ); + + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + lookupPath.append( classpath ); + } + + return new AntClassLoader( getTask().getProject(), lookupPath ); + } + + protected DescriptorHandler getWeblogicDescriptorHandler( final File srcDir ) + { + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + protected void processElement() + { + if( currentElement.equals( "type-storage" ) ) + { + // Get the filename of vendor specific descriptor + String fileNameWithMETA = currentText; + //trim the META_INF\ off of the file name + String fileName = fileNameWithMETA.substring( META_DIR.length(), + fileNameWithMETA.length() ); + File descriptorFile = new File( srcDir, fileName ); + + ejbFiles.put( fileNameWithMETA, descriptorFile ); + } + } + }; + + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, DEFAULT_WL51_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, DEFAULT_WL60_51_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB600, DEFAULT_WL60_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, weblogicDTD ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB600, weblogicDTD ); + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + + /** + * Helper method to check to see if a weblogic EBJ1.1 jar needs to be + * rebuilt using ejbc. Called from writeJar it sees if the "Bean" classes + * are the only thing that needs to be updated and either updates the Jar + * with the Bean classfile or returns true, saying that the whole weblogic + * jar needs to be regened with ejbc. This allows faster build times for + * working developers.

              + * + * The way weblogic ejbc works is it creates wrappers for the publicly + * defined methods as they are exposed in the remote interface. If the + * actual bean changes without changing the the method signatures then only + * the bean classfile needs to be updated and the rest of the weblogic jar + * file can remain the same. If the Interfaces, ie. the method signatures + * change or if the xml deployment dicriptors changed, the whole jar needs + * to be rebuilt with ejbc. This is not strictly true for the xml files. If + * the JNDI name changes then the jar doesnt have to be rebuild, but if the + * resources references change then it does. At this point the weblogic jar + * gets rebuilt if the xml files change at all. + * + * @param genericJarFile java.io.File The generic jar file. + * @param weblogicJarFile java.io.File The weblogic jar file to check to see + * if it needs to be rebuilt. + * @return The RebuildRequired value + */ + protected boolean isRebuildRequired( File genericJarFile, File weblogicJarFile ) + { + boolean rebuild = false; + + JarFile genericJar = null; + JarFile wlJar = null; + File newWLJarFile = null; + JarOutputStream newJarStream = null; + + try + { + log( "Checking if weblogic Jar needs to be rebuilt for jar " + weblogicJarFile.getName(), + Project.MSG_VERBOSE ); + // Only go forward if the generic and the weblogic file both exist + if( genericJarFile.exists() && genericJarFile.isFile() + && weblogicJarFile.exists() && weblogicJarFile.isFile() ) + { + //open jar files + genericJar = new JarFile( genericJarFile ); + wlJar = new JarFile( weblogicJarFile ); + + Hashtable genericEntries = new Hashtable(); + Hashtable wlEntries = new Hashtable(); + Hashtable replaceEntries = new Hashtable(); + + //get the list of generic jar entries + for( Enumeration e = genericJar.entries(); e.hasMoreElements(); ) + { + JarEntry je = ( JarEntry )e.nextElement(); + genericEntries.put( je.getName().replace( '\\', '/' ), je ); + } + //get the list of weblogic jar entries + for( Enumeration e = wlJar.entries(); e.hasMoreElements(); ) + { + JarEntry je = ( JarEntry )e.nextElement(); + wlEntries.put( je.getName(), je ); + } + + //Cycle Through generic and make sure its in weblogic + ClassLoader genericLoader = getClassLoaderFromJar( genericJarFile ); + for( Enumeration e = genericEntries.keys(); e.hasMoreElements(); ) + { + String filepath = ( String )e.nextElement(); + if( wlEntries.containsKey( filepath ) ) + {// File name/path match + + // Check files see if same + JarEntry genericEntry = ( JarEntry )genericEntries.get( filepath ); + JarEntry wlEntry = ( JarEntry )wlEntries.get( filepath ); + if( ( genericEntry.getCrc() != wlEntry.getCrc() ) || // Crc's Match + ( genericEntry.getSize() != wlEntry.getSize() ) ) + {// Size Match + + if( genericEntry.getName().endsWith( ".class" ) ) + { + //File are different see if its an object or an interface + String classname = genericEntry.getName().replace( File.separatorChar, '.' ); + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + Class genclass = genericLoader.loadClass( classname ); + if( genclass.isInterface() ) + { + //Interface changed rebuild jar. + log( "Interface " + genclass.getName() + " has changed", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + else + { + //Object class Changed update it. + replaceEntries.put( filepath, genericEntry ); + } + } + else + { + // is it the manifest. If so ignore it + if( !genericEntry.getName().equals( "META-INF/MANIFEST.MF" ) ) + { + //File other then class changed rebuild + log( "Non class file " + genericEntry.getName() + " has changed", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + } + } + } + else + {// a file doesnt exist rebuild + + log( "File " + filepath + " not present in weblogic jar", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + } + + if( !rebuild ) + { + log( "No rebuild needed - updating jar", Project.MSG_VERBOSE ); + newWLJarFile = new File( weblogicJarFile.getAbsolutePath() + ".temp" ); + if( newWLJarFile.exists() ) + { + newWLJarFile.delete(); + } + + newJarStream = new JarOutputStream( new FileOutputStream( newWLJarFile ) ); + newJarStream.setLevel( 0 ); + + //Copy files from old weblogic jar + for( Enumeration e = wlEntries.elements(); e.hasMoreElements(); ) + { + byte[] buffer = new byte[1024]; + int bytesRead; + InputStream is; + JarEntry je = ( JarEntry )e.nextElement(); + if( je.getCompressedSize() == -1 || + je.getCompressedSize() == je.getSize() ) + { + newJarStream.setLevel( 0 ); + } + else + { + newJarStream.setLevel( 9 ); + } + + // Update with changed Bean class + if( replaceEntries.containsKey( je.getName() ) ) + { + log( "Updating Bean class from generic Jar " + je.getName(), Project.MSG_VERBOSE ); + // Use the entry from the generic jar + je = ( JarEntry )replaceEntries.get( je.getName() ); + is = genericJar.getInputStream( je ); + } + else + {//use fle from original weblogic jar + + is = wlJar.getInputStream( je ); + } + newJarStream.putNextEntry( new JarEntry( je.getName() ) ); + + while( ( bytesRead = is.read( buffer ) ) != -1 ) + { + newJarStream.write( buffer, 0, bytesRead ); + } + is.close(); + } + } + else + { + log( "Weblogic Jar rebuild needed due to changed interface or XML", Project.MSG_VERBOSE ); + } + } + else + { + rebuild = true; + } + } + catch( ClassNotFoundException cnfe ) + { + String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + + ". Details: " + + cnfe.getMessage(); + throw new BuildException( cnfmsg, cnfe ); + } + catch( IOException ioe ) + { + String msg = "IOException while processing ejb-jar file " + + ". Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + finally + { + // need to close files and perhaps rename output + if( genericJar != null ) + { + try + { + genericJar.close(); + } + catch( IOException closeException ) + {} + } + + if( wlJar != null ) + { + try + { + wlJar.close(); + } + catch( IOException closeException ) + {} + } + + if( newJarStream != null ) + { + try + { + newJarStream.close(); + } + catch( IOException closeException ) + {} + + weblogicJarFile.delete(); + newWLJarFile.renameTo( weblogicJarFile ); + if( !weblogicJarFile.exists() ) + { + rebuild = true; + } + } + } + + return rebuild; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + File weblogicDD = new File( getConfig().descriptorDir, ddPrefix + WL_DD ); + + if( weblogicDD.exists() ) + { + ejbFiles.put( META_DIR + WL_DD, + weblogicDD ); + } + else + { + log( "Unable to locate weblogic deployment descriptor. It was expected to be in " + + weblogicDD.getPath(), Project.MSG_WARN ); + return; + } + + if( !newCMP ) + { + log( "The old method for locating CMP files has been DEPRECATED.", Project.MSG_VERBOSE ); + log( "Please adjust your weblogic descriptor and set newCMP=\"true\" " + + "to use the new CMP descriptor inclusion mechanism. ", Project.MSG_VERBOSE ); + // The the weblogic cmp deployment descriptor + File weblogicCMPDD = new File( getConfig().descriptorDir, ddPrefix + WL_CMP_DD ); + + if( weblogicCMPDD.exists() ) + { + ejbFiles.put( META_DIR + WL_CMP_DD, + weblogicCMPDD ); + } + } + else + { + // now that we have the weblogic descriptor, we parse the file + // to find other descriptors needed to deploy the bean. + // this could be the weblogic-cmp-rdbms.xml or any other O/R + // mapping tool descriptors. + try + { + File ejbDescriptor = ( File )ejbFiles.get( META_DIR + EJB_DD ); + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + SAXParser saxParser = saxParserFactory.newSAXParser(); + DescriptorHandler handler = getWeblogicDescriptorHandler( ejbDescriptor.getParentFile() ); + saxParser.parse( new InputSource + ( new FileInputStream + ( weblogicDD ) ), + handler ); + + Hashtable ht = handler.getFiles(); + Enumeration e = ht.keys(); + while( e.hasMoreElements() ) + { + String key = ( String )e.nextElement(); + ejbFiles.put( key, ht.get( key ) ); + } + } + catch( Exception e ) + { + String msg = "Exception while adding Vendor specific files: " + e.toString(); + throw new BuildException( msg, e ); + } + } + } + + protected void registerKnownDTDs( DescriptorHandler handler ) + { + // register all the known DTDs + handler.registerDTD( PUBLICID_EJB11, DEFAULT_WL51_EJB11_DTD_LOCATION ); + handler.registerDTD( PUBLICID_EJB11, DEFAULT_WL60_EJB11_DTD_LOCATION ); + handler.registerDTD( PUBLICID_EJB11, ejb11DTD ); + handler.registerDTD( PUBLICID_EJB20, DEFAULT_WL60_EJB20_DTD_LOCATION ); + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarFile, Hashtable files, + String publicId ) + throws BuildException + { + // need to create a generic jar first. + File genericJarFile = super.getVendorOutputJarFile( baseName ); + super.writeJar( baseName, genericJarFile, files, publicId ); + + if( alwaysRebuild || isRebuildRequired( genericJarFile, jarFile ) ) + { + buildWeblogicJar( genericJarFile, jarFile, publicId ); + } + if( !keepGeneric ) + { + log( "deleting generic jar " + genericJarFile.toString(), + Project.MSG_VERBOSE ); + genericJarFile.delete(); + } + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } + + /** + * Helper method invoked by execute() for each WebLogic jar to be built. + * Encapsulates the logic of constructing a java task for calling + * weblogic.ejbc and executing it. + * + * @param sourceJar java.io.File representing the source (EJB1.1) jarfile. + * @param destJar java.io.File representing the destination, WebLogic + * jarfile. + * @param publicId Description of Parameter + */ + private void buildWeblogicJar( File sourceJar, File destJar, String publicId ) + { + org.apache.tools.ant.taskdefs.Java javaTask = null; + + if( noEJBC ) + { + try + { + getTask().getProject().copyFile( sourceJar, destJar ); + if( !keepgenerated ) + { + sourceJar.delete(); + } + return; + } + catch( IOException e ) + { + throw new BuildException( "Unable to write EJB jar", e ); + } + } + + String ejbcClassName = ejbcClass; + + try + { + javaTask = ( Java )getTask().getProject().createTask( "java" ); + javaTask.setTaskName( "ejbc" ); + + if( getJvmDebugLevel() != null ) + { + javaTask.createJvmarg().setLine( " -Dweblogic.StdoutSeverityLevel=" + jvmDebugLevel ); + } + + if( ejbcClassName == null ) + { + // try to determine it from publicId + if( PUBLICID_EJB11.equals( publicId ) ) + { + ejbcClassName = COMPILER_EJB11; + } + else if( PUBLICID_EJB20.equals( publicId ) ) + { + ejbcClassName = COMPILER_EJB20; + } + else + { + log( "Unrecognized publicId " + publicId + " - using EJB 1.1 compiler", Project.MSG_WARN ); + ejbcClassName = COMPILER_EJB11; + } + } + + javaTask.setClassname( ejbcClassName ); + javaTask.createArg().setLine( additionalArgs ); + if( keepgenerated ) + { + javaTask.createArg().setValue( "-keepgenerated" ); + } + if( compiler == null ) + { + // try to use the compiler specified by build.compiler. Right now we are just going + // to allow Jikes + String buildCompiler = getTask().getProject().getProperty( "build.compiler" ); + if( buildCompiler != null && buildCompiler.equals( "jikes" ) ) + { + javaTask.createArg().setValue( "-compiler" ); + javaTask.createArg().setValue( "jikes" ); + } + } + else + { + if( !compiler.equals( DEFAULT_COMPILER ) ) + { + javaTask.createArg().setValue( "-compiler" ); + javaTask.createArg().setLine( compiler ); + } + } + javaTask.createArg().setValue( sourceJar.getPath() ); + javaTask.createArg().setValue( destJar.getPath() ); + + Path classpath = wlClasspath; + if( classpath == null ) + { + classpath = getCombinedClasspath(); + } + + javaTask.setFork( true ); + if( classpath != null ) + { + javaTask.setClasspath( classpath ); + } + + log( "Calling " + ejbcClassName + " for " + sourceJar.toString(), + Project.MSG_VERBOSE ); + + if( javaTask.executeJava() != 0 ) + { + throw new BuildException( "Ejbc reported an error" ); + } + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling " + ejbcClassName + ". Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java new file mode 100644 index 000000000..3fab8bd54 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +public class WeblogicTOPLinkDeploymentTool extends WeblogicDeploymentTool +{ + + private final static String TL_DTD_LOC = "http://www.objectpeople.com/tlwl/dtd/toplink-cmp_2_5_1.dtd"; + private String toplinkDTD; + + private String toplinkDescriptor; + + /** + * Setter used to store the name of the toplink descriptor. + * + * @param inString the string to use as the descriptor name. + */ + public void setToplinkdescriptor( String inString ) + { + this.toplinkDescriptor = inString; + } + + /** + * Setter used to store the location of the toplink DTD file. This is + * expected to be an URL (file or otherwise). If running this on NT using a + * file URL, the safest thing would be to not use a drive spec in the URL + * and make sure the file resides on the drive that ANT is running from. + * This will keep the setting in the build XML platform independent. + * + * @param inString the string to use as the DTD location. + */ + public void setToplinkdtd( String inString ) + { + this.toplinkDTD = inString; + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + public void validateConfigured() + throws BuildException + { + super.validateConfigured(); + if( toplinkDescriptor == null ) + { + throw new BuildException( "The toplinkdescriptor attribute must be specified" ); + } + } + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + DescriptorHandler handler = super.getDescriptorHandler( srcDir ); + if( toplinkDTD != null ) + { + handler.registerDTD( "-//The Object People, Inc.//DTD TOPLink for WebLogic CMP 2.5.1//EN", + toplinkDTD ); + } + else + { + handler.registerDTD( "-//The Object People, Inc.//DTD TOPLink for WebLogic CMP 2.5.1//EN", + TL_DTD_LOC ); + } + return handler; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + super.addVendorFiles( ejbFiles, ddPrefix ); + // Then the toplink deployment descriptor + + // Setup a naming standard here?. + + + File toplinkDD = new File( getConfig().descriptorDir, ddPrefix + toplinkDescriptor ); + + if( toplinkDD.exists() ) + { + ejbFiles.put( META_DIR + toplinkDescriptor, + toplinkDD ); + } + else + { + log( "Unable to locate toplink deployment descriptor. It was expected to be in " + + toplinkDD.getPath(), Project.MSG_WARN ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java new file mode 100644 index 000000000..8272c3e20 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java @@ -0,0 +1,1633 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.jar.*; +import javax.xml.parsers.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.taskdefs.*; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.types.*; +import org.xml.sax.*; + + +/** + * Websphere deployment tool that augments the ejbjar task. + * + * @author Maneesh Sahu + */ + +public class WebsphereDeploymentTool extends GenericDeploymentTool +{ + + + + public final static String PUBLICID_EJB11 + + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + + public final static String PUBLICID_EJB20 + + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"; + + protected final static String SCHEMA_DIR = "Schema/"; + + + + protected final static String WAS_EXT = "ibm-ejb-jar-ext.xmi"; + + protected final static String WAS_BND = "ibm-ejb-jar-bnd.xmi"; + + protected final static String WAS_CMP_MAP = "Map.mapxmi"; + + protected final static String WAS_CMP_SCHEMA = "Schema.dbxmi"; + + + + /** + * Instance variable that stores the suffix for the websphere jarfile. + */ + + private String jarSuffix = ".jar"; + + + + /** + * Instance variable that determines whether generic ejb jars are kept. + */ + + + + private boolean keepgenerated = false; + + + + private String additionalArgs = ""; + + + + private boolean keepGeneric = false; + + + + private String compiler = null; + + + + private boolean alwaysRebuild = true; + + + + private boolean ejbdeploy = true; + + + + /** + * Indicates if the old CMP location convention is to be used. + */ + + private boolean newCMP = false; + + + + /** + * The classpath to the websphere classes. + */ + + private Path wasClasspath = null; + + + + /** + * true - Only output error messages, suppress informational messages + */ + + private boolean quiet = true; + + + + /** + * the scratchdir for the ejbdeploy operation + */ + + private String tempdir = "_ejbdeploy_temp"; + + + + /** + * true - Only generate the deployment code, do not run RMIC or Javac + */ + + private boolean codegen; + + + + /** + * The name of the database to create. (For top-down mapping only) + */ + + private String dbName; + + + + /** + * The name of the schema to create. (For top-down mappings only) + */ + + private String dbSchema; + + + + /** + * The DB Vendor name, the EJB is persisted against + */ + + private String dbVendor; + + + + /** + * Instance variable that stores the location of the ejb 1.1 DTD file. + */ + + private String ejb11DTD; + + + + /** + * true - Disable informational messages + */ + + private boolean noinform; + + + + /** + * true - Disable the validation steps + */ + + private boolean novalidate; + + + + /** + * true - Disable warning and informational messages + */ + + private boolean nowarn; + + + + /** + * Additional options for RMIC + */ + + private String rmicOptions; + + + + /** + * true - Enable internal tracing + */ + + private boolean trace; + + + + /** + * true- Use the WebSphere 3.5 compatible mapping rules + */ + + private boolean use35MappingRules; + + + + /** + * sets some additional args to send to ejbdeploy. + * + * @param args The new Args value + */ + + public void setArgs( String args ) + { + + this.additionalArgs = args; + + } + + + + /** + * (true) Only generate the deployment code, do not run RMIC or Javac + * + * @param codegen The new Codegen value + */ + + public void setCodegen( boolean codegen ) + { + + this.codegen = codegen; + + } + + + + /** + * The compiler (switch -compiler) to use + * + * @param compiler The new Compiler value + */ + + public void setCompiler( String compiler ) + { + + this.compiler = compiler; + + } + + + + /** + * Sets the name of the Database to create + * + * @param dbName The new Dbname value + */ + + public void setDbname( String dbName ) + { + + this.dbName = dbName; + + } + + + + /** + * Sets the name of the schema to create + * + * @param dbSchema The new Dbschema value + */ + + public void setDbschema( String dbSchema ) + { + + this.dbSchema = dbSchema; + + } + + + + /** + * Sets the DB Vendor for the Entity Bean mapping + * + * @param dbvendor The new Dbvendor value + */ + + public void setDbvendor( DBVendor dbvendor ) + { + + this.dbVendor = dbvendor.getValue(); + + } + + + + /** + * Setter used to store the location of the Sun's Generic EJB DTD. This can + * be a file on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + + public void setEJBdtd( String inString ) + { + + this.ejb11DTD = inString; + + } + + + + /** + * Decide, wether ejbdeploy should be called or not + * + * @param ejbdeploy + */ + + public void setEjbdeploy( boolean ejbdeploy ) + { + + this.ejbdeploy = ejbdeploy; + + } + + + + /** + * Sets whether -keepgenerated is passed to ejbdeploy (that is, the .java + * source files are kept). + * + * @param inValue either 'true' or 'false' + */ + + public void setKeepgenerated( String inValue ) + { + + this.keepgenerated = Boolean.valueOf( inValue ).booleanValue(); + + } + + + + /** + * Setter used to store the value of keepGeneric + * + * @param inValue a string, either 'true' or 'false'. + */ + + public void setKeepgeneric( boolean inValue ) + { + + this.keepGeneric = inValue; + + } + + + + /** + * Set the value of the newCMP scheme. The old CMP scheme locates the + * websphere CMP descriptor based on the naming convention where the + * websphere CMP file is expected to be named with the bean name as the + * prefix. Under this scheme the name of the CMP descriptor does not match + * the name actually used in the main websphere EJB descriptor. Also, + * descriptors which contain multiple CMP references could not be used. + * + * @param newCMP The new NewCMP value + */ + + public void setNewCMP( boolean newCMP ) + { + + this.newCMP = newCMP; + + } + + + + /** + * (true) Disable informational messages + * + * @param noinfom The new Noinform value + */ + + public void setNoinform( boolean noinfom ) + { + + this.noinform = noinform; + + } + + + + /** + * (true) Disable the validation steps + * + * @param novalidate The new Novalidate value + */ + + public void setNovalidate( boolean novalidate ) + { + + this.novalidate = novalidate; + + } + + + + /** + * (true) Disable warning and informational messages + * + * @param nowarn The new Nowarn value + */ + + public void setNowarn( boolean nowarn ) + { + + this.nowarn = nowarn; + + } + + + + /** + * Set the value of the oldCMP scheme. This is an antonym for newCMP + * + * @param oldCMP The new OldCMP value + */ + + public void setOldCMP( boolean oldCMP ) + { + + this.newCMP = !oldCMP; + + } + + + + /** + * (true) Only output error messages, suppress informational messages + * + * @param quiet The new Quiet value + */ + + public void setQuiet( boolean quiet ) + { + + this.quiet = quiet; + + } + + + + /** + * Set the rebuild flag to false to only update changes in the jar rather + * than rerunning ejbdeploy + * + * @param rebuild The new Rebuild value + */ + + public void setRebuild( boolean rebuild ) + { + + this.alwaysRebuild = rebuild; + + } + + + + + + /** + * Setter used to store the suffix for the generated websphere jar file. + * + * @param inString the string to use as the suffix. + */ + + public void setSuffix( String inString ) + { + + this.jarSuffix = inString; + + } + + + + /** + * Sets the temporary directory for the ejbdeploy task + * + * @param tempdir The new Tempdir value + */ + + public void setTempdir( String tempdir ) + { + + this.tempdir = tempdir; + + } + + + + /** + * (true) Enable internal tracing + * + * @param trace The new Trace value + */ + + public void setTrace( boolean trace ) + { + + this.trace = trace; + + } + + + + /** + * (true) Use the WebSphere 3.5 compatible mapping rules + * + * @param attr The new Use35 value + */ + + public void setUse35( boolean attr ) + { + + use35MappingRules = attr; + + } + + + + public void setWASClasspath( Path wasClasspath ) + { + + this.wasClasspath = wasClasspath; + + } + + + + /** + * Get the classpath to the websphere classpaths + * + * @return Description of the Returned Value + */ + + public Path createWASClasspath() + { + + if( wasClasspath == null ) + { + + wasClasspath = new Path( getTask().getProject() ); + + } + + return wasClasspath.createPath(); + + } + + + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + + public void validateConfigured() + throws BuildException + { + + super.validateConfigured(); + + } + + + + /** + * Helper method invoked by isRebuildRequired to get a ClassLoader for a Jar + * File passed to it. + * + * @param classjar java.io.File representing jar file to get classes from. + * @return The ClassLoaderFromJar value + * @exception IOException Description of Exception + */ + + protected ClassLoader getClassLoaderFromJar( File classjar ) + throws IOException + { + + Path lookupPath = new Path( getTask().getProject() ); + + lookupPath.setLocation( classjar ); + + + + Path classpath = getCombinedClasspath(); + + if( classpath != null ) + { + + lookupPath.append( classpath ); + + } + + + + return new AntClassLoader( getTask().getProject(), lookupPath ); + + } + + + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + + DescriptorHandler handler = new DescriptorHandler( getTask(), srcDir ); + + // register all the DTDs, both the ones that are known and + + + // any supplied by the user + + handler.registerDTD( PUBLICID_EJB11, ejb11DTD ); + + + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + + } + + + + return handler; + + } + + + + /** + * Gets the options for the EJB Deploy operation + * + * @return String + */ + + protected String getOptions() + { + + // Set the options + + + StringBuffer options = new StringBuffer(); + + if( dbVendor != null ) + { + + options.append( " -dbvendor " ).append( dbVendor ); + + } + + if( dbName != null ) + { + + options.append( " -dbname \"" ).append( dbName ).append( "\"" ); + + } + + + + if( dbSchema != null ) + { + + options.append( " -dbschema \"" ).append( dbSchema ).append( "\"" ); + + } + + + + if( codegen ) + { + + options.append( " -codegen" ); + + } + + + + if( quiet ) + { + + options.append( " -quiet" ); + + } + + + + if( novalidate ) + { + + options.append( " -novalidate" ); + + } + + + + if( nowarn ) + { + + options.append( " -nowarn" ); + + } + + + + if( noinform ) + { + + options.append( " -noinform" ); + + } + + + + if( trace ) + { + + options.append( " -trace" ); + + } + + + + if( use35MappingRules ) + { + + options.append( " -35" ); + + } + + + + if( rmicOptions != null ) + { + + options.append( " -rmic \"" ).append( rmicOptions ).append( "\"" ); + + } + + + + return options.toString(); + + } + + + + protected DescriptorHandler getWebsphereDescriptorHandler( final File srcDir ) + { + + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + + protected void processElement() { } + + }; + + + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + + } + + return handler; + + } + + + + + + /** + * Helper method to check to see if a websphere EBJ1.1 jar needs to be + * rebuilt using ejbdeploy. Called from writeJar it sees if the "Bean" + * classes are the only thing that needs to be updated and either updates + * the Jar with the Bean classfile or returns true, saying that the whole + * websphere jar needs to be regened with ejbdeploy. This allows faster + * build times for working developers.

              + * + * The way websphere ejbdeploy works is it creates wrappers for the publicly + * defined methods as they are exposed in the remote interface. If the + * actual bean changes without changing the the method signatures then only + * the bean classfile needs to be updated and the rest of the websphere jar + * file can remain the same. If the Interfaces, ie. the method signatures + * change or if the xml deployment dicriptors changed, the whole jar needs + * to be rebuilt with ejbdeploy. This is not strictly true for the xml + * files. If the JNDI name changes then the jar doesnt have to be rebuild, + * but if the resources references change then it does. At this point the + * websphere jar gets rebuilt if the xml files change at all. + * + * @param genericJarFile java.io.File The generic jar file. + * @param websphereJarFile java.io.File The websphere jar file to check to + * see if it needs to be rebuilt. + * @return The RebuildRequired value + */ + + protected boolean isRebuildRequired( File genericJarFile, File websphereJarFile ) + { + + boolean rebuild = false; + + + + JarFile genericJar = null; + + JarFile wasJar = null; + + File newwasJarFile = null; + + JarOutputStream newJarStream = null; + + + + try + { + + log( "Checking if websphere Jar needs to be rebuilt for jar " + websphereJarFile.getName(), + + Project.MSG_VERBOSE ); + + // Only go forward if the generic and the websphere file both exist + + + if( genericJarFile.exists() && genericJarFile.isFile() + + && websphereJarFile.exists() && websphereJarFile.isFile() ) + { + + //open jar files + + + genericJar = new JarFile( genericJarFile ); + + wasJar = new JarFile( websphereJarFile ); + + + + Hashtable genericEntries = new Hashtable(); + + Hashtable wasEntries = new Hashtable(); + + Hashtable replaceEntries = new Hashtable(); + + + + //get the list of generic jar entries + + for( Enumeration e = genericJar.entries(); e.hasMoreElements(); ) + { + + JarEntry je = ( JarEntry )e.nextElement(); + + genericEntries.put( je.getName().replace( '\\', '/' ), je ); + + } + + //get the list of websphere jar entries + + + for( Enumeration e = wasJar.entries(); e.hasMoreElements(); ) + { + + JarEntry je = ( JarEntry )e.nextElement(); + + wasEntries.put( je.getName(), je ); + + } + + + + //Cycle Through generic and make sure its in websphere + + ClassLoader genericLoader = getClassLoaderFromJar( genericJarFile ); + + for( Enumeration e = genericEntries.keys(); e.hasMoreElements(); ) + { + + String filepath = ( String )e.nextElement(); + + if( wasEntries.containsKey( filepath ) ) + {// File name/path match + + + // Check files see if same + + JarEntry genericEntry = ( JarEntry )genericEntries.get( filepath ); + + JarEntry wasEntry = ( JarEntry )wasEntries.get( filepath ); + + if( ( genericEntry.getCrc() != wasEntry.getCrc() ) || // Crc's Match + + ( genericEntry.getSize() != wasEntry.getSize() ) ) + {// Size Match + + + if( genericEntry.getName().endsWith( ".class" ) ) + { + + //File are different see if its an object or an interface + + + String classname = genericEntry.getName().replace( File.separatorChar, '.' ); + + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + + Class genclass = genericLoader.loadClass( classname ); + + if( genclass.isInterface() ) + { + + //Interface changed rebuild jar. + + + log( "Interface " + genclass.getName() + " has changed", Project.MSG_VERBOSE ); + + rebuild = true; + + break; + + } + + else + { + + //Object class Changed update it. + + + replaceEntries.put( filepath, genericEntry ); + + } + + } + + else + { + + // is it the manifest. If so ignore it + + + if( !genericEntry.getName().equals( "META-INF/MANIFEST.MF" ) ) + { + + //File other then class changed rebuild + + + log( "Non class file " + genericEntry.getName() + " has changed", Project.MSG_VERBOSE ); + + rebuild = true; + + } + + break; + + } + + } + + } + + else + {// a file doesnt exist rebuild + + + log( "File " + filepath + " not present in websphere jar", Project.MSG_VERBOSE ); + + rebuild = true; + + break; + + } + + } + + + + if( !rebuild ) + { + + log( "No rebuild needed - updating jar", Project.MSG_VERBOSE ); + + newwasJarFile = new File( websphereJarFile.getAbsolutePath() + ".temp" ); + + if( newwasJarFile.exists() ) + { + + newwasJarFile.delete(); + + } + + + + newJarStream = new JarOutputStream( new FileOutputStream( newwasJarFile ) ); + + newJarStream.setLevel( 0 ); + + + + //Copy files from old websphere jar + + for( Enumeration e = wasEntries.elements(); e.hasMoreElements(); ) + { + + byte[] buffer = new byte[1024]; + + int bytesRead; + + InputStream is; + + JarEntry je = ( JarEntry )e.nextElement(); + + if( je.getCompressedSize() == -1 || + + je.getCompressedSize() == je.getSize() ) + { + + newJarStream.setLevel( 0 ); + + } + + else + { + + newJarStream.setLevel( 9 ); + + } + + + + // Update with changed Bean class + + if( replaceEntries.containsKey( je.getName() ) ) + { + + log( "Updating Bean class from generic Jar " + je.getName(), + + Project.MSG_VERBOSE ); + + // Use the entry from the generic jar + + + je = ( JarEntry )replaceEntries.get( je.getName() ); + + is = genericJar.getInputStream( je ); + + } + + else + {//use fle from original websphere jar + + + is = wasJar.getInputStream( je ); + + } + + newJarStream.putNextEntry( new JarEntry( je.getName() ) ); + + + + while( ( bytesRead = is.read( buffer ) ) != -1 ) + { + + newJarStream.write( buffer, 0, bytesRead ); + + } + + is.close(); + + } + + } + + else + { + + log( "websphere Jar rebuild needed due to changed interface or XML", Project.MSG_VERBOSE ); + + } + + } + + else + { + + rebuild = true; + + } + + } + + catch( ClassNotFoundException cnfe ) + { + + String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + + + ". Details: " + + + cnfe.getMessage(); + + throw new BuildException( cnfmsg, cnfe ); + + } + + catch( IOException ioe ) + { + + String msg = "IOException while processing ejb-jar file " + + + ". Details: " + + + ioe.getMessage(); + + throw new BuildException( msg, ioe ); + + } + + finally + { + + // need to close files and perhaps rename output + + + if( genericJar != null ) + { + + try + { + + genericJar.close(); + + } + + catch( IOException closeException ) + {} + + } + + + + if( wasJar != null ) + { + + try + { + + wasJar.close(); + + } + + catch( IOException closeException ) + {} + + } + + + + if( newJarStream != null ) + { + + try + { + + newJarStream.close(); + + } + + catch( IOException closeException ) + {} + + + + websphereJarFile.delete(); + + newwasJarFile.renameTo( websphereJarFile ); + + if( !websphereJarFile.exists() ) + { + + rebuild = true; + + } + + } + + } + + + + return rebuild; + + } + + + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param baseName The feature to be added to the VendorFiles attribute + */ + + protected void addVendorFiles( Hashtable ejbFiles, String baseName ) + { + + + + String ddPrefix = ( usingBaseJarName() ? "" : baseName ); + + String dbPrefix = ( dbVendor == null ) ? "" : dbVendor + "-"; + + + + // Get the Extensions document + + File websphereEXT = new File( getConfig().descriptorDir, ddPrefix + WAS_EXT ); + + if( websphereEXT.exists() ) + { + + ejbFiles.put( META_DIR + WAS_EXT, + + websphereEXT ); + + } + else + { + + log( "Unable to locate websphere extensions. It was expected to be in " + + + websphereEXT.getPath(), Project.MSG_VERBOSE ); + + } + + + + File websphereBND = new File( getConfig().descriptorDir, ddPrefix + WAS_BND ); + + if( websphereBND.exists() ) + { + + ejbFiles.put( META_DIR + WAS_BND, + + websphereBND ); + + } + else + { + + log( "Unable to locate websphere bindings. It was expected to be in " + + + websphereBND.getPath(), Project.MSG_VERBOSE ); + + } + + + + if( !newCMP ) + { + + log( "The old method for locating CMP files has been DEPRECATED.", Project.MSG_VERBOSE ); + + log( "Please adjust your websphere descriptor and set newCMP=\"true\" " + + + "to use the new CMP descriptor inclusion mechanism. ", Project.MSG_VERBOSE ); + + } + + else + { + + // We attempt to put in the MAP and Schema files of CMP beans + + + try + { + + // Add the Map file + + + File websphereMAP = new File( getConfig().descriptorDir, + + ddPrefix + dbPrefix + WAS_CMP_MAP ); + + if( websphereMAP.exists() ) + { + + ejbFiles.put( META_DIR + WAS_CMP_MAP, + + websphereMAP ); + + } + else + { + + log( "Unable to locate the websphere Map: " + + + websphereMAP.getPath(), Project.MSG_VERBOSE ); + + } + + File websphereSchema = new File( getConfig().descriptorDir, + + ddPrefix + dbPrefix + WAS_CMP_SCHEMA ); + + if( websphereSchema.exists() ) + { + + ejbFiles.put( META_DIR + SCHEMA_DIR + WAS_CMP_SCHEMA, + + websphereSchema ); + + } + else + { + + log( "Unable to locate the websphere Schema: " + + + websphereSchema.getPath(), Project.MSG_VERBOSE ); + + } + + // Theres nothing else to see here...keep moving sonny + + + } + + catch( Exception e ) + { + + String msg = "Exception while adding Vendor specific files: " + + + e.toString(); + + throw new BuildException( msg, e ); + + } + + } + + } + + + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + + protected void writeJar( String baseName, File jarFile, Hashtable files, String publicId ) + + throws BuildException + { + + if( ejbdeploy ) + { + + // create the -generic.jar, if required + + + File genericJarFile = super.getVendorOutputJarFile( baseName ); + + super.writeJar( baseName, genericJarFile, files, publicId ); + + + + // create the output .jar, if required + + if( alwaysRebuild || isRebuildRequired( genericJarFile, jarFile ) ) + { + + buildWebsphereJar( genericJarFile, jarFile ); + + } + + if( !keepGeneric ) + { + + log( "deleting generic jar " + genericJarFile.toString(), + + Project.MSG_VERBOSE ); + + genericJarFile.delete(); + + } + + } + + else + { + + // create the "undeployed" output .jar, if required + + + super.writeJar( baseName, jarFile, files, publicId ); + + } + + /* + * / need to create a generic jar first. + * File genericJarFile = super.getVendorOutputJarFile(baseName); + * super.writeJar(baseName, genericJarFile, files, publicId); + * if (alwaysRebuild || isRebuildRequired(genericJarFile, jarFile)) { + * buildWebsphereJar(genericJarFile, jarFile); + * } + * if (!keepGeneric) { + * log("deleting generic jar " + genericJarFile.toString(), + * Project.MSG_VERBOSE); + * genericJarFile.delete(); + * } + */ + + } + + + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + + File getVendorOutputJarFile( String baseName ) + { + + return new File( getDestDir(), baseName + jarSuffix ); + + }// end getOptions + + + + /** + * Helper method invoked by execute() for each websphere jar to be built. + * Encapsulates the logic of constructing a java task for calling + * websphere.ejbdeploy and executing it. + * + * @param sourceJar java.io.File representing the source (EJB1.1) jarfile. + * @param destJar java.io.File representing the destination, websphere + * jarfile. + */ + + private void buildWebsphereJar( File sourceJar, File destJar ) + { + + try + { + + if( ejbdeploy ) + { + + String args = + + " " + sourceJar.getPath() + + + " " + tempdir + + + " " + destJar.getPath() + + + " " + getOptions(); + + + + if( getCombinedClasspath() != null && getCombinedClasspath().toString().length() > 0 ) + + args += " -cp " + getCombinedClasspath(); + + + + // Why do my ""'s get stripped away??? + + log( "EJB Deploy Options: " + args, Project.MSG_VERBOSE ); + + + + Java javaTask = ( Java )getTask().getProject().createTask( "java" ); + + // Set the JvmArgs + + + javaTask.createJvmarg().setValue( "-Xms64m" ); + + javaTask.createJvmarg().setValue( "-Xmx128m" ); + + + + // Set the Environment variable + + Environment.Variable var = new Environment.Variable(); + + var.setKey( "websphere.lib.dir" ); + + var.setValue( getTask().getProject().getProperty( "websphere.home" ) + "/lib" ); + + javaTask.addSysproperty( var ); + + + + // Set the working directory + + javaTask.setDir( new File( getTask().getProject().getProperty( "websphere.home" ) ) ); + + + + // Set the Java class name + + javaTask.setTaskName( "ejbdeploy" ); + + javaTask.setClassname( "com.ibm.etools.ejbdeploy.EJBDeploy" ); + + + + Commandline.Argument arguments = javaTask.createArg(); + + arguments.setLine( args ); + + + + Path classpath = wasClasspath; + + if( classpath == null ) + { + + classpath = getCombinedClasspath(); + + } + + + + if( classpath != null ) + { + + javaTask.setClasspath( classpath ); + + javaTask.setFork( true ); + + } + + else + { + + javaTask.setFork( true ); + + } + + + + log( "Calling websphere.ejbdeploy for " + sourceJar.toString(), + + Project.MSG_VERBOSE ); + + + + javaTask.execute(); + + } + + } + + catch( Exception e ) + { + + // Have to catch this because of the semantics of calling main() + + + String msg = "Exception while calling ejbdeploy. Details: " + e.toString(); + + throw new BuildException( msg, e ); + + } + + } + + + /** + * Enumerated attribute with the values for the database vendor types + * + * @author RT + */ + + public static class DBVendor extends EnumeratedAttribute + { + + public String[] getValues() + { + + return new String[]{ + + "SQL92", "SQL99", "DB2UDBWIN_V71", "DB2UDBOS390_V6", "DB2UDBAS400_V4R5", + + "ORACLE_V8", "INFORMIX_V92", "SYBASE_V1192", "MSSQLSERVER_V7", "MYSQL_V323" + + }; + + } + + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java new file mode 100644 index 000000000..ddd1d9e6e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java @@ -0,0 +1,645 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.i18n; +import java.io.*; +import java.util.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.*; +import org.apache.tools.ant.util.*; + +/** + * Translates text embedded in files using Resource Bundle files. + * + * @author Magesh Umasankar + */ +public class Translate extends MatchingTask +{ + /** + * Vector to hold source file sets. + */ + private Vector filesets = new Vector(); + /** + * Holds key value pairs loaded from resource bundle file + */ + private Hashtable resourceMap = new Hashtable(); + /** + * Used to resolve file names. + */ + private FileUtils fileUtils = FileUtils.newFileUtils(); + /** + * Last Modified Timestamp of resource bundle file being used. + */ + private long[] bundleLastModified = new long[7]; + /** + * Has at least one file from the bundle been loaded? + */ + private boolean loaded = false; + + /** + * Family name of resource bundle + */ + private String bundle; + /** + * Locale specific country of the resource bundle + */ + private String bundleCountry; + /** + * Resource Bundle file encoding scheme, defaults to srcEncoding + */ + private String bundleEncoding; + /** + * Locale specific language of the resource bundle + */ + private String bundleLanguage; + /** + * Locale specific variant of the resource bundle + */ + private String bundleVariant; + /** + * Destination file encoding scheme + */ + private String destEncoding; + /** + * Last Modified Timestamp of destination file being used. + */ + private long destLastModified; + /** + * Ending token to identify keys + */ + private String endToken; + /** + * Create new destination file? Defaults to false. + */ + private boolean forceOverwrite; + /** + * Generated locale based on user attributes + */ + private Locale locale; + /** + * Source file encoding scheme + */ + private String srcEncoding; + /** + * Last Modified Timestamp of source file being used. + */ + private long srcLastModified; + /** + * Starting token to identify keys + */ + private String startToken; + /** + * Destination directory + */ + private File toDir; + + /** + * Sets Family name of resource bundle + * + * @param bundle The new Bundle value + */ + public void setBundle( String bundle ) + { + this.bundle = bundle; + } + + /** + * Sets locale specific country of resource bundle + * + * @param bundleCountry The new BundleCountry value + */ + public void setBundleCountry( String bundleCountry ) + { + this.bundleCountry = bundleCountry; + } + + /** + * Sets Resource Bundle file encoding scheme + * + * @param bundleEncoding The new BundleEncoding value + */ + public void setBundleEncoding( String bundleEncoding ) + { + this.bundleEncoding = bundleEncoding; + } + + /** + * Sets locale specific language of resource bundle + * + * @param bundleLanguage The new BundleLanguage value + */ + public void setBundleLanguage( String bundleLanguage ) + { + this.bundleLanguage = bundleLanguage; + } + + /** + * Sets locale specific variant of resource bundle + * + * @param bundleVariant The new BundleVariant value + */ + public void setBundleVariant( String bundleVariant ) + { + this.bundleVariant = bundleVariant; + } + + /** + * Sets destination file encoding scheme. Defaults to source file encoding + * + * @param destEncoding The new DestEncoding value + */ + public void setDestEncoding( String destEncoding ) + { + this.destEncoding = destEncoding; + } + + /** + * Sets ending token to identify keys + * + * @param endToken The new EndToken value + */ + public void setEndToken( String endToken ) + { + this.endToken = endToken; + } + + /** + * Overwrite existing file irrespective of whether it is newer than the + * source file as well as the resource bundle file? Defaults to false. + * + * @param forceOverwrite The new ForceOverwrite value + */ + public void setForceOverwrite( boolean forceOverwrite ) + { + this.forceOverwrite = forceOverwrite; + } + + /** + * Sets source file encoding scheme + * + * @param srcEncoding The new SrcEncoding value + */ + public void setSrcEncoding( String srcEncoding ) + { + this.srcEncoding = srcEncoding; + } + + /** + * Sets starting token to identify keys + * + * @param startToken The new StartToken value + */ + public void setStartToken( String startToken ) + { + this.startToken = startToken; + } + + /** + * Sets Destination directory + * + * @param toDir The new ToDir value + */ + public void setToDir( File toDir ) + { + this.toDir = toDir; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Check attributes values, load resource map and translate + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( bundle == null ) + { + throw new BuildException( "The bundle attribute must be set.", + location ); + } + + if( startToken == null ) + { + throw new BuildException( "The starttoken attribute must be set.", + location ); + } + + if( startToken.length() != 1 ) + { + throw new BuildException( + "The starttoken attribute must be a single character.", + location ); + } + + if( endToken == null ) + { + throw new BuildException( "The endtoken attribute must be set.", + location ); + } + + if( endToken.length() != 1 ) + { + throw new BuildException( + "The endtoken attribute must be a single character.", + location ); + } + + if( bundleLanguage == null ) + { + Locale l = Locale.getDefault(); + bundleLanguage = l.getLanguage(); + } + + if( bundleCountry == null ) + { + bundleCountry = Locale.getDefault().getCountry(); + } + + locale = new Locale( bundleLanguage, bundleCountry ); + + if( bundleVariant == null ) + { + Locale l = new Locale( bundleLanguage, bundleCountry ); + bundleVariant = l.getVariant(); + } + + if( toDir == null ) + { + throw new BuildException( "The todir attribute must be set.", + location ); + } + + if( !toDir.exists() ) + { + toDir.mkdirs(); + } + else + { + if( toDir.isFile() ) + { + throw new BuildException( toDir + " is not a directory" ); + } + } + + if( srcEncoding == null ) + { + srcEncoding = System.getProperty( "file.encoding" ); + } + + if( destEncoding == null ) + { + destEncoding = srcEncoding; + } + + if( bundleEncoding == null ) + { + bundleEncoding = srcEncoding; + } + + loadResourceMaps(); + + translate(); + } + + /** + * Load resourceMap with key value pairs. Values of existing keys are not + * overwritten. Bundle's encoding scheme is used. + * + * @param ins Description of Parameter + * @exception BuildException Description of Exception + */ + private void loadResourceMap( FileInputStream ins ) + throws BuildException + { + try + { + BufferedReader in = null; + InputStreamReader isr = new InputStreamReader( ins, bundleEncoding ); + in = new BufferedReader( isr ); + String line = null; + while( ( line = in.readLine() ) != null ) + { + //So long as the line isn't empty and isn't a comment... + if( line.trim().length() > 1 && + ( '#' != line.charAt( 0 ) || '!' != line.charAt( 0 ) ) ) + { + //Legal Key-Value separators are :, = and white space. + int sepIndex = line.indexOf( '=' ); + if( -1 == sepIndex ) + { + sepIndex = line.indexOf( ':' ); + } + if( -1 == sepIndex ) + { + for( int k = 0; k < line.length(); k++ ) + { + if( Character.isSpaceChar( line.charAt( k ) ) ) + { + sepIndex = k; + break; + } + } + } + //Only if we do have a key is there going to be a value + if( -1 != sepIndex ) + { + String key = line.substring( 0, sepIndex ).trim(); + String value = line.substring( sepIndex + 1 ).trim(); + //Handle line continuations, if any + while( value.endsWith( "\\" ) ) + { + value = value.substring( 0, value.length() - 1 ); + if( ( line = in.readLine() ) != null ) + { + value = value + line.trim(); + } + else + { + break; + } + } + if( key.length() > 0 ) + { + //Has key already been loaded into resourceMap? + if( resourceMap.get( key ) == null ) + { + resourceMap.put( key, value ); + } + } + } + } + } + if( in != null ) + { + in.close(); + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + + /** + * Load resource maps based on resource bundle encoding scheme. The resource + * bundle lookup searches for resource files with various suffixes on the + * basis of (1) the desired locale and (2) the default locale + * (basebundlename), in the following order from lower-level (more specific) + * to parent-level (less specific): basebundlename + "_" + language1 + "_" + + * country1 + "_" + variant1 basebundlename + "_" + language1 + "_" + + * country1 basebundlename + "_" + language1 basebundlename basebundlename + + * "_" + language2 + "_" + country2 + "_" + variant2 basebundlename + "_" + + * language2 + "_" + country2 basebundlename + "_" + language2 To the + * generated name, a ".properties" string is appeneded and once this file is + * located, it is treated just like a properties file but with bundle + * encoding also considered while loading. + * + * @exception BuildException Description of Exception + */ + private void loadResourceMaps() + throws BuildException + { + Locale locale = new Locale( bundleLanguage, + bundleCountry, + bundleVariant ); + String language = locale.getLanguage().length() > 0 ? + "_" + locale.getLanguage() : + ""; + String country = locale.getCountry().length() > 0 ? + "_" + locale.getCountry() : + ""; + String variant = locale.getVariant().length() > 0 ? + "_" + locale.getVariant() : + ""; + String bundleFile = bundle + language + country + variant; + processBundle( bundleFile, 0, false ); + + bundleFile = bundle + language + country; + processBundle( bundleFile, 1, false ); + + bundleFile = bundle + language; + processBundle( bundleFile, 2, false ); + + bundleFile = bundle; + processBundle( bundleFile, 3, false ); + + //Load default locale bundle files + //using default file encoding scheme. + locale = Locale.getDefault(); + + language = locale.getLanguage().length() > 0 ? + "_" + locale.getLanguage() : + ""; + country = locale.getCountry().length() > 0 ? + "_" + locale.getCountry() : + ""; + variant = locale.getVariant().length() > 0 ? + "_" + locale.getVariant() : + ""; + bundleEncoding = System.getProperty( "file.encoding" ); + + bundleFile = bundle + language + country + variant; + processBundle( bundleFile, 4, false ); + + bundleFile = bundle + language + country; + processBundle( bundleFile, 5, false ); + + bundleFile = bundle + language; + processBundle( bundleFile, 6, true ); + } + + /** + * Process each file that makes up this bundle. + * + * @param bundleFile Description of Parameter + * @param i Description of Parameter + * @param checkLoaded Description of Parameter + * @exception BuildException Description of Exception + */ + private void processBundle( String bundleFile, int i, + boolean checkLoaded ) + throws BuildException + { + bundleFile += ".properties"; + FileInputStream ins = null; + try + { + ins = new FileInputStream( bundleFile ); + loaded = true; + bundleLastModified[i] = new File( bundleFile ).lastModified(); + log( "Using " + bundleFile, Project.MSG_DEBUG ); + loadResourceMap( ins ); + } + catch( IOException ioe ) + { + log( bundleFile + " not found.", Project.MSG_DEBUG ); + //if all resource files associated with this bundle + //have been scanned for and still not able to + //find a single resrouce file, throw exception + if( !loaded && checkLoaded ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + } + + /** + * Reads source file line by line using the source encoding and searches for + * keys that are sandwiched between the startToken and endToken. The values + * for these keys are looked up from the hashtable and substituted. If the + * hashtable doesn't contain the key, they key itself is used as the value. + * Detination files and directories are created as needed. The destination + * file is overwritten only if the forceoverwritten attribute is set to true + * if the source file or any associated bundle resource file is newer than + * the destination file. + * + * @exception BuildException Description of Exception + */ + private void translate() + throws BuildException + { + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + for( int j = 0; j < srcFiles.length; j++ ) + { + try + { + File dest = fileUtils.resolveFile( toDir, srcFiles[j] ); + //Make sure parent dirs exist, else, create them. + try + { + File destDir = new File( dest.getParent() ); + if( !destDir.exists() ) + { + destDir.mkdirs(); + } + } + catch( Exception e ) + { + log( "Exception occured while trying to check/create " + + " parent directory. " + e.getMessage(), + Project.MSG_DEBUG ); + } + destLastModified = dest.lastModified(); + srcLastModified = new File( srcFiles[i] ).lastModified(); + //Check to see if dest file has to be recreated + if( forceOverwrite + || destLastModified < srcLastModified + || destLastModified < bundleLastModified[0] + || destLastModified < bundleLastModified[1] + || destLastModified < bundleLastModified[2] + || destLastModified < bundleLastModified[3] + || destLastModified < bundleLastModified[4] + || destLastModified < bundleLastModified[5] + || destLastModified < bundleLastModified[6] ) + { + log( "Processing " + srcFiles[j], + Project.MSG_DEBUG ); + FileOutputStream fos = new FileOutputStream( dest ); + BufferedWriter out = new BufferedWriter( + new OutputStreamWriter( fos, + destEncoding ) ); + FileInputStream fis = new FileInputStream( srcFiles[j] ); + BufferedReader in = new BufferedReader( + new InputStreamReader( fis, + srcEncoding ) ); + String line; + while( ( line = in.readLine() ) != null ) + { + StringBuffer newline = new StringBuffer( line ); + int startIndex = -1; + int endIndex = -1; + outer : + while( true ) + { + startIndex = line.indexOf( startToken, endIndex + 1 ); + if( startIndex < 0 || + startIndex + 1 >= line.length() ) + { + break; + } + endIndex = line.indexOf( endToken, startIndex + 1 ); + if( endIndex < 0 ) + { + break; + } + String matches = line.substring( startIndex + 1, + endIndex ); + //If there is a white space or = or :, then + //it isn't to be treated as a valid key. + for( int k = 0; k < matches.length(); k++ ) + { + char c = matches.charAt( k ); + if( c == ':' || + c == '=' || + Character.isSpaceChar( c ) ) + { + endIndex = endIndex - 1; + continue outer; + } + } + String replace = null; + replace = ( String )resourceMap.get( matches ); + //If the key hasn't been loaded into resourceMap, + //use the key itself as the value also. + if( replace == null ) + { + log( "Warning: The key: " + matches + + " hasn't been defined.", + Project.MSG_DEBUG ); + replace = matches; + } + line = line.substring( 0, startIndex ) + + replace + + line.substring( endIndex + 1 ); + endIndex = startIndex + replace.length() + 1; + if( endIndex + 1 >= line.length() ) + { + break; + } + } + out.write( line ); + out.newLine(); + } + if( in != null ) + { + in.close(); + } + if( out != null ) + { + out.close(); + } + } + else + { + log( "Skipping " + srcFiles[j] + + " as destination file is up to date", + Project.MSG_VERBOSE ); + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java new file mode 100644 index 000000000..1526cd393 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.Project; +import com.ibm.ivj.util.base.ToolData; +import org.apache.tools.ant.BuildException; + + +/** + * This class is the equivalent to org.apache.tools.ant.Main for the VAJ tool + * environment. It's main is called when the user selects Tools->Ant Build from + * the VAJ project menu. Additionally this class provides methods to save build + * info for a project in the repository and load it from the repository + * + * @author RT + * @author: Wolf Siberski + */ +public class VAJAntTool +{ + private final static String TOOL_DATA_KEY = "AntTool"; + + + /** + * Loads the BuildInfo for the specified VAJ project from the tool data for + * this project. If there is no build info stored for that project, a new + * default BuildInfo is returned + * + * @param projectName String project name + * @return BuildInfo buildInfo build info for the specified project + */ + public static VAJBuildInfo loadBuildData( String projectName ) + { + VAJBuildInfo result = null; + try + { + Project project = + VAJLocalUtil.getWorkspace().loadedProjectNamed( projectName ); + if( project.testToolRepositoryData( TOOL_DATA_KEY ) ) + { + ToolData td = project.getToolRepositoryData( TOOL_DATA_KEY ); + String data = ( String )td.getData(); + result = VAJBuildInfo.parse( data ); + } + else + { + result = new VAJBuildInfo(); + } + result.setVAJProjectName( projectName ); + } + catch( Throwable t ) + { + throw new BuildException( "BuildInfo for Project " + + projectName + " could not be loaded" + t ); + } + return result; + } + + + /** + * Starts the application. + * + * @param args an array of command-line arguments. VAJ puts the VAJ project + * name into args[1] when starting the tool from the project context + * menu + */ + public static void main( java.lang.String[] args ) + { + try + { + VAJBuildInfo info; + if( args.length >= 2 && args[1] instanceof String ) + { + String projectName = ( String )args[1]; + info = loadBuildData( projectName ); + } + else + { + info = new VAJBuildInfo(); + } + + VAJAntToolGUI mainFrame = new VAJAntToolGUI( info ); + mainFrame.show(); + } + catch( Throwable t ) + { + // if all error handling fails, output at least + // something on the console + t.printStackTrace(); + } + } + + + /** + * Saves the BuildInfo for a project in the VAJ repository. + * + * @param info BuildInfo build info to save + */ + public static void saveBuildData( VAJBuildInfo info ) + { + String data = info.asDataString(); + try + { + ToolData td = new ToolData( TOOL_DATA_KEY, data ); + VAJLocalUtil.getWorkspace().loadedProjectNamed( + info.getVAJProjectName() ).setToolRepositoryData( td ); + } + catch( Throwable t ) + { + throw new BuildException( "BuildInfo for Project " + + info.getVAJProjectName() + " could not be saved", t ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java new file mode 100644 index 000000000..7aa8916e0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java @@ -0,0 +1,1803 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.Choice; +import java.awt.Dialog; +import java.awt.FileDialog; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Label; +import java.awt.List; +import java.awt.Menu; +import java.awt.MenuBar; +import java.awt.MenuItem; +import java.awt.Panel; +import java.awt.SystemColor; +import java.awt.TextArea; +import java.awt.TextField; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.TextEvent; +import java.awt.event.TextListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.beans.PropertyChangeListener; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Vector; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + +/** + * This is a simple grafical user interface to provide the information needed by + * ANT and to start the build-process within IBM VisualAge for Java.

              + * + * I was using AWT to make it independent from the JDK-version. Please don't ask + * me for a Swing-version:I am very familiar with Swing and I really think that + * it's not necessary for such a simple gui!

              + * + * It is completely developed in VAJ using the visual composition editor. About + * 90% of the code is generated by VAJ, but in fact I did a lot of + * code-beautification ;-).

              + * + * + * + * @author RT + * @version 1.0 h + * @author: Christoph Wilhelms, TUI Infotec GmbH + */ +public class VAJAntToolGUI extends Frame +{ + private final static String lineSeparator = "\r\n"; + /** + * Members + */ + private VAJBuildLogger logger = new VAJBuildLogger(); + private PrivateEventHandler iEventHandler = new PrivateEventHandler(); + + /** + * Members of the main-window + */ + // main model + private VAJBuildInfo iBuildInfo = null; + // Menue + private MenuBar iAntMakeMenuBar = null; + private Menu iFileMenu = null; + private MenuItem iSaveMenuItem = null; + private MenuItem iMenuSeparator = null; + private MenuItem iShowLogMenuItem = null; + private Menu iHelpMenu = null; + private MenuItem iAboutMenuItem = null; + // Container + private Panel iContentsPane = null; + private Panel iOptionenPanel = null; + private Panel iCommandButtonPanel = null; + private FlowLayout iCommandButtonPanelFlowLayout = null; + // Project name + private Label iProjectLabel = null; + private Label iProjectText = null; + // XML-file + private Label iBuildFileLabel = null; + private TextField iBuildFileTextField = null; + private boolean iConnPtoP2Aligning = false; + private Button iBrowseButton = null; + private FileDialog iFileDialog = null; + // Options + private Choice iMessageOutputLevelChoice = null; + private Label iMessageOutputLevelLabel = null; + private Label iTargetLabel = null; + private List iTargetList = null; + // Command-buttons + private Button iBuildButton = null; + private Button iReloadButton = null; + private Button iCloseButton = null; + /** + * log-Window + */ + // Container + private Frame iMessageFrame = null; + private Panel iMessageCommandPanel = null; + private Panel iMessageContentPanel = null; + // Components + private TextArea iMessageTextArea = null; + private Button iMessageOkButton = null; + private Button iMessageClearLogButton = null; + /** + * About-dialog + */ + // Container + private Dialog iAboutDialog = null; + private Panel iAboutDialogContentPanel = null; + private Panel iAboutInfoPanel = null; + private Panel iAboutCommandPanel = null; + // Labels + private Label iAboutTitleLabel = null; + private Label iAboutDevLabel = null; + private Label iAboutContactLabel = null; + // Buttons + private Button iAboutOkButton = null; + + private Button iStopButton = null; + + /** + * AntMake constructor called by VAJAntTool integration. + * + * @param newBuildInfo Description of Parameter + */ + + public VAJAntToolGUI( VAJBuildInfo newBuildInfo ) + { + super(); + setBuildInfo( newBuildInfo ); + initialize(); + } + + /** + * AntMake default-constructor. + */ + private VAJAntToolGUI() + { + super(); + initialize(); + } + + /** + * This method is used to center dialogs. + * + * @param dialog Description of Parameter + */ + public static void centerDialog( Dialog dialog ) + { + dialog.setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( dialog.getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) - ( dialog.getSize().height / 2 ) ); + } + + /** + * Copied from DefaultLogger to provide the same time-format. + * + * @param millis Description of Parameter + * @return Description of the Returned Value + */ + public static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + } + + /** + * Set the BuildInfo to a new value. + * + * @param newValue org.apache.tools.ant.taskdefs.optional.vaj.VAJBuildInfo + */ + private void setBuildInfo( VAJBuildInfo newValue ) + { + if( iBuildInfo != newValue ) + { + try + { + /* + * Stop listening for events from the current object + */ + if( iBuildInfo != null ) + { + iBuildInfo.removePropertyChangeListener( iEventHandler ); + } + iBuildInfo = newValue; + + /* + * Listen for events from the new object + */ + if( iBuildInfo != null ) + { + iBuildInfo.addPropertyChangeListener( iEventHandler ); + } + connectProjectNameToLabel(); + connectBuildFileNameToTextField(); + + // Select the log-level given by BuildInfo + getMessageOutputLevelChoice().select( iBuildInfo.getOutputMessageLevel() ); + fillList(); + // BuildInfo can conly be saved to a VAJ project if tool API is called via the projects context-menu + if( ( iBuildInfo.getVAJProjectName() == null ) || ( iBuildInfo.getVAJProjectName().equals( "" ) ) ) + { + getSaveMenuItem().setEnabled( false ); + } + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + } + + /** + * Return the AboutCommandPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutCommandPanel() + { + if( iAboutCommandPanel == null ) + { + try + { + iAboutCommandPanel = new Panel(); + iAboutCommandPanel.setName( "AboutCommandPanel" ); + iAboutCommandPanel.setLayout( new java.awt.FlowLayout() ); + getAboutCommandPanel().add( getAboutOkButton(), getAboutOkButton().getName() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutCommandPanel; + } + + /** + * Return the AboutContactLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutContactLabel() + { + if( iAboutContactLabel == null ) + { + try + { + iAboutContactLabel = new Label(); + iAboutContactLabel.setName( "AboutContactLabel" ); + iAboutContactLabel.setAlignment( java.awt.Label.CENTER ); + iAboutContactLabel.setText( "contact: wolf.siberski@tui.de or christoph.wilhelms@tui.de" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutContactLabel; + } + + /** + * Return the AboutDevLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutDevLabel() + { + if( iAboutDevLabel == null ) + { + try + { + iAboutDevLabel = new Label(); + iAboutDevLabel.setName( "AboutDevLabel" ); + iAboutDevLabel.setAlignment( java.awt.Label.CENTER ); + iAboutDevLabel.setText( "developed by Wolf Siberski & Christoph Wilhelms" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDevLabel; + } + + /** + * Return the AboutDialog property value. + * + * @return java.awt.Dialog + */ + private Dialog getAboutDialog() + { + if( iAboutDialog == null ) + { + try + { + iAboutDialog = new Dialog( this ); + iAboutDialog.setName( "AboutDialog" ); + iAboutDialog.setResizable( false ); + iAboutDialog.setLayout( new java.awt.BorderLayout() ); + iAboutDialog.setBounds( 550, 14, 383, 142 ); + iAboutDialog.setModal( true ); + iAboutDialog.setTitle( "About..." ); + getAboutDialog().add( getAboutDialogContentPanel(), "Center" ); + iAboutDialog.pack(); + centerDialog( iAboutDialog ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDialog; + } + + /** + * Return the AboutDialogContentPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutDialogContentPanel() + { + if( iAboutDialogContentPanel == null ) + { + try + { + iAboutDialogContentPanel = new Panel(); + iAboutDialogContentPanel.setName( "AboutDialogContentPanel" ); + iAboutDialogContentPanel.setLayout( new java.awt.BorderLayout() ); + getAboutDialogContentPanel().add( getAboutCommandPanel(), "South" ); + getAboutDialogContentPanel().add( getAboutInfoPanel(), "Center" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDialogContentPanel; + } + + /** + * Return the AboutInfoPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutInfoPanel() + { + if( iAboutInfoPanel == null ) + { + try + { + iAboutInfoPanel = new Panel(); + iAboutInfoPanel.setName( "AboutInfoPanel" ); + iAboutInfoPanel.setLayout( new GridBagLayout() ); + + GridBagConstraints constraintsAboutTitleLabel = new GridBagConstraints(); + constraintsAboutTitleLabel.gridx = 0; + constraintsAboutTitleLabel.gridy = 0; + constraintsAboutTitleLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutTitleLabel.weightx = 1.0; + constraintsAboutTitleLabel.weighty = 1.0; + constraintsAboutTitleLabel.insets = new Insets( 4, 0, 4, 0 ); + getAboutInfoPanel().add( getAboutTitleLabel(), constraintsAboutTitleLabel ); + + GridBagConstraints constraintsAboutDevLabel = new GridBagConstraints(); + constraintsAboutDevLabel.gridx = 0; + constraintsAboutDevLabel.gridy = 1; + constraintsAboutDevLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutDevLabel.weightx = 1.0; + constraintsAboutDevLabel.insets = new Insets( 4, 0, 0, 0 ); + getAboutInfoPanel().add( getAboutDevLabel(), constraintsAboutDevLabel ); + + GridBagConstraints constraintsAboutContactLabel = new GridBagConstraints(); + constraintsAboutContactLabel.gridx = 0; + constraintsAboutContactLabel.gridy = 2; + constraintsAboutContactLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutContactLabel.weightx = 1.0; + constraintsAboutContactLabel.insets = new Insets( 2, 0, 4, 0 ); + getAboutInfoPanel().add( getAboutContactLabel(), constraintsAboutContactLabel ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutInfoPanel; + } + + /** + * Return the AboutMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getAboutMenuItem() + { + if( iAboutMenuItem == null ) + { + try + { + iAboutMenuItem = new MenuItem(); + iAboutMenuItem.setLabel( "About..." ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutMenuItem; + } + + /** + * Return the AboutOkButton property value. + * + * @return java.awt.Button + */ + private Button getAboutOkButton() + { + if( iAboutOkButton == null ) + { + try + { + iAboutOkButton = new Button(); + iAboutOkButton.setName( "AboutOkButton" ); + iAboutOkButton.setLabel( "OK" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutOkButton; + } + + /** + * Return the AboutTitleLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutTitleLabel() + { + if( iAboutTitleLabel == null ) + { + try + { + iAboutTitleLabel = new Label(); + iAboutTitleLabel.setName( "AboutTitleLabel" ); + iAboutTitleLabel.setFont( new Font( "Arial", 1, 12 ) ); + iAboutTitleLabel.setAlignment( Label.CENTER ); + iAboutTitleLabel.setText( "Ant VisualAge for Java Tool-Integration" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutTitleLabel; + } + + /** + * Return the AntMakeMenuBar property value. + * + * @return java.awt.MenuBar + */ + private MenuBar getAntMakeMenuBar() + { + if( iAntMakeMenuBar == null ) + { + try + { + iAntMakeMenuBar = new MenuBar(); + iAntMakeMenuBar.add( getFileMenu() ); + iAntMakeMenuBar.add( getHelpMenu() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAntMakeMenuBar; + } + + /** + * Return the BrowseButton property value. + * + * @return Button + */ + private Button getBrowseButton() + { + if( iBrowseButton == null ) + { + try + { + iBrowseButton = new Button(); + iBrowseButton.setName( "BrowseButton" ); + iBrowseButton.setLabel( "..." ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBrowseButton; + } + + /** + * Return the BuildButton property value. + * + * @return java.awt.Button + */ + private Button getBuildButton() + { + if( iBuildButton == null ) + { + try + { + iBuildButton = new Button(); + iBuildButton.setName( "BuildButton" ); + iBuildButton.setLabel( "Execute" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildButton; + } + + /** + * Return the BuildFileLabel property value. + * + * @return java.awt.Label + */ + private Label getBuildFileLabel() + { + if( iBuildFileLabel == null ) + { + try + { + iBuildFileLabel = new Label(); + iBuildFileLabel.setName( "BuildFileLabel" ); + iBuildFileLabel.setText( "Ant-Buildfile:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildFileLabel; + } + + /** + * Return the BuildFileTextField property value. + * + * @return java.awt.TextField + */ + private TextField getBuildFileTextField() + { + if( iBuildFileTextField == null ) + { + try + { + iBuildFileTextField = new TextField(); + iBuildFileTextField.setName( "BuildFileTextField" ); + iBuildFileTextField.setBackground( SystemColor.textHighlightText ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildFileTextField; + } + + /** + * Return the BuildInfo property value. + * + * @return org.apache.tools.ant.taskdefs.optional.ide.VAJBuildInfo + */ + private VAJBuildInfo getBuildInfo() + { + return iBuildInfo; + } + + /** + * Return the CloseButton property value. + * + * @return java.awt.Button + */ + private Button getCloseButton() + { + if( iCloseButton == null ) + { + try + { + iCloseButton = new Button(); + iCloseButton.setName( "CloseButton" ); + iCloseButton.setLabel( "Close" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iCloseButton; + } + + /** + * Return the CommandButtonPanel property value. + * + * @return java.awt.Panel + */ + private Panel getCommandButtonPanel() + { + if( iCommandButtonPanel == null ) + { + try + { + iCommandButtonPanel = new Panel(); + iCommandButtonPanel.setName( "CommandButtonPanel" ); + iCommandButtonPanel.setLayout( getCommandButtonPanelFlowLayout() ); + iCommandButtonPanel.setBackground( SystemColor.control ); + iCommandButtonPanel.add( getReloadButton() ); + iCommandButtonPanel.add( getBuildButton() ); + iCommandButtonPanel.add( getStopButton() ); + iCommandButtonPanel.add( getCloseButton() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iCommandButtonPanel; + } + + /** + * Return the CommandButtonPanelFlowLayout property value. + * + * @return java.awt.FlowLayout + */ + private FlowLayout getCommandButtonPanelFlowLayout() + { + FlowLayout iCommandButtonPanelFlowLayout = null; + try + { + /* + * Create part + */ + iCommandButtonPanelFlowLayout = new FlowLayout(); + iCommandButtonPanelFlowLayout.setAlignment( FlowLayout.RIGHT ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + ; + return iCommandButtonPanelFlowLayout; + } + + /** + * Return the ContentsPane property value. + * + * @return java.awt.Panel + */ + private Panel getContentsPane() + { + if( iContentsPane == null ) + { + try + { + iContentsPane = new Panel(); + iContentsPane.setName( "ContentsPane" ); + iContentsPane.setLayout( new BorderLayout() ); + getContentsPane().add( getCommandButtonPanel(), "South" ); + getContentsPane().add( getOptionenPanel(), "Center" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iContentsPane; + } + + /** + * Return the FileDialog property value. + * + * @return java.awt.FileDialog + */ + private FileDialog getFileDialog() + { + if( iFileDialog == null ) + { + try + { + iFileDialog = new FileDialog( this ); + iFileDialog.setName( "FileDialog" ); + iFileDialog.setLayout( null ); + centerDialog( iFileDialog ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iFileDialog; + } + + /** + * Return the FileMenu property value. + * + * @return java.awt.Menu + */ + private Menu getFileMenu() + { + if( iFileMenu == null ) + { + try + { + iFileMenu = new Menu(); + iFileMenu.setLabel( "File" ); + iFileMenu.add( getSaveMenuItem() ); + iFileMenu.add( getMenuSeparator() ); + iFileMenu.add( getShowLogMenuItem() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iFileMenu; + } + + /** + * Return the HelpMenu property value. + * + * @return java.awt.Menu + */ + private Menu getHelpMenu() + { + if( iHelpMenu == null ) + { + try + { + iHelpMenu = new Menu(); + iHelpMenu.setLabel( "Help" ); + iHelpMenu.add( getAboutMenuItem() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iHelpMenu; + } + + /** + * Return the MenuSeparator1 property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getMenuSeparator() + { + if( iMenuSeparator == null ) + { + try + { + iMenuSeparator = new MenuItem(); + iMenuSeparator.setLabel( "-" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMenuSeparator; + } + + /** + * Return the MessageClearLogButton property value. + * + * @return java.awt.Button + */ + private Button getMessageClearLogButton() + { + if( iMessageClearLogButton == null ) + { + try + { + iMessageClearLogButton = new Button(); + iMessageClearLogButton.setName( "MessageClearLogButton" ); + iMessageClearLogButton.setLabel( "Clear Log" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageClearLogButton; + } + + /** + * Return the MessageCommandPanel property value. + * + * @return java.awt.Panel + */ + private Panel getMessageCommandPanel() + { + if( iMessageCommandPanel == null ) + { + try + { + iMessageCommandPanel = new Panel(); + iMessageCommandPanel.setName( "MessageCommandPanel" ); + iMessageCommandPanel.setLayout( new FlowLayout() ); + getMessageCommandPanel().add( getMessageClearLogButton(), getMessageClearLogButton().getName() ); + getMessageCommandPanel().add( getMessageOkButton(), getMessageOkButton().getName() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageCommandPanel; + } + + /** + * Return the MessageContentPanel property value. + * + * @return java.awt.Panel + */ + private Panel getMessageContentPanel() + { + if( iMessageContentPanel == null ) + { + try + { + iMessageContentPanel = new Panel(); + iMessageContentPanel.setName( "MessageContentPanel" ); + iMessageContentPanel.setLayout( new BorderLayout() ); + iMessageContentPanel.setBackground( SystemColor.control ); + getMessageContentPanel().add( getMessageTextArea(), "Center" ); + getMessageContentPanel().add( getMessageCommandPanel(), "South" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageContentPanel; + } + + /** + * Return the MessageFrame property value. + * + * @return java.awt.Frame + */ + private Frame getMessageFrame() + { + if( iMessageFrame == null ) + { + try + { + iMessageFrame = new Frame(); + iMessageFrame.setName( "MessageFrame" ); + iMessageFrame.setLayout( new BorderLayout() ); + iMessageFrame.setBounds( 0, 0, 750, 250 ); + iMessageFrame.setTitle( "Message Log" ); + iMessageFrame.add( getMessageContentPanel(), "Center" ); + iMessageFrame.setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( iMessageFrame.getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageFrame; + } + + /** + * Return the MessageOkButton property value. + * + * @return java.awt.Button + */ + private Button getMessageOkButton() + { + if( iMessageOkButton == null ) + { + try + { + iMessageOkButton = new Button(); + iMessageOkButton.setName( "MessageOkButton" ); + iMessageOkButton.setLabel( "Close" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOkButton; + } + + /** + * Return the MessageOutputLevelChoice property value. + * + * @return java.awt.Choice + */ + private Choice getMessageOutputLevelChoice() + { + if( iMessageOutputLevelChoice == null ) + { + try + { + iMessageOutputLevelChoice = new Choice(); + iMessageOutputLevelChoice.setName( "MessageOutputLevelChoice" ); + iMessageOutputLevelChoice.add( "Error" ); + iMessageOutputLevelChoice.add( "Warning" ); + iMessageOutputLevelChoice.add( "Info" ); + iMessageOutputLevelChoice.add( "Verbose" ); + iMessageOutputLevelChoice.add( "Debug" ); + iMessageOutputLevelChoice.select( 2 ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOutputLevelChoice; + } + + /** + * Return the MessageOutputLevelLabel property value. + * + * @return java.awt.Label + */ + private Label getMessageOutputLevelLabel() + { + if( iMessageOutputLevelLabel == null ) + { + try + { + iMessageOutputLevelLabel = new Label(); + iMessageOutputLevelLabel.setName( "MessageOutputLevelLabel" ); + iMessageOutputLevelLabel.setText( "Message Level:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOutputLevelLabel; + } + + /** + * Return the MessageTextArea property value. + * + * @return java.awt.TextArea + */ + private TextArea getMessageTextArea() + { + if( iMessageTextArea == null ) + { + try + { + iMessageTextArea = new TextArea(); + iMessageTextArea.setName( "MessageTextArea" ); + iMessageTextArea.setFont( new Font( "monospaced", 0, 12 ) ); + iMessageTextArea.setText( "" ); + iMessageTextArea.setEditable( false ); + iMessageTextArea.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageTextArea; + } + + /** + * Return the Panel1 property value. + * + * @return java.awt.Panel + */ + private Panel getOptionenPanel() + { + if( iOptionenPanel == null ) + { + try + { + iOptionenPanel = new Panel(); + iOptionenPanel.setName( "OptionenPanel" ); + iOptionenPanel.setLayout( new GridBagLayout() ); + iOptionenPanel.setBackground( SystemColor.control ); + + GridBagConstraints constraintsProjectLabel = new GridBagConstraints(); + constraintsProjectLabel.gridx = 0; + constraintsProjectLabel.gridy = 0; + constraintsProjectLabel.anchor = GridBagConstraints.WEST; + constraintsProjectLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getProjectLabel(), constraintsProjectLabel ); + + GridBagConstraints constraintsBuildFileLabel = new GridBagConstraints(); + constraintsBuildFileLabel.gridx = 0; + constraintsBuildFileLabel.gridy = 1; + constraintsBuildFileLabel.anchor = GridBagConstraints.WEST; + constraintsBuildFileLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBuildFileLabel(), constraintsBuildFileLabel ); + + GridBagConstraints constraintsTargetLabel = new GridBagConstraints(); + constraintsTargetLabel.gridx = 0; + constraintsTargetLabel.gridy = 2; + constraintsTargetLabel.anchor = GridBagConstraints.NORTHWEST; + constraintsTargetLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getTargetLabel(), constraintsTargetLabel ); + + GridBagConstraints constraintsProjectText = new GridBagConstraints(); + constraintsProjectText.gridx = 1; + constraintsProjectText.gridy = 0; + constraintsProjectText.gridwidth = 2; + constraintsProjectText.fill = GridBagConstraints.HORIZONTAL; + constraintsProjectText.anchor = GridBagConstraints.WEST; + constraintsProjectText.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getProjectText(), constraintsProjectText ); + + GridBagConstraints constraintsBuildFileTextField = new GridBagConstraints(); + constraintsBuildFileTextField.gridx = 1; + constraintsBuildFileTextField.gridy = 1; + constraintsBuildFileTextField.fill = GridBagConstraints.HORIZONTAL; + constraintsBuildFileTextField.anchor = GridBagConstraints.WEST; + constraintsBuildFileTextField.weightx = 1.0; + constraintsBuildFileTextField.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBuildFileTextField(), constraintsBuildFileTextField ); + + GridBagConstraints constraintsBrowseButton = new GridBagConstraints(); + constraintsBrowseButton.gridx = 2; + constraintsBrowseButton.gridy = 1; + constraintsBrowseButton.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBrowseButton(), constraintsBrowseButton ); + + GridBagConstraints constraintsTargetList = new GridBagConstraints(); + constraintsTargetList.gridx = 1; + constraintsTargetList.gridy = 2; + constraintsTargetList.gridheight = 2; + constraintsTargetList.fill = GridBagConstraints.BOTH; + constraintsTargetList.weightx = 1.0; + constraintsTargetList.weighty = 1.0; + constraintsTargetList.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getTargetList(), constraintsTargetList ); + + GridBagConstraints constraintsMessageOutputLevelLabel = new GridBagConstraints(); + constraintsMessageOutputLevelLabel.gridx = 0; + constraintsMessageOutputLevelLabel.gridy = 4; + constraintsMessageOutputLevelLabel.anchor = GridBagConstraints.WEST; + constraintsMessageOutputLevelLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getMessageOutputLevelLabel(), constraintsMessageOutputLevelLabel ); + + GridBagConstraints constraintsMessageOutputLevelChoice = new GridBagConstraints(); + constraintsMessageOutputLevelChoice.gridx = 1; + constraintsMessageOutputLevelChoice.gridy = 4; + constraintsMessageOutputLevelChoice.fill = GridBagConstraints.HORIZONTAL; + constraintsMessageOutputLevelChoice.anchor = GridBagConstraints.WEST; + constraintsMessageOutputLevelChoice.weightx = 1.0; + constraintsMessageOutputLevelChoice.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getMessageOutputLevelChoice(), constraintsMessageOutputLevelChoice ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iOptionenPanel; + } + + /** + * Return the ProjectLabel property value. + * + * @return java.awt.Label + */ + private Label getProjectLabel() + { + if( iProjectLabel == null ) + { + try + { + iProjectLabel = new Label(); + iProjectLabel.setName( "ProjectLabel" ); + iProjectLabel.setText( "Projectname:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iProjectLabel; + } + + /** + * Return the ProjectText property value. + * + * @return java.awt.Label + */ + private Label getProjectText() + { + if( iProjectText == null ) + { + try + { + iProjectText = new Label(); + iProjectText.setName( "ProjectText" ); + iProjectText.setText( " " ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iProjectText; + } + + /** + * Return the ReloadButton property value. + * + * @return java.awt.Button + */ + private Button getReloadButton() + { + if( iReloadButton == null ) + { + try + { + iReloadButton = new Button(); + iReloadButton.setName( "ReloadButton" ); + iReloadButton.setLabel( "(Re)Load" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iReloadButton; + } + + /** + * Return the SaveMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getSaveMenuItem() + { + if( iSaveMenuItem == null ) + { + try + { + iSaveMenuItem = new MenuItem(); + iSaveMenuItem.setLabel( "Save BuildInfo To Repository" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iSaveMenuItem; + } + + /** + * Return the ShowLogMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getShowLogMenuItem() + { + if( iShowLogMenuItem == null ) + { + try + { + iShowLogMenuItem = new MenuItem(); + iShowLogMenuItem.setLabel( "Log" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iShowLogMenuItem; + } + + /** + * Return the StopButton property value. + * + * @return java.awt.Button + */ + private Button getStopButton() + { + if( iStopButton == null ) + { + try + { + iStopButton = new Button(); + iStopButton.setName( "StopButton" ); + iStopButton.setLabel( "Stop" ); + iStopButton.setEnabled( false ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iStopButton; + } + + /** + * Return the TargetLabel property value. + * + * @return java.awt.Label + */ + private Label getTargetLabel() + { + if( iTargetLabel == null ) + { + try + { + iTargetLabel = new Label(); + iTargetLabel.setName( "TargetLabel" ); + iTargetLabel.setText( "Target:" ); + iTargetLabel.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iTargetLabel; + } + + /** + * Return the TargetList property value. + * + * @return java.awt.List + */ + private List getTargetList() + { + if( iTargetList == null ) + { + try + { + iTargetList = new List(); + iTargetList.setName( "TargetList" ); + iTargetList.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iTargetList; + } + + /** + * connectBuildFileNameToTextField: (BuildInfo.buildFileName <--> + * BuildFileTextField.text) + */ + private void connectBuildFileNameToTextField() + { + /* + * Set the target from the source + */ + try + { + if( iConnPtoP2Aligning == false ) + { + iConnPtoP2Aligning = true; + if( ( getBuildInfo() != null ) ) + { + getBuildFileTextField().setText( getBuildInfo().getBuildFileName() ); + } + iConnPtoP2Aligning = false; + } + } + catch( Throwable iExc ) + { + iConnPtoP2Aligning = false; + handleException( iExc ); + } + } + + /** + * connectProjectNameToLabel: (BuildInfo.vajProjectName <--> + * ProjectText.text) + */ + private void connectProjectNameToLabel() + { + /* + * Set the target from the source + */ + try + { + if( ( getBuildInfo() != null ) ) + { + getProjectText().setText( getBuildInfo().getVAJProjectName() ); + } + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + + /** + * connectTextFieldToBuildFileName: (BuildInfo.buildFileName <--> + * BuildFileTextField.text) + */ + private void connectTextFieldToBuildFileName() + { + /* + * Set the source from the target + */ + try + { + if( iConnPtoP2Aligning == false ) + { + iConnPtoP2Aligning = true; + if( ( getBuildInfo() != null ) ) + { + getBuildInfo().setBuildFileName( getBuildFileTextField().getText() ); + } + iConnPtoP2Aligning = false; + } + } + catch( Throwable iExc ) + { + iConnPtoP2Aligning = false; + handleException( iExc ); + } + } + + /** + * external build of a .jar-file + */ + private void executeTarget() + { + try + { + getMessageFrame().show(); + getBuildInfo().executeProject( logger ); + } + catch( Throwable exc ) + { + logger.logException( exc ); + } + return; + } + + /** + * Fills the taget-list with project-targets + */ + private void fillList() + { + getTargetList().removeAll(); + Vector targets = getBuildInfo().getProjectTargets(); + for( int i = 0; i < targets.size(); i++ ) + { + getTargetList().add( targets.elementAt( i ).toString() ); + } + getTargetList().select( iBuildInfo.getProjectTargets().indexOf( iBuildInfo.getTarget() ) ); + if( getTargetList().getSelectedIndex() >= 0 ) + { + getBuildButton().setEnabled( true ); + } + } + + /** + * Called whenever the part throws an exception. + * + * @param exception Throwable + */ + private void handleException( Throwable exception ) + { + // Write exceptions to the log-window + String trace = StringUtils.getStackTrace( exception ); + + getMessageTextArea().append( lineSeparator + lineSeparator + trace ); + getMessageFrame().show(); + + } + + /** + * Initializes connections + * + * @exception Exception The exception description. + */ + private void initConnections() + throws Exception + { + this.addWindowListener( iEventHandler ); + getBrowseButton().addActionListener( iEventHandler ); + getCloseButton().addActionListener( iEventHandler ); + getBuildButton().addActionListener( iEventHandler ); + getStopButton().addActionListener( iEventHandler ); + getSaveMenuItem().addActionListener( iEventHandler ); + getAboutOkButton().addActionListener( iEventHandler ); + getAboutMenuItem().addActionListener( iEventHandler ); + getMessageOkButton().addActionListener( iEventHandler ); + getMessageClearLogButton().addActionListener( iEventHandler ); + getMessageOkButton().addActionListener( iEventHandler ); + getShowLogMenuItem().addActionListener( iEventHandler ); + getAboutDialog().addWindowListener( iEventHandler ); + getMessageFrame().addWindowListener( iEventHandler ); + getReloadButton().addActionListener( iEventHandler ); + getTargetList().addItemListener( iEventHandler ); + getMessageOutputLevelChoice().addItemListener( iEventHandler ); + getBuildFileTextField().addTextListener( iEventHandler ); + connectProjectNameToLabel(); + connectBuildFileNameToTextField(); + } + + /** + * Initialize the class. + */ + private void initialize() + { + try + { + setName( "AntMake" ); + setMenuBar( getAntMakeMenuBar() ); + setLayout( new java.awt.BorderLayout() ); + setSize( 389, 222 ); + setTitle( "Ant VisualAge for Java Tool-Integration" ); + add( getContentsPane(), "Center" ); + initConnections(); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) - ( getSize().height ) ); + if( ( getTargetList().getItemCount() == 0 ) || ( getTargetList().getSelectedIndex() < 0 ) ) + { + getBuildButton().setEnabled( false ); + } + } + + /** + * Saves the build-informations to repository + */ + private void saveBuildInfo() + { + try + { + VAJAntTool.saveBuildData( getBuildInfo() ); + } + catch( Throwable exc ) + { + // This Exception occurs when you try to write into a versioned project + handleException( exc ); + } + return; + } + + /** + * Eventhandler to handle all AWT-events + * + * @author RT + */ + private class PrivateEventHandler implements ActionListener, ItemListener, TextListener, WindowListener, PropertyChangeListener + { + /** + * ActionListener method + * + * @param e Description of Parameter + */ + public void actionPerformed( ActionEvent e ) + { + try + { + /* + * #### Main App-Frame #### + */ + // browse XML-File with filechooser + if( e.getSource() == VAJAntToolGUI.this.getBrowseButton() ) + { + getFileDialog().setDirectory( getBuildFileTextField().getText().substring( 0, getBuildFileTextField().getText().lastIndexOf( '\\' ) + 1 ) ); + getFileDialog().setFile( "*.xml" ); + getFileDialog().show(); + if( !getFileDialog().getFile().equals( "" ) ) + { + getBuildFileTextField().setText( getFileDialog().getDirectory() + getFileDialog().getFile() ); + } + } + // dispose and exit application + if( e.getSource() == VAJAntToolGUI.this.getCloseButton() ) + { + dispose(); + System.exit( 0 ); + } + // start build-process + if( e.getSource() == VAJAntToolGUI.this.getBuildButton() ) + { + executeTarget(); + } + if( e.getSource() == VAJAntToolGUI.this.getStopButton() ) + { + getBuildInfo().cancelBuild(); + } + if( e.getSource() == VAJAntToolGUI.this.getReloadButton() ) + { + try + { + getBuildInfo().updateTargetList(); + fillList(); + } + catch( Throwable fileNotFound ) + { + handleException( fileNotFound ); + getTargetList().removeAll(); + getBuildButton().setEnabled( false ); + } + } + // MenuItems + if( e.getSource() == VAJAntToolGUI.this.getSaveMenuItem() ) + saveBuildInfo(); + if( e.getSource() == VAJAntToolGUI.this.getAboutMenuItem() ) + getAboutDialog().show(); + if( e.getSource() == VAJAntToolGUI.this.getShowLogMenuItem() ) + getMessageFrame().show(); + /* + * #### About dialog #### + */ + if( e.getSource() == VAJAntToolGUI.this.getAboutOkButton() ) + getAboutDialog().dispose(); + /* + * #### Log frame #### + */ + if( e.getSource() == VAJAntToolGUI.this.getMessageOkButton() ) + getMessageFrame().dispose(); + if( e.getSource() == VAJAntToolGUI.this.getMessageClearLogButton() ) + getMessageTextArea().setText( "" ); + if( e.getSource() == VAJAntToolGUI.this.getMessageOkButton() ) + getMessageFrame().dispose(); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + /** + * ItemListener method + * + * @param e Description of Parameter + */ + public void itemStateChanged( ItemEvent e ) + { + try + { + if( e.getSource() == VAJAntToolGUI.this.getTargetList() ) + getBuildButton().setEnabled( true ); + if( e.getSource() == VAJAntToolGUI.this.getMessageOutputLevelChoice() ) + getBuildInfo().setOutputMessageLevel( getMessageOutputLevelChoice().getSelectedIndex() ); + if( e.getSource() == VAJAntToolGUI.this.getTargetList() ) + getBuildInfo().setTarget( getTargetList().getSelectedItem() ); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + /** + * PropertyChangeListener method + * + * @param evt Description of Parameter + */ + public void propertyChange( java.beans.PropertyChangeEvent evt ) + { + if( evt.getSource() == VAJAntToolGUI.this.getBuildInfo() && ( evt.getPropertyName().equals( "projectName" ) ) ) + connectProjectNameToLabel(); + if( evt.getSource() == VAJAntToolGUI.this.getBuildInfo() && ( evt.getPropertyName().equals( "buildFileName" ) ) ) + connectBuildFileNameToTextField(); + } + + /** + * TextListener method + * + * @param e Description of Parameter + */ + public void textValueChanged( TextEvent e ) + { + if( e.getSource() == VAJAntToolGUI.this.getBuildFileTextField() ) + connectTextFieldToBuildFileName(); + } + + public void windowActivated( WindowEvent e ) { } + + public void windowClosed( WindowEvent e ) { } + + /** + * WindowListener methods + * + * @param e Description of Parameter + */ + public void windowClosing( WindowEvent e ) + { + try + { + if( e.getSource() == VAJAntToolGUI.this ) + { + dispose(); + System.exit( 0 ); + } + if( e.getSource() == VAJAntToolGUI.this.getAboutDialog() ) + getAboutDialog().dispose(); + if( e.getSource() == VAJAntToolGUI.this.getMessageFrame() ) + getMessageFrame().dispose(); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + public void windowDeactivated( WindowEvent e ) { } + + public void windowDeiconified( WindowEvent e ) { } + + public void windowIconified( WindowEvent e ) { } + + public void windowOpened( WindowEvent e ) { } + } + + /** + * This internal BuildLogger, to be honest, is just a BuildListener. It does + * nearly the same as the DefaultLogger, but uses the Loggin-Window for + * output. + * + * @author RT + */ + private class VAJBuildLogger implements BuildListener + { + private long startTime = System.currentTimeMillis(); + + /** + * VAJBuildLogger constructor comment. + */ + public VAJBuildLogger() + { + super(); + } + + /** + * Fired after the last target has finished. This event will still be + * thrown if an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void buildFinished( BuildEvent event ) + { + getStopButton().setEnabled( false ); + getBuildButton().setEnabled( true ); + getBuildButton().requestFocus(); + + Throwable error = event.getException(); + + if( error == null ) + { + getMessageTextArea().append( lineSeparator + "BUILD SUCCESSFUL" ); + } + else + { + logException( error ); + } + + getMessageTextArea().append( lineSeparator + "Total time: " + formatTime( System.currentTimeMillis() - startTime ) ); + } + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + public void buildStarted( BuildEvent event ) + { + getStopButton().setEnabled( true ); + getBuildButton().setEnabled( false ); + getStopButton().requestFocus(); + + startTime = System.currentTimeMillis(); + getMessageTextArea().append( lineSeparator ); + } + + + /** + * Outputs an exception. + * + * @param error Description of Parameter + */ + public void logException( Throwable error ) + { + getMessageTextArea().append( lineSeparator + "BUILD FAILED" + lineSeparator ); + + if( error instanceof BuildException ) + { + getMessageTextArea().append( error.toString() ); + + Throwable nested = ( ( BuildException )error ).getCause(); + if( nested != null ) + { + nested.printStackTrace( System.err ); + } + } + else + { + error.printStackTrace( System.err ); + } + } + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + * @see BuildEvent#getMessage() + * @see BuildEvent#getPriority() + */ + public void messageLogged( BuildEvent event ) + { + if( event.getPriority() <= getBuildInfo().getOutputMessageLevel() ) + { + String msg = ""; + if( event.getTask() != null ) + msg = "[" + event.getTask().getTaskName() + "] "; + getMessageTextArea().append( lineSeparator + msg + event.getMessage() ); + } + } + + /** + * Fired when a target has finished. This event will still be thrown if + * an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void targetFinished( BuildEvent event ) { } + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTarget() + */ + public void targetStarted( BuildEvent event ) + { + if( getBuildInfo().getOutputMessageLevel() <= Project.MSG_INFO ) + { + getMessageTextArea().append( lineSeparator + event.getTarget().getName() + ":" ); + } + } + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void taskFinished( BuildEvent event ) { } + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTask() + */ + public void taskStarted( BuildEvent event ) { } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java new file mode 100644 index 000000000..a504426bb --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java @@ -0,0 +1,585 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Target; + +/** + * This class wraps the Ant project information needed to start Ant from Visual + * Age. It serves the following purposes: - acts as model for AntMakeFrame - + * converts itself to/from String (to store the information as ToolData in the + * VA repository) - wraps Project functions for the GUI (get target list, + * execute target) - manages a seperate thread for Ant project execution this + * allows interrupting a running build from a GUI + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +class VAJBuildInfo implements Runnable +{ + + // name of the VA project this BuildInfo belongs to + private String vajProjectName = ""; + + // name of the Ant build file + private String buildFileName = ""; + + // main targets found in the build file + private Vector projectTargets = new Vector(); + + // target selected for execution + private java.lang.String target = ""; + + // log level + private int outputMessageLevel = Project.MSG_INFO; + + // is true if Project initialization was successful + private transient boolean projectInitialized = false; + + // Support for bound properties + protected transient PropertyChangeSupport propertyChange; + + // thread for Ant build execution + private Thread buildThread; + + // Ant Project created from build file + private transient Project project; + + // the listener used to log output. + private BuildListener projectLogger; + + /** + * Creates a BuildInfo object from a String The String must be in the format + * outputMessageLevel'|'buildFileName'|'defaultTarget'|'(project target'|')* + * + * @param data java.lang.String + * @return org.apache.tools.ant.taskdefs.optional.vaj.BuildInfo + */ + public static VAJBuildInfo parse( String data ) + { + VAJBuildInfo result = new VAJBuildInfo(); + + try + { + StringTokenizer tok = new StringTokenizer( data, "|" ); + result.setOutputMessageLevel( tok.nextToken() ); + result.setBuildFileName( tok.nextToken() ); + result.setTarget( tok.nextToken() ); + while( tok.hasMoreTokens() ) + { + result.projectTargets.addElement( tok.nextToken() ); + } + } + catch( Throwable t ) + { + // if parsing the info fails, just return + // an empty VAJBuildInfo + } + return result; + } + + /** + * Search for the insert position to keep names a sorted list of Strings + * This method has been copied from org.apache.tools.ant.Main + * + * @param names Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private static int findTargetPosition( Vector names, String name ) + { + int res = names.size(); + for( int i = 0; i < names.size() && res == names.size(); i++ ) + { + if( name.compareTo( ( String )names.elementAt( i ) ) < 0 ) + { + res = i; + } + } + return res; + } + + /** + * Sets the build file name + * + * @param newBuildFileName The new BuildFileName value + */ + public void setBuildFileName( String newBuildFileName ) + { + String oldValue = buildFileName; + buildFileName = newBuildFileName; + setProjectInitialized( false ); + firePropertyChange( "buildFileName", oldValue, buildFileName ); + } + + /** + * Sets the log level (value must be one of the constants in Project) + * + * @param newOutputMessageLevel The new OutputMessageLevel value + */ + public void setOutputMessageLevel( int newOutputMessageLevel ) + { + int oldValue = outputMessageLevel; + outputMessageLevel = newOutputMessageLevel; + firePropertyChange( "outputMessageLevel", + new Integer( oldValue ), new Integer( outputMessageLevel ) ); + } + + /** + * Sets the target to execute when executeBuild is called + * + * @param newTarget build target + */ + public void setTarget( String newTarget ) + { + String oldValue = target; + target = newTarget; + firePropertyChange( "target", oldValue, target ); + } + + /** + * Sets the name of the Visual Age for Java project where this BuildInfo + * belongs to + * + * @param newVAJProjectName The new VAJProjectName value + */ + public void setVAJProjectName( String newVAJProjectName ) + { + String oldValue = vajProjectName; + vajProjectName = newVAJProjectName; + firePropertyChange( "VAJProjectName", oldValue, vajProjectName ); + } + + /** + * Returns the build file name. + * + * @return build file name. + */ + public String getBuildFileName() + { + return buildFileName; + } + + /** + * Returns the log level + * + * @return log level. + */ + public int getOutputMessageLevel() + { + return outputMessageLevel; + } + + /** + * return a list of all targets in the current buildfile + * + * @return The ProjectTargets value + */ + public Vector getProjectTargets() + { + return projectTargets; + } + + /** + * returns the selected target. + * + * @return The Target value + */ + public java.lang.String getTarget() + { + return target; + } + + /** + * returns the VA project name + * + * @return The VAJProjectName value + */ + public String getVAJProjectName() + { + return vajProjectName; + } + + /** + * Returns true, if the Ant project is initialized (i.e. buildfile loaded) + * + * @return The ProjectInitialized value + */ + public boolean isProjectInitialized() + { + return projectInitialized; + } + + + /** + * The addPropertyChangeListener method was generated to support the + * propertyChange field. + * + * @param listener The feature to be added to the PropertyChangeListener + * attribute + */ + public synchronized void addPropertyChangeListener( PropertyChangeListener listener ) + { + getPropertyChange().addPropertyChangeListener( listener ); + } + + /** + * Returns the BuildInfo information as String. The BuildInfo can be rebuilt + * from that String by calling parse(). + * + * @return java.lang.String + */ + public String asDataString() + { + String result = getOutputMessageLevel() + "|" + getBuildFileName() + + "|" + getTarget(); + for( Enumeration e = getProjectTargets().elements(); + e.hasMoreElements(); ) + { + result = result + "|" + e.nextElement(); + } + + return result; + } + + + /** + * cancels a build. + */ + public void cancelBuild() + { + buildThread.interrupt(); + } + + /** + * Executes the target set by setTarget(). + * + * @param logger Description of Parameter + */ + public void executeProject( BuildListener logger ) + { + Throwable error; + projectLogger = logger; + try + { + buildThread = new Thread( this ); + buildThread.setPriority( Thread.MIN_PRIORITY ); + buildThread.start(); + } + catch( RuntimeException exc ) + { + error = exc; + throw exc; + } + catch( Error err ) + { + error = err; + throw err; + } + } + + /** + * The firePropertyChange method was generated to support the propertyChange + * field. + * + * @param propertyName Description of Parameter + * @param oldValue Description of Parameter + * @param newValue Description of Parameter + */ + public void firePropertyChange( java.lang.String propertyName, java.lang.Object oldValue, java.lang.Object newValue ) + { + getPropertyChange().firePropertyChange( propertyName, oldValue, newValue ); + } + + /** + * The removePropertyChangeListener method was generated to support the + * propertyChange field. + * + * @param listener Description of Parameter + */ + public synchronized void removePropertyChangeListener( PropertyChangeListener listener ) + { + getPropertyChange().removePropertyChangeListener( listener ); + } + + /** + * Executes a build. This method is executed by the Ant execution thread + */ + public void run() + { + try + { + InterruptedChecker ic = new InterruptedChecker( projectLogger ); + BuildEvent e = new BuildEvent( getProject() ); + try + { + ic.buildStarted( e ); + + if( !isProjectInitialized() ) + { + initProject(); + } + + project.addBuildListener( ic ); + project.executeTarget( target ); + + ic.buildFinished( e ); + } + catch( Throwable t ) + { + e.setException( t ); + ic.buildFinished( e ); + } + finally + { + project.removeBuildListener( ic ); + } + } + catch( Throwable t2 ) + { + System.out.println( "unexpected exception!" ); + t2.printStackTrace(); + } + } + + /** + * reloads the build file and updates the target list + */ + public void updateTargetList() + { + project = new Project(); + initProject(); + projectTargets.removeAllElements(); + Enumeration ptargets = project.getTargets().elements(); + while( ptargets.hasMoreElements() ) + { + Target currentTarget = ( Target )ptargets.nextElement(); + if( currentTarget.getDescription() != null ) + { + String targetName = currentTarget.getName(); + int pos = findTargetPosition( projectTargets, targetName ); + projectTargets.insertElementAt( targetName, pos ); + } + } + } + + /** + * Accessor for the propertyChange field. + * + * @return The PropertyChange value + */ + protected PropertyChangeSupport getPropertyChange() + { + if( propertyChange == null ) + { + propertyChange = new PropertyChangeSupport( this ); + } + return propertyChange; + } + + /** + * Sets the log level (value must be one of the constants in Project) + * + * @param outputMessageLevel log level as String. + */ + private void setOutputMessageLevel( String outputMessageLevel ) + { + int level = Integer.parseInt( outputMessageLevel ); + setOutputMessageLevel( level ); + } + + /** + * sets the initialized flag + * + * @param initialized The new ProjectInitialized value + */ + private void setProjectInitialized( boolean initialized ) + { + Boolean oldValue = new Boolean( projectInitialized ); + projectInitialized = initialized; + firePropertyChange( "projectInitialized", oldValue, new Boolean( projectInitialized ) ); + } + + /** + * Returns the Ant project + * + * @return org.apache.tools.ant.Project + */ + private Project getProject() + { + if( project == null ) + { + project = new Project(); + } + return project; + } + + /** + * Initializes the Ant project. Assumes that the project attribute is + * already set. + */ + private void initProject() + { + try + { + project.init(); + File buildFile = new File( getBuildFileName() ); + project.setUserProperty( "ant.file", buildFile.getAbsolutePath() ); + ProjectHelper.configureProject( project, buildFile ); + setProjectInitialized( true ); + } + catch( RuntimeException exc ) + { + setProjectInitialized( false ); + throw exc; + } + catch( Error err ) + { + setProjectInitialized( false ); + throw err; + } + } + + /** + * This exception is thrown when a build is interrupted + * + * @author RT + */ + public static class BuildInterruptedException extends BuildException + { + public String toString() + { + return "BUILD INTERRUPTED"; + } + } + + /** + * BuildListener which checks for interruption and throws Exception if build + * process is interrupted. This class is a wrapper around a 'real' listener. + * + * @author RT + */ + private class InterruptedChecker implements BuildListener + { + // the real listener + BuildListener wrappedListener; + + /** + * Can only be constructed as wrapper around a real listener + * + * @param listener the real listener + */ + public InterruptedChecker( BuildListener listener ) + { + super(); + wrappedListener = listener; + } + + /** + * Fired after the last target has finished. This event will still be + * thrown if an error occured during the build. + * + * @param event Description of Parameter + */ + public void buildFinished( BuildEvent event ) + { + wrappedListener.buildFinished( event ); + checkInterrupted(); + } + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + public void buildStarted( BuildEvent event ) + { + wrappedListener.buildStarted( event ); + checkInterrupted(); + } + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + */ + public void messageLogged( BuildEvent event ) + { + wrappedListener.messageLogged( event ); + checkInterrupted(); + } + + /** + * Fired when a target has finished. This event will still be thrown if + * an error occured during the build. + * + * @param event Description of Parameter + */ + public void targetFinished( BuildEvent event ) + { + wrappedListener.targetFinished( event ); + checkInterrupted(); + } + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + */ + public void targetStarted( BuildEvent event ) + { + wrappedListener.targetStarted( event ); + checkInterrupted(); + } + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + */ + public void taskFinished( BuildEvent event ) + { + wrappedListener.taskFinished( event ); + checkInterrupted(); + } + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + */ + public void taskStarted( BuildEvent event ) + { + wrappedListener.taskStarted( event ); + checkInterrupted(); + } + + /** + * checks if the thread was interrupted. When an interrupt occured, + * throw an Exception to stop the execution. + */ + protected void checkInterrupted() + { + if( buildThread.isInterrupted() ) + { + throw new BuildInterruptedException(); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java new file mode 100644 index 000000000..4d49c923d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.PatternSet; + +/** + * Export packages from the Visual Age for Java workspace. The packages are + * specified similar to all other MatchingTasks. Since the VA Workspace is not + * file based, this task is simulating a directory hierarchy for the workspace: + * The 'root' contains all project 'dir's, and the projects contain their + * respective package 'dir's. Example:

              <vajexport + * destdir="C:/builddir/source">  <include + * name="/MyVAProject/org/foo/subsystem1/**" />  <exclude + * name="/MyVAProject/org/foo/subsystem1/test/**"/> </vajexport> + *
              exports all packages in the project MyVAProject which start + * with 'org.foo.subsystem1' except of these starting with + * 'org.foo.subsystem1.test'. There are flags to choose which items to export: + * exportSources: export Java sources exportResources: export project resources + * exportClasses: export class files exportDebugInfo: export class files with + * debug info (use with exportClasses) default is exporting Java files and + * resources. + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJExport extends VAJTask +{ + protected boolean exportSources = true; + protected boolean exportResources = true; + protected boolean exportClasses = false; + protected boolean exportDebugInfo = false; + protected boolean useDefaultExcludes = true; + protected boolean overwrite = true; + + protected PatternSet patternSet = new PatternSet(); + //set set... method comments for description + protected File destDir; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Set the destination directory into which the selected items should be + * exported + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. Currently only patterns denoting packages are supported + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + patternSet.setExcludes( excludes ); + } + + /** + * if exportClasses is set, class files are exported + * + * @param doExport The new ExportClasses value + */ + public void setExportClasses( boolean doExport ) + { + exportClasses = doExport; + } + + /** + * if exportDebugInfo is set, the exported class files contain debug info + * + * @param doExport The new ExportDebugInfo value + */ + public void setExportDebugInfo( boolean doExport ) + { + exportDebugInfo = doExport; + } + + /** + * if exportResources is set, resource file will be exported + * + * @param doExport The new ExportResources value + */ + public void setExportResources( boolean doExport ) + { + exportResources = doExport; + } + + /** + * if exportSources is set, java files will be exported + * + * @param doExport The new ExportSources value + */ + public void setExportSources( boolean doExport ) + { + exportSources = doExport; + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space.Currently only patterns denoting packages are supported + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + patternSet.setIncludes( includes ); + } + + /** + * if Overwrite is set, files will be overwritten during export + * + * @param doOverwrite The new Overwrite value + */ + public void setOverwrite( boolean doOverwrite ) + { + overwrite = doOverwrite; + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + return patternSet.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + return patternSet.createInclude(); + } + + /** + * do the export + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a destdir + if( destDir == null ) + { + throw new BuildException( "destdir attribute must be set!" ); + } + + // delegate the export to the VAJUtil object. + getUtil().exportPackages( destDir, + patternSet.getIncludePatterns( getProject() ), + patternSet.getExcludePatterns( getProject() ), + exportClasses, exportDebugInfo, + exportResources, exportSources, + useDefaultExcludes, overwrite ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java new file mode 100644 index 000000000..a78a51efc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; + +/** + * A Remote Access to Tools Servlet to extract package sets from the Workbench + * to the local file system. The following table describes the servlet + * parameters. + * + * + * + * + * + * Parameter + * + * + * + * Values + * + * + * + * Description + * + * + * + * + * + * + * + * dir + * + * + * + * Any valid directory name on the server. + * + * + * + * The directory to export the files to on the machine where the servlet + * is being run. If the directory doesn't exist, it will be created.

              + * + * Relative paths are relative to IBMVJava/ide/tools/com-ibm-ivj-toolserver, + * where IBMVJava is the VisualAge for Java installation directory. + * + * + * + * + * + * + * + * include + * + * + * + * See below. + * + * + * + * The pattern used to indicate which projects and packages to export. + * + * + * + * + * + * + * + * + * exclude + * + * + * + * See below + * + * + * + * The pattern used to indicate which projects and packages not + * to export. + * + * + * + * + * + * + * + * cls + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export class files. Defaults to "no". + * + * + * + * + * + * + * + * src + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export source files. Defaults to "yes". + * + * + * + * + * + * + * + * res + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export resource files associated with the included project(s). Defaults + * to "yes". + * + * + * + * + * + * + * + * dex + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Use the default exclusion patterns. Defaults to "yes". See below for an + * explanation of default excludes. + * + * + * + * + * + * + * + * owr + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Overwrite any existing files. Defaults to "yes". + * + * + * + * + * + *

              + * + * The vajexport servlet uses include and exclude parameters to form the + * criteria for selecting packages to export. The parameter is broken up into + * ProjectName/packageNameSegments, where ProjectName is what you expect, and + * packageNameSegments is a partial (or complete) package name, separated by + * forward slashes, rather than periods. Each packageNameSegment can have + * wildcard characters.

              + * + * + * + * + * + * Wildcard Characters + * + * + * + * Description + * + * + * + * + * + * + * + * * + * + * + * + * Match zero or more characters in that segment. + * + * + * + * + * + * + * + * ? + * + * + * + * Match one character in that segment. + * + * + * + * + * + * + * + * ** + * + * + * + * Matches all characters in zero or more segments. + * + * + * + * + * + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJExportServlet extends VAJToolsServlet +{ + // constants for servlet param names + public final static String WITH_DEBUG_INFO = "deb"; + public final static String OVERWRITE_PARAM = "owr"; + + /** + * Respond to a request to export packages from the Workbench. + */ + protected void executeRequest() + { + getUtil().exportPackages( + new File( getFirstParamValueString( DIR_PARAM ) ), + getParamValues( INCLUDE_PARAM ), + getParamValues( EXCLUDE_PARAM ), + getBooleanParam( CLASSES_PARAM, false ), + getBooleanParam( WITH_DEBUG_INFO, false ), + getBooleanParam( RESOURCES_PARAM, true ), + getBooleanParam( SOURCES_PARAM, true ), + getBooleanParam( DEFAULT_EXCLUDES_PARAM, true ), + getBooleanParam( OVERWRITE_PARAM, true ) + ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java new file mode 100644 index 000000000..36d63bbf8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.lang.reflect.Field; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.types.FileSet; + +/** + * Import source, class files, and resources to the Visual Age for Java + * workspace using FileSets.

              + * + * Example:

              + * <vajimport project="MyVAProject">
              + *   <fileset dir="src">
              + *     <include name="org/foo/subsystem1/**" />
              + *     <exclude name="/org/foo/subsystem1/test/**" />
              + *  </fileset>
              + * </vajexport>
              + * 
              import all source and resource files from the "src" directory which + * start with 'org.foo.subsystem1', except of these starting with + * 'org.foo.subsystem1.test' into the project MyVAProject.

              + * + * If MyVAProject isn't loaded into the Workspace, a new edition is created in + * the repository and automatically loaded into the Workspace. There has to be + * at least one nested FileSet element.

              + * + * There are attributes to choose which items to export: + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * project + * + * + * + * the name of the Project to import to + * + * + * + * Yes + * + * + * + * + * + * + * + * importSources + * + * + * + * import Java sources, defaults to "yes" + * + * + * + * No + * + * + * + * + * + * + * + * importResources + * + * + * + * import resource files (anything that doesn't end with .java or .class), + * defaults to "yes" + * + * + * + * No + * + * + * + * + * + * + * + * importClasses + * + * + * + * import class files, defaults to "no" + * + * + * + * No + * + * + * + * + * + * + * + * @author RT + * @author: Glenn McAllister, inspired by a similar task written by Peter Kelley + */ +public class VAJImport extends VAJTask +{ + protected Vector filesets = new Vector(); + protected boolean importSources = true; + protected boolean importResources = true; + protected boolean importClasses = false; + protected String importProject = null; + protected boolean useDefaultExcludes = true; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Import .class files. + * + * @param importClasses The new ImportClasses value + */ + public void setImportClasses( boolean importClasses ) + { + this.importClasses = importClasses; + } + + /** + * Import resource files (anything that doesn't end in .class or .java) + * + * @param importResources The new ImportResources value + */ + public void setImportResources( boolean importResources ) + { + this.importResources = importResources; + } + + /** + * Import .java files + * + * @param importSources The new ImportSources value + */ + public void setImportSources( boolean importSources ) + { + this.importSources = importSources; + } + + + /** + * The VisualAge for Java Project name to import into. + * + * @param projectName The new Project value + */ + public void setProject( String projectName ) + { + this.importProject = projectName; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Do the import. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( filesets.size() == 0 ) + { + throw new BuildException( "At least one fileset is required!" ); + } + + if( importProject == null || "".equals( importProject ) ) + { + throw new BuildException( "The VisualAge for Java Project name is required!" ); + } + + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + importFileset( ( FileSet )e.nextElement() ); + } + } + + /** + * Import all files from the fileset into the Project in the Workspace. + * + * @param fileset Description of Parameter + */ + protected void importFileset( FileSet fileset ) + { + DirectoryScanner ds = fileset.getDirectoryScanner( this.project ); + if( ds.getIncludedFiles().length == 0 ) + { + return; + } + + String[] includes = null; + String[] excludes = null; + + // Hack to get includes and excludes. We could also use getIncludedFiles, + // but that would result in very long HTTP-requests. + // Therefore we want to send the patterns only to the remote tool server + // and let him figure out the files. + try + { + Class directoryScanner = ds.getClass(); + + Field includesField = directoryScanner.getDeclaredField( "includes" ); + includesField.setAccessible( true ); + includes = ( String[] )includesField.get( ds ); + + Field excludesField = directoryScanner.getDeclaredField( "excludes" ); + excludesField.setAccessible( true ); + excludes = ( String[] )excludesField.get( ds ); + } + catch( NoSuchFieldException nsfe ) + { + throw new BuildException( + "DirectoryScanner.includes or .excludes missing" + nsfe.getMessage() ); + } + catch( IllegalAccessException iae ) + { + throw new BuildException( + "Access to DirectoryScanner.includes or .excludes not allowed" ); + } + + getUtil().importFiles( importProject, ds.getBasedir(), + includes, excludes, + importClasses, importResources, importSources, + useDefaultExcludes ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java new file mode 100644 index 000000000..9ea01067e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; + + +/** + * A Remote Access to Tools Servlet to import a Project from files into the + * Repository. The following table describes the servlet parameters. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
              + * Parameter + * + * Description + *
              + * project + * + * The name of the project where you want the imported items to go. + *
              + * dir + * + * The directory you want to import from. + *
              + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJImportServlet extends VAJToolsServlet +{ + /** + * Respond to a request to import files to the Repository + */ + protected void executeRequest() + { + getUtil().importFiles( + getFirstParamValueString( PROJECT_NAME_PARAM ), + new File( getFirstParamValueString( DIR_PARAM ) ), + getParamValues( INCLUDE_PARAM ), + getParamValues( EXCLUDE_PARAM ), + getBooleanParam( CLASSES_PARAM, false ), + getBooleanParam( RESOURCES_PARAM, true ), + getBooleanParam( SOURCES_PARAM, true ), + false// no default excludes, because they + // are already added on client side + // getBooleanParam(DEFAULT_EXCLUDES_PARAM, true) + ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java new file mode 100644 index 000000000..d42912f1f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.util.Vector; + +/** + * Load specific project versions into the Visual Age for Java workspace. Each + * project and version name has to be specified completely. Example: + *

              <vajload>  <project name="MyVAProject" + * version="2.1"/>  <project name="Apache Xerces" version="1.2.0"/> + * </vajload>
              + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJLoad extends VAJTask +{ + Vector projectDescriptions = new Vector(); + + /** + * Add a project description entry on the project list. + * + * @return Description of the Returned Value + */ + public VAJProjectDescription createVAJProject() + { + VAJProjectDescription d = new VAJProjectDescription(); + projectDescriptions.addElement( d ); + return d; + } + + /** + * Load specified projects. + */ + public void execute() + { + getUtil().loadProjects( projectDescriptions ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java new file mode 100644 index 000000000..86c9f8a79 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; + + + + + + +/** + * This is only there for backward compatibility with the default task list and + * will be removed soon + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJLoadProjects extends VAJLoad +{ +} + + + + + + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java new file mode 100644 index 000000000..1fca4d63d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.util.Vector; + +/** + * A Remote Access to Tools Servlet to load a Project from the Repository into + * the Workbench. The following table describes the servlet parameters. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
              + * Parameter + * + * Description + *
              + * project + * + * The name of the Project you want to load into the Workbench. + *
              + * version + * + * The version of the package you want to load into the Workbench. + *
              + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJLoadServlet extends VAJToolsServlet +{ + + // constants for servlet param names + public final static String VERSION_PARAM = "version"; + + /** + * Respond to a request to load a project from the Repository into the + * Workbench. + */ + protected void executeRequest() + { + String[] projectNames = getParamValues( PROJECT_NAME_PARAM ); + String[] versionNames = getParamValues( VERSION_PARAM ); + + Vector projectDescriptions = new Vector( projectNames.length ); + for( int i = 0; i < projectNames.length && i < versionNames.length; i++ ) + { + VAJProjectDescription desc = new VAJProjectDescription(); + desc.setName( projectNames[i] ); + desc.setVersion( versionNames[i] ); + projectDescriptions.addElement( desc ); + } + + util.loadProjects( projectDescriptions ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java new file mode 100644 index 000000000..247c2427a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java @@ -0,0 +1,530 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.ExportCodeSpec; +import com.ibm.ivj.util.base.ImportCodeSpec; +import com.ibm.ivj.util.base.IvjException; +import com.ibm.ivj.util.base.Package; +import com.ibm.ivj.util.base.Project; +import com.ibm.ivj.util.base.ProjectEdition; +import com.ibm.ivj.util.base.ToolEnv; +import com.ibm.ivj.util.base.Type; +import com.ibm.ivj.util.base.Workspace; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; + + +/** + * Helper class for VAJ tasks. Holds Workspace singleton and wraps IvjExceptions + * into BuildExceptions + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +abstract class VAJLocalUtil implements VAJUtil +{ + // singleton containing the VAJ workspace + private static Workspace workspace; + + /** + * get a project from the Workspace. + * + * @param importProject Description of Parameter + * @return The VAJProject value + */ + static Project getVAJProject( String importProject ) + { + Project found = null; + Project[] currentProjects = getWorkspace().getProjects(); + + for( int i = 0; i < currentProjects.length; i++ ) + { + Project p = currentProjects[i]; + if( p.getName().equals( importProject ) ) + { + found = p; + break; + } + } + + if( found == null ) + { + try + { + found = getWorkspace().createProject( importProject, true ); + } + catch( IvjException e ) + { + throw createBuildException( "Error while creating Project " + + importProject + ": ", e ); + } + } + + return found; + } + + /** + * returns the current VAJ workspace. + * + * @return com.ibm.ivj.util.base.Workspace + */ + static Workspace getWorkspace() + { + if( workspace == null ) + { + workspace = ToolEnv.connectToWorkspace(); + if( workspace == null ) + { + throw new BuildException( + "Unable to connect to Workspace! " + + "Make sure you are running in VisualAge for Java." ); + } + } + + return workspace; + } + + /** + * Wraps IvjException into a BuildException + * + * @param errMsg Additional error message + * @param e IvjException which is wrapped + * @return org.apache.tools.ant.BuildException + */ + static BuildException createBuildException( + String errMsg, IvjException e ) + { + errMsg = errMsg + "\n" + e.getMessage(); + String[] errors = e.getErrors(); + if( errors != null ) + { + for( int i = 0; i < errors.length; i++ ) + { + errMsg = errMsg + "\n" + errors[i]; + } + } + return new BuildException( errMsg, e ); + } + + + //----------------------------------------------------------- + // export + //----------------------------------------------------------- + + /** + * export packages + * + * @param dest Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + public void exportPackages( + File dest, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, + boolean exportResources, boolean exportSources, + boolean useDefaultExcludes, boolean overwrite ) + { + if( includePatterns == null || includePatterns.length == 0 ) + { + log( "You must specify at least one include attribute. " + + "Not exporting", MSG_ERR ); + } + else + { + try + { + VAJWorkspaceScanner scanner = new VAJWorkspaceScanner(); + scanner.setIncludes( includePatterns ); + scanner.setExcludes( excludePatterns ); + if( useDefaultExcludes ) + { + scanner.addDefaultExcludes(); + } + scanner.scan(); + + Package[] packages = scanner.getIncludedPackages(); + + log( "Exporting " + packages.length + " package(s) to " + + dest, MSG_INFO ); + for( int i = 0; i < packages.length; i++ ) + { + log( " " + packages[i].getName(), MSG_VERBOSE ); + } + + ExportCodeSpec exportSpec = new ExportCodeSpec(); + exportSpec.setPackages( packages ); + exportSpec.includeJava( exportSources ); + exportSpec.includeClass( exportClasses ); + exportSpec.includeResources( exportResources ); + exportSpec.includeClassDebugInfo( exportDebugInfo ); + exportSpec.useSubdirectories( true ); + exportSpec.overwriteFiles( overwrite ); + exportSpec.setExportDirectory( dest.getAbsolutePath() ); + + getWorkspace().exportData( exportSpec ); + } + catch( IvjException ex ) + { + throw createBuildException( "Exporting failed!", ex ); + } + } + } + + + //----------------------------------------------------------- + // import + //----------------------------------------------------------- + + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @exception BuildException Description of Exception + */ + public void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ) + throws BuildException + { + + if( importProject == null || "".equals( importProject ) ) + { + throw new BuildException( "The VisualAge for Java project " + + "name is required!" ); + } + + ImportCodeSpec importSpec = new ImportCodeSpec(); + importSpec.setDefaultProject( getVAJProject( importProject ) ); + + DirectoryScanner ds = new DirectoryScanner(); + ds.setBasedir( srcDir ); + ds.setIncludes( includePatterns ); + ds.setExcludes( excludePatterns ); + if( useDefaultExcludes ) + { + ds.addDefaultExcludes(); + } + ds.scan(); + + Vector classes = new Vector(); + Vector sources = new Vector(); + Vector resources = new Vector(); + + scanForImport( srcDir, ds.getIncludedFiles(), classes, sources, resources ); + + StringBuffer summaryLog = new StringBuffer( "Importing " ); + addFilesToImport( importSpec, importClasses, classes, "Class", summaryLog ); + addFilesToImport( importSpec, importSources, sources, "Java", summaryLog ); + addFilesToImport( importSpec, importResources, resources, "Resource", summaryLog ); + importSpec.setResourcePath( srcDir.getAbsolutePath() ); + + summaryLog.append( " into the project '" ); + summaryLog.append( importProject ); + summaryLog.append( "'." ); + + log( summaryLog.toString(), MSG_INFO ); + + try + { + Type[] importedTypes = getWorkspace().importData( importSpec ); + if( importedTypes == null ) + { + throw new BuildException( "Unable to import into Workspace!" ); + } + else + { + log( importedTypes.length + " types imported", MSG_DEBUG ); + for( int i = 0; i < importedTypes.length; i++ ) + { + log( importedTypes[i].getPackage().getName() + + "." + importedTypes[i].getName() + + " into " + importedTypes[i].getProject().getName(), + MSG_DEBUG ); + } + } + } + catch( IvjException ivje ) + { + throw createBuildException( "Error while importing into workspace: ", + ivje ); + } + } + + + //----------------------------------------------------------- + // load + //----------------------------------------------------------- + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + public void loadProjects( Vector projectDescriptions ) + { + Vector expandedDescs = getExpandedDescriptions( projectDescriptions ); + + // output warnings for projects not found + for( Enumeration e = projectDescriptions.elements(); e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + if( !d.projectFound() ) + { + log( "No Projects match the name " + d.getName(), MSG_WARN ); + } + } + + log( "Loading " + expandedDescs.size() + + " project(s) into workspace", MSG_INFO ); + + for( Enumeration e = expandedDescs.elements(); + e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + + ProjectEdition pe = findProjectEdition( d.getName(), d.getVersion() ); + try + { + log( "Loading '" + d.getName() + "', Version '" + d.getVersion() + + "', into Workspace", MSG_VERBOSE ); + pe.loadIntoWorkspace(); + } + catch( IvjException ex ) + { + throw createBuildException( "Project '" + d.getName() + + "' could not be loaded.", ex ); + } + } + } + + + /** + * return project descriptions containing full project names instead of + * patterns with wildcards. + * + * @param projectDescs Description of Parameter + * @return The ExpandedDescriptions value + */ + private Vector getExpandedDescriptions( Vector projectDescs ) + { + Vector expandedDescs = new Vector( projectDescs.size() ); + try + { + String[] projectNames = + getWorkspace().getRepository().getProjectNames(); + for( int i = 0; i < projectNames.length; i++ ) + { + for( Enumeration e = projectDescs.elements(); + e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + String pattern = d.getName(); + if( VAJWorkspaceScanner.match( pattern, projectNames[i] ) ) + { + d.setProjectFound(); + expandedDescs.addElement( new VAJProjectDescription( + projectNames[i], d.getVersion() ) ); + break; + } + } + } + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + return expandedDescs; + } + + /** + * Adds files to an import specification. Helper method for importFiles() + * + * @param spec import specification + * @param doImport only add files if doImport is true + * @param files the files to add + * @param fileType type of files (Source/Class/Resource) + * @param summaryLog buffer for logging + */ + private void addFilesToImport( + ImportCodeSpec spec, boolean doImport, + Vector files, String fileType, + StringBuffer summaryLog ) + { + + if( doImport ) + { + String[] fileArr = new String[files.size()]; + files.copyInto( fileArr ); + try + { + // here it is assumed that fileType is one of the + // following strings: // "Java", "Class", "Resource" + String methodName = "set" + fileType + "Files"; + Class[] methodParams = new Class[]{fileArr.getClass()}; + java.lang.reflect.Method method = + spec.getClass().getDeclaredMethod( methodName, methodParams ); + method.invoke( spec, new Object[]{fileArr} ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + if( files.size() > 0 ) + { + logFiles( files, fileType ); + summaryLog.append( files.size() ); + summaryLog.append( " " + fileType.toLowerCase() + " file" ); + summaryLog.append( files.size() > 1 ? "s, " : ", " ); + } + } + } + + /** + * returns a list of project names matching the given pattern + * + * @param pattern Description of Parameter + * @return Description of the Returned Value + */ + private Vector findMatchingProjects( String pattern ) + { + String[] projectNames; + try + { + projectNames = getWorkspace().getRepository().getProjectNames(); + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + Vector matchingProjects = new Vector(); + for( int i = 0; i < projectNames.length; i++ ) + { + if( VAJWorkspaceScanner.match( pattern, projectNames[i] ) ) + { + matchingProjects.addElement( projectNames[i] ); + } + } + + return matchingProjects; + } + + /** + * Finds a specific project edition in the repository. + * + * @param name project name + * @param versionName project version name + * @return com.ibm.ivj.util.base.ProjectEdition the specified edition + */ + private ProjectEdition findProjectEdition( + String name, String versionName ) + { + try + { + ProjectEdition[] editions = null; + editions = getWorkspace().getRepository().getProjectEditions( name ); + + if( editions == null ) + { + throw new BuildException( "Project " + name + " doesn't exist" ); + } + + ProjectEdition pe = null; + for( int i = 0; i < editions.length && pe == null; i++ ) + { + if( versionName.equals( editions[i].getVersionName() ) ) + { + pe = editions[i]; + } + } + if( pe == null ) + { + throw new BuildException( "Version " + versionName + + " of Project " + name + " doesn't exist" ); + } + return pe; + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + } + + /** + * Logs a list of file names to the message log + * + * @param fileNames java.util.Vector file names to be logged + * @param fileType Description of Parameter + */ + private void logFiles( Vector fileNames, String fileType ) + { + log( fileType + " files found for import:", MSG_VERBOSE ); + for( Enumeration e = fileNames.elements(); e.hasMoreElements(); ) + { + log( " " + e.nextElement(), MSG_VERBOSE ); + } + } + + + /** + * Sort the files into classes, sources, and resources. + * + * @param dir Description of Parameter + * @param files Description of Parameter + * @param classes Description of Parameter + * @param sources Description of Parameter + * @param resources Description of Parameter + */ + private void scanForImport( + File dir, + String[] files, + Vector classes, + Vector sources, + Vector resources ) + { + for( int i = 0; i < files.length; i++ ) + { + String file = ( new File( dir, files[i] ) ).getAbsolutePath(); + if( file.endsWith( ".java" ) || file.endsWith( ".JAVA" ) ) + { + sources.addElement( file ); + } + else + if( file.endsWith( ".class" ) || file.endsWith( ".CLASS" ) ) + { + classes.addElement( file ); + } + else + { + // for resources VA expects the path relative to the resource path + resources.addElement( files[i] ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java new file mode 100644 index 000000000..9e2105863 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import org.apache.tools.ant.BuildException; + +/** + * Type class. Holds information about a project edition. + * + * @author RT + * @author: Wolf Siberski + */ +public class VAJProjectDescription +{ + private String name; + private boolean projectFound; + private String version; + + public VAJProjectDescription() { } + + public VAJProjectDescription( String n, String v ) + { + name = n; + version = v; + } + + public void setName( String newName ) + { + if( newName == null || newName.equals( "" ) ) + { + throw new BuildException( "name attribute must be set" ); + } + name = newName; + } + + public void setProjectFound() + { + projectFound = true; + } + + public void setVersion( String newVersion ) + { + if( newVersion == null || newVersion.equals( "" ) ) + { + throw new BuildException( "version attribute must be set" ); + } + version = newVersion; + } + + public String getName() + { + return name; + } + + public String getVersion() + { + return version; + } + + public boolean projectFound() + { + return projectFound; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java new file mode 100644 index 000000000..c14253574 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Helper class for VAJ tasks. Holds Workspace singleton and wraps IvjExceptions + * into BuildExceptions + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +class VAJRemoteUtil implements VAJUtil +{ + // calling task + Task caller; + + // VAJ remote tool server + String remoteServer; + + public VAJRemoteUtil( Task caller, String remote ) + { + this.caller = caller; + this.remoteServer = remote; + } + + /** + * export the array of Packages + * + * @param destDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + public void exportPackages( File destDir, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, boolean exportResources, + boolean exportSources, boolean useDefaultExcludes, boolean overwrite ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajexport?" + + VAJExportServlet.WITH_DEBUG_INFO + "=" + exportDebugInfo + "&" + + VAJExportServlet.OVERWRITE_PARAM + "=" + overwrite + "&" + + assembleImportExportParams( destDir, + includePatterns, excludePatterns, + exportClasses, exportResources, + exportSources, useDefaultExcludes ); + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + } + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + */ + public void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajimport?" + + VAJImportServlet.PROJECT_NAME_PARAM + "=" + + importProject + "&" + + assembleImportExportParams( srcDir, + includePatterns, excludePatterns, + importClasses, importResources, + importSources, useDefaultExcludes ); + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + + } + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + public void loadProjects( Vector projectDescriptions ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajload?"; + String delimiter = ""; + for( Enumeration e = projectDescriptions.elements(); e.hasMoreElements(); ) + { + VAJProjectDescription pd = ( VAJProjectDescription )e.nextElement(); + request = request + + delimiter + VAJLoadServlet.PROJECT_NAME_PARAM + + "=" + pd.getName().replace( ' ', '+' ) + + "&" + VAJLoadServlet.VERSION_PARAM + + "=" + pd.getVersion().replace( ' ', '+' ); + //the first param needs no delimiter, but all other + delimiter = "&"; + } + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + } + + /** + * logs a message. + * + * @param msg Description of Parameter + * @param level Description of Parameter + */ + public void log( String msg, int level ) + { + caller.log( msg, level ); + } + + /** + * Assemble string for parameters common for import and export Helper method + * to remove double code. + * + * @param dir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param includeClasses Description of Parameter + * @param includeResources Description of Parameter + * @param includeSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @return Description of the Returned Value + */ + private String assembleImportExportParams( + File dir, + String[] includePatterns, String[] excludePatterns, + boolean includeClasses, boolean includeResources, + boolean includeSources, boolean useDefaultExcludes ) + { + String result = + VAJToolsServlet.DIR_PARAM + "=" + + dir.getAbsolutePath().replace( '\\', '/' ) + "&" + + VAJToolsServlet.CLASSES_PARAM + "=" + includeClasses + "&" + + VAJToolsServlet.RESOURCES_PARAM + "=" + includeResources + "&" + + VAJToolsServlet.SOURCES_PARAM + "=" + includeSources + "&" + + VAJToolsServlet.DEFAULT_EXCLUDES_PARAM + "=" + useDefaultExcludes; + + if( includePatterns != null ) + { + for( int i = 0; i < includePatterns.length; i++ ) + { + result = result + "&" + VAJExportServlet.INCLUDE_PARAM + "=" + + includePatterns[i].replace( ' ', '+' ).replace( '\\', '/' ); + } + } + if( excludePatterns != null ) + { + for( int i = 0; i < excludePatterns.length; i++ ) + { + result = result + "&" + VAJExportServlet.EXCLUDE_PARAM + "=" + + excludePatterns[i].replace( ' ', '+' ).replace( '\\', '/' ); + } + } + + return result; + } + + /** + * Sends a servlet request. + * + * @param request Description of Parameter + */ + private void sendRequest( String request ) + { + boolean requestFailed = false; + try + { + log( "Request: " + request, MSG_DEBUG ); + + //must be HTTP connection + URL requestUrl = new URL( request ); + HttpURLConnection connection = + ( HttpURLConnection )requestUrl.openConnection(); + + InputStream is = null; + // retry three times + for( int i = 0; i < 3; i++ ) + { + try + { + is = connection.getInputStream(); + break; + } + catch( IOException ex ) + { + } + } + if( is == null ) + { + log( "Can't get " + request, MSG_ERR ); + throw new BuildException( "Couldn't execute " + request ); + } + + // log the response + BufferedReader br = new BufferedReader( new InputStreamReader( is ) ); + String line = br.readLine(); + while( line != null ) + { + int level = MSG_ERR; + try + { + // the first char of each line contains the log level + level = Integer.parseInt( line.substring( 0, 1 ) ); + if( level == MSG_ERR ) + { + requestFailed = true; + } + } + catch( Exception e ) + { + log( "Response line doesn't contain log level!", MSG_ERR ); + } + log( line.substring( 2 ), level ); + line = br.readLine(); + } + + } + catch( IOException ex ) + { + log( "Error sending tool request to VAJ" + ex, MSG_ERR ); + throw new BuildException( "Couldn't execute " + request ); + } + if( requestFailed ) + { + throw new BuildException( "VAJ tool request failed" ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java new file mode 100644 index 000000000..3c19052ec --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +/** + * Super class for all VAJ tasks. Contains common attributes (remoteServer) and + * util methods + * + * @author: Wolf Siberski + */ +import org.apache.tools.ant.Task; + + +public class VAJTask extends Task +{ + + // server name / port of VAJ remote tool api server + protected String remoteServer = null; + + // holds the appropriate VAJUtil implementation + private VAJUtil util = null; + + /** + * Set remote server attribute + * + * @param remoteServer The new Remote value + */ + public void setRemote( String remoteServer ) + { + this.remoteServer = remoteServer; + } + + + /** + * returns the VAJUtil implementation + * + * @return The Util value + */ + protected VAJUtil getUtil() + { + if( util == null ) + { + if( remoteServer == null ) + { + util = new VAJLocalToolUtil(); + } + else + { + util = new VAJRemoteUtil( this, remoteServer ); + } + } + return util; + } + + /** + * Adaption of VAJLocalUtil to Task context. + * + * @author RT + */ + class VAJLocalToolUtil extends VAJLocalUtil + { + public void log( String msg, int level ) + { + VAJTask.this.log( msg, level ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java new file mode 100644 index 000000000..c27e163b9 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.StringUtils; + +/** + * Abstract base class to provide common services for the VAJ tool API servlets + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public abstract class VAJToolsServlet extends HttpServlet +{ + + // constants for servlet param names + public final static String DIR_PARAM = "dir"; + public final static String INCLUDE_PARAM = "include"; + public final static String EXCLUDE_PARAM = "exclude"; + public final static String CLASSES_PARAM = "cls"; + public final static String SOURCES_PARAM = "src"; + public final static String RESOURCES_PARAM = "res"; + public final static String DEFAULT_EXCLUDES_PARAM = "dex"; + public final static String PROJECT_NAME_PARAM = "project"; + + // current request + HttpServletRequest request; + + // response to current request + HttpServletResponse response; + + // implementation of VAJUtil used by the servlet + VAJUtil util; + + /** + * Respond to a HTTP request. This method initializes the servlet and + * handles errors. The real work is done in the abstract method + * executeRequest() + * + * @param req Description of Parameter + * @param res Description of Parameter + * @exception ServletException Description of Exception + * @exception IOException Description of Exception + */ + public void doGet( HttpServletRequest req, HttpServletResponse res ) + throws ServletException, IOException + { + try + { + response = res; + request = req; + initRequest(); + executeRequest(); + } + catch( BuildException e ) + { + util.log( "Error occured: " + e.getMessage(), VAJUtil.MSG_ERR ); + } + catch( Exception e ) + { + try + { + if( !( e instanceof BuildException ) ) + { + String trace = StringUtils.getStackTrace( e ); + util.log( "Program error in " + this.getClass().getName() + + ":\n" + trace, VAJUtil.MSG_ERR ); + } + } + catch( Throwable t ) + { + t.printStackTrace(); + } + finally + { + if( !( e instanceof BuildException ) ) + { + throw new ServletException( e.getMessage() ); + } + } + } + } + + /** + * Get the boolean value of a parameter. + * + * @param param Description of Parameter + * @return The BooleanParam value + */ + protected boolean getBooleanParam( String param ) + { + return getBooleanParam( param, false ); + } + + /** + * Get the boolean value of a parameter, with a default value if the + * parameter hasn't been passed to the servlet. + * + * @param param Description of Parameter + * @param defaultValue Description of Parameter + * @return The BooleanParam value + */ + protected boolean getBooleanParam( String param, boolean defaultValue ) + { + String value = getFirstParamValueString( param ); + if( value != null ) + { + return toBoolean( value ); + } + else + { + return defaultValue; + } + } + + /** + * Returns the first encountered value for a parameter. + * + * @param param Description of Parameter + * @return The FirstParamValueString value + */ + protected String getFirstParamValueString( String param ) + { + String[] paramValuesArray = request.getParameterValues( param ); + if( paramValuesArray == null ) + { + return null; + } + return paramValuesArray[0]; + } + + /** + * Returns all values for a parameter. + * + * @param param Description of Parameter + * @return The ParamValues value + */ + protected String[] getParamValues( String param ) + { + return request.getParameterValues( param ); + } + + + /** + * Execute the request by calling the appropriate VAJ tool API methods. This + * method must be implemented by the concrete servlets + */ + protected abstract void executeRequest(); + + /** + * initialize the servlet. + * + * @exception IOException Description of Exception + */ + protected void initRequest() + throws IOException + { + response.setContentType( "text/ascii" ); + if( util == null ) + { + util = new VAJLocalServletUtil(); + } + } + + /** + * A utility method to translate the strings "yes", "true", and "ok" to + * boolean true, and everything else to false. + * + * @param string Description of Parameter + * @return Description of the Returned Value + */ + protected boolean toBoolean( String string ) + { + String lower = string.toLowerCase(); + return ( lower.equals( "yes" ) || lower.equals( "true" ) || lower.equals( "ok" ) ); + } + + /** + * Get the VAJUtil implementation + * + * @return The Util value + */ + VAJUtil getUtil() + { + return util; + } + + /** + * Adaptation of VAJUtil for servlet context. + * + * @author RT + */ + class VAJLocalServletUtil extends VAJLocalUtil + { + public void log( String msg, int level ) + { + try + { + if( msg != null ) + { + msg = msg.replace( '\r', ' ' ); + int i = 0; + while( i < msg.length() ) + { + int nlPos = msg.indexOf( '\n', i ); + if( nlPos == -1 ) + { + nlPos = msg.length(); + } + response.getWriter().println( Integer.toString( level ) + + " " + msg.substring( i, nlPos ) ); + i = nlPos + 1; + } + } + } + catch( IOException e ) + { + throw new BuildException( "logging failed. msg was: " + + e.getMessage() ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java new file mode 100644 index 000000000..203b97252 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; +import java.util.Vector; + +/** + * Helper interface for VAJ tasks. Encapsulates the interface to the VAJ tool + * API. + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +interface VAJUtil +{ + // log levels + public final static int MSG_DEBUG = 4; + public final static int MSG_ERR = 0; + public final static int MSG_INFO = 2; + public final static int MSG_VERBOSE = 3; + public final static int MSG_WARN = 1; + + /** + * export the array of Packages + * + * @param dest Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + void exportPackages( + File dest, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, + boolean exportResources, boolean exportSources, + boolean useDefaultExcludes, boolean overwrite ); + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + */ + void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ); + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + void loadProjects( Vector projectDescriptions ); + + /** + * Logs a message with the specified log level. + * + * @param msg Description of Parameter + * @param level Description of Parameter + */ + void log( String msg, int level ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java new file mode 100644 index 000000000..9624722af --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.IvjException; +import com.ibm.ivj.util.base.Package; +import com.ibm.ivj.util.base.Project; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; + +/** + * Class for scanning a Visual Age for Java workspace for packages matching a + * certain criteria.

              + * + * These criteria consist of a set of include and exclude patterns. With these + * patterns, you can select which packages you want to have included, and which + * packages you want to have excluded. You can add patterns to be excluded by + * default with the addDefaultExcludes method. The patters that are excluded by + * default include + *

                + *
              • IBM*\**
              • + *
              • Java class libraries\**
              • + *
              • Sun class libraries*\**
              • + *
              • JSP Page Compile Generated Code\**
              • + *
              • VisualAge*\**
              • + *
              + *

              + * + * This class works like DirectoryScanner. + * + * @author Wolf Siberski, TUI Infotec (based on Arnout J. Kuipers + * DirectoryScanner) + * @see org.apache.tools.ant.DirectoryScanner + */ +class VAJWorkspaceScanner extends DirectoryScanner +{ + + // Patterns that should be excluded by default. + private final static String[] DEFAULTEXCLUDES = + { + "IBM*/**", + "Java class libraries/**", + "Sun class libraries*/**", + "JSP Page Compile Generated Code/**", + "VisualAge*/**", + }; + + // The packages that where found and matched at least + // one includes, and matched no excludes. + private Vector packagesIncluded = new Vector(); + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @return true when the string matches against the pattern, + * false otherwise. + */ + protected static boolean match( String pattern, String str ) + { + return DirectoryScanner.match( pattern, str ); + } + + /** + * Get the names of the packages that matched at least one of the include + * patterns, and didn't match one of the exclude patterns. + * + * @return the matching packages + */ + public Package[] getIncludedPackages() + { + int count = packagesIncluded.size(); + Package[] packages = new Package[count]; + for( int i = 0; i < count; i++ ) + { + packages[i] = ( Package )packagesIncluded.elementAt( i ); + } + return packages; + } + + /** + * Adds the array with default exclusions to the current exclusions set. + */ + public void addDefaultExcludes() + { + int excludesLength = excludes == null ? 0 : excludes.length; + String[] newExcludes; + newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length]; + if( excludesLength > 0 ) + { + System.arraycopy( excludes, 0, newExcludes, 0, excludesLength ); + } + for( int i = 0; i < DEFAULTEXCLUDES.length; i++ ) + { + newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i]. + replace( '/', File.separatorChar ). + replace( '\\', File.separatorChar ); + } + excludes = newExcludes; + } + + /** + * Finds all Projects specified in include patterns. + * + * @return the projects + */ + public Vector findMatchingProjects() + { + Project[] projects = VAJLocalUtil.getWorkspace().getProjects(); + + Vector matchingProjects = new Vector(); + + boolean allProjectsMatch = false; + for( int i = 0; i < projects.length; i++ ) + { + Project project = projects[i]; + for( int j = 0; j < includes.length && !allProjectsMatch; j++ ) + { + StringTokenizer tok = + new StringTokenizer( includes[j], File.separator ); + String projectNamePattern = tok.nextToken(); + if( projectNamePattern.equals( "**" ) ) + { + // if an include pattern starts with '**', + // all projects match + allProjectsMatch = true; + } + else + if( match( projectNamePattern, project.getName() ) ) + { + matchingProjects.addElement( project ); + break; + } + } + } + + if( allProjectsMatch ) + { + matchingProjects = new Vector(); + for( int i = 0; i < projects.length; i++ ) + { + matchingProjects.addElement( projects[i] ); + } + } + + return matchingProjects; + } + + /** + * Scans the workspace for packages that match at least one include pattern, + * and don't match any exclude patterns. + */ + public void scan() + { + if( includes == null ) + { + // No includes supplied, so set it to 'matches all' + includes = new String[1]; + includes[0] = "**"; + } + if( excludes == null ) + { + excludes = new String[0]; + } + + // only scan projects which are included in at least one include pattern + Vector matchingProjects = findMatchingProjects(); + for( Enumeration e = matchingProjects.elements(); e.hasMoreElements(); ) + { + Project project = ( Project )e.nextElement(); + scanProject( project ); + } + } + + /** + * Scans a project for packages that match at least one include pattern, and + * don't match any exclude patterns. + * + * @param project Description of Parameter + */ + public void scanProject( Project project ) + { + try + { + Package[] packages = project.getPackages(); + if( packages != null ) + { + for( int i = 0; i < packages.length; i++ ) + { + Package item = packages[i]; + // replace '.' by file seperator because the patterns are + // using file seperator syntax (and we can use the match + // methods this way). + String name = + project.getName() + + File.separator + + item.getName().replace( '.', File.separatorChar ); + if( isIncluded( name ) && !isExcluded( name ) ) + { + packagesIncluded.addElement( item ); + } + } + } + } + catch( IvjException e ) + { + throw VAJLocalUtil.createBuildException( "VA Exception occured: ", e ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/default.ini b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/default.ini new file mode 100644 index 000000000..1ccb8944f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/default.ini @@ -0,0 +1,4 @@ +Name=Ant +Version=0.1 +Help-Item=Ant Help,doc/VAJAntTool.html +Menu-Items=Ant Build,org.apache.tools.ant.taskdefs.optional.ide.VAJAntTool,-P; diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java new file mode 100644 index 000000000..912a18bb9 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.javacc; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/** + * Taskdef for the JJTree compiler compiler. + * + * @author thomas.haas@softwired-inc.com + * @author Michael Saunders michael@amtec.com + * + */ +public class JJTree extends Task +{ + + // keys to optional attributes + private final static String BUILD_NODE_FILES = "BUILD_NODE_FILES"; + private final static String MULTI = "MULTI"; + private final static String NODE_DEFAULT_VOID = "NODE_DEFAULT_VOID"; + private final static String NODE_FACTORY = "NODE_FACTORY"; + private final static String NODE_SCOPE_HOOK = "NODE_SCOPE_HOOK"; + private final static String NODE_USES_PARSER = "NODE_USES_PARSER"; + private final static String STATIC = "STATIC"; + private final static String VISITOR = "VISITOR"; + + private final static String NODE_PACKAGE = "NODE_PACKAGE"; + private final static String VISITOR_EXCEPTION = "VISITOR_EXCEPTION"; + private final static String NODE_PREFIX = "NODE_PREFIX"; + + private final Hashtable optionalAttrs = new Hashtable(); + + // required attributes + private File outputDirectory = null; + private File target = null; + private File javaccHome = null; + + private CommandlineJava cmdl = new CommandlineJava(); + + public JJTree() + { + cmdl.setVm( "java" ); + cmdl.setClassname( "COM.sun.labs.jjtree.Main" ); + } + + + public void setBuildnodefiles( boolean buildNodeFiles ) + { + optionalAttrs.put( BUILD_NODE_FILES, new Boolean( buildNodeFiles ) ); + } + + public void setJavacchome( File javaccHome ) + { + this.javaccHome = javaccHome; + } + + public void setMulti( boolean multi ) + { + optionalAttrs.put( MULTI, new Boolean( multi ) ); + } + + public void setNodedefaultvoid( boolean nodeDefaultVoid ) + { + optionalAttrs.put( NODE_DEFAULT_VOID, new Boolean( nodeDefaultVoid ) ); + } + + public void setNodefactory( boolean nodeFactory ) + { + optionalAttrs.put( NODE_FACTORY, new Boolean( nodeFactory ) ); + } + + public void setNodepackage( String nodePackage ) + { + optionalAttrs.put( NODE_PACKAGE, new String( nodePackage ) ); + } + + public void setNodeprefix( String nodePrefix ) + { + optionalAttrs.put( NODE_PREFIX, new String( nodePrefix ) ); + } + + public void setNodescopehook( boolean nodeScopeHook ) + { + optionalAttrs.put( NODE_SCOPE_HOOK, new Boolean( nodeScopeHook ) ); + } + + public void setNodeusesparser( boolean nodeUsesParser ) + { + optionalAttrs.put( NODE_USES_PARSER, new Boolean( nodeUsesParser ) ); + } + + public void setOutputdirectory( File outputDirectory ) + { + this.outputDirectory = outputDirectory; + } + + public void setStatic( boolean staticParser ) + { + optionalAttrs.put( STATIC, new Boolean( staticParser ) ); + } + + public void setTarget( File target ) + { + this.target = target; + } + + public void setVisitor( boolean visitor ) + { + optionalAttrs.put( VISITOR, new Boolean( visitor ) ); + } + + public void setVisitorException( String visitorException ) + { + optionalAttrs.put( VISITOR_EXCEPTION, new String( visitorException ) ); + } + + public void execute() + throws BuildException + { + + // load command line with optional attributes + Enumeration iter = optionalAttrs.keys(); + while( iter.hasMoreElements() ) + { + String name = ( String )iter.nextElement(); + Object value = optionalAttrs.get( name ); + cmdl.createArgument().setValue( "-" + name + ":" + value.toString() ); + } + + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // use the directory containing the target as the output directory + if( outputDirectory == null ) + { + outputDirectory = new File( target.getParent() ); + } + if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "'outputdirectory' " + outputDirectory + " is not a directory." ); + } + // convert backslashes to slashes, otherwise jjtree will put this as + // comments and this seems to confuse javacc + cmdl.createArgument().setValue( + "-OUTPUT_DIRECTORY:" + outputDirectory.getAbsolutePath().replace( '\\', '/' ) ); + + String targetName = target.getName(); + final File javaFile = new File( outputDirectory, + targetName.substring( 0, targetName.indexOf( ".jjt" ) ) + ".jj" ); + if( javaFile.exists() && target.lastModified() < javaFile.lastModified() ) + { + project.log( "Target is already built - skipping (" + target + ")" ); + return; + } + cmdl.createArgument().setValue( target.getAbsolutePath() ); + + if( javaccHome == null || !javaccHome.isDirectory() ) + { + throw new BuildException( "Javacchome not set." ); + } + final Path classpath = cmdl.createClasspath( project ); + classpath.createPathElement().setPath( javaccHome.getAbsolutePath() + + "/JavaCC.zip" ); + classpath.addJavaRuntime(); + + final Commandline.Argument arg = cmdl.createVmArgument(); + arg.setValue( "-mx140M" ); + arg.setValue( "-Dinstall.root=" + javaccHome.getAbsolutePath() ); + + final Execute process = + new Execute( new LogStreamHandler( this, + Project.MSG_INFO, + Project.MSG_INFO ), + null ); + log( cmdl.toString(), Project.MSG_VERBOSE ); + process.setCommandline( cmdl.getCommandline() ); + + try + { + if( process.execute() != 0 ) + { + throw new BuildException( "JJTree failed." ); + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to launch JJTree: " + e ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java new file mode 100644 index 000000000..f85e9089c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.javacc; +import java.io.File; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/** + * Taskdef for the JavaCC compiler compiler. + * + * @author thomas.haas@softwired-inc.com + * @author Michael Saunders michael@amtec.com + * + */ +public class JavaCC extends Task +{ + + // keys to optional attributes + private final static String LOOKAHEAD = "LOOKAHEAD"; + private final static String CHOICE_AMBIGUITY_CHECK = "CHOICE_AMBIGUITY_CHECK"; + private final static String OTHER_AMBIGUITY_CHECK = "OTHER_AMBIGUITY_CHECK"; + + private final static String STATIC = "STATIC"; + private final static String DEBUG_PARSER = "DEBUG_PARSER"; + private final static String DEBUG_LOOKAHEAD = "DEBUG_LOOKAHEAD"; + private final static String DEBUG_TOKEN_MANAGER = "DEBUG_TOKEN_MANAGER"; + private final static String OPTIMIZE_TOKEN_MANAGER = "OPTIMIZE_TOKEN_MANAGER"; + private final static String ERROR_REPORTING = "ERROR_REPORTING"; + private final static String JAVA_UNICODE_ESCAPE = "JAVA_UNICODE_ESCAPE"; + private final static String UNICODE_INPUT = "UNICODE_INPUT"; + private final static String IGNORE_CASE = "IGNORE_CASE"; + private final static String COMMON_TOKEN_ACTION = "COMMON_TOKEN_ACTION"; + private final static String USER_TOKEN_MANAGER = "USER_TOKEN_MANAGER"; + private final static String USER_CHAR_STREAM = "USER_CHAR_STREAM"; + private final static String BUILD_PARSER = "BUILD_PARSER"; + private final static String BUILD_TOKEN_MANAGER = "BUILD_TOKEN_MANAGER"; + private final static String SANITY_CHECK = "SANITY_CHECK"; + private final static String FORCE_LA_CHECK = "FORCE_LA_CHECK"; + private final static String CACHE_TOKENS = "CACHE_TOKENS"; + + private final Hashtable optionalAttrs = new Hashtable(); + + // required attributes + private File outputDirectory = null; + private File target = null; + private File javaccHome = null; + + private CommandlineJava cmdl = new CommandlineJava(); + + public JavaCC() + { + cmdl.setVm( "java" ); + cmdl.setClassname( "COM.sun.labs.javacc.Main" ); + } + + public void setBuildparser( boolean buildParser ) + { + optionalAttrs.put( BUILD_PARSER, new Boolean( buildParser ) ); + } + + public void setBuildtokenmanager( boolean buildTokenManager ) + { + optionalAttrs.put( BUILD_TOKEN_MANAGER, new Boolean( buildTokenManager ) ); + } + + public void setCachetokens( boolean cacheTokens ) + { + optionalAttrs.put( CACHE_TOKENS, new Boolean( cacheTokens ) ); + } + + public void setChoiceambiguitycheck( int choiceAmbiguityCheck ) + { + optionalAttrs.put( CHOICE_AMBIGUITY_CHECK, new Integer( choiceAmbiguityCheck ) ); + } + + public void setCommontokenaction( boolean commonTokenAction ) + { + optionalAttrs.put( COMMON_TOKEN_ACTION, new Boolean( commonTokenAction ) ); + } + + public void setDebuglookahead( boolean debugLookahead ) + { + optionalAttrs.put( DEBUG_LOOKAHEAD, new Boolean( debugLookahead ) ); + } + + public void setDebugparser( boolean debugParser ) + { + optionalAttrs.put( DEBUG_PARSER, new Boolean( debugParser ) ); + } + + public void setDebugtokenmanager( boolean debugTokenManager ) + { + optionalAttrs.put( DEBUG_TOKEN_MANAGER, new Boolean( debugTokenManager ) ); + } + + public void setErrorreporting( boolean errorReporting ) + { + optionalAttrs.put( ERROR_REPORTING, new Boolean( errorReporting ) ); + } + + public void setForcelacheck( boolean forceLACheck ) + { + optionalAttrs.put( FORCE_LA_CHECK, new Boolean( forceLACheck ) ); + } + + public void setIgnorecase( boolean ignoreCase ) + { + optionalAttrs.put( IGNORE_CASE, new Boolean( ignoreCase ) ); + } + + public void setJavacchome( File javaccHome ) + { + this.javaccHome = javaccHome; + } + + public void setJavaunicodeescape( boolean javaUnicodeEscape ) + { + optionalAttrs.put( JAVA_UNICODE_ESCAPE, new Boolean( javaUnicodeEscape ) ); + } + + + public void setLookahead( int lookahead ) + { + optionalAttrs.put( LOOKAHEAD, new Integer( lookahead ) ); + } + + public void setOptimizetokenmanager( boolean optimizeTokenManager ) + { + optionalAttrs.put( OPTIMIZE_TOKEN_MANAGER, new Boolean( optimizeTokenManager ) ); + } + + public void setOtherambiguityCheck( int otherAmbiguityCheck ) + { + optionalAttrs.put( OTHER_AMBIGUITY_CHECK, new Integer( otherAmbiguityCheck ) ); + } + + public void setOutputdirectory( File outputDirectory ) + { + this.outputDirectory = outputDirectory; + } + + public void setSanitycheck( boolean sanityCheck ) + { + optionalAttrs.put( SANITY_CHECK, new Boolean( sanityCheck ) ); + } + + public void setStatic( boolean staticParser ) + { + optionalAttrs.put( STATIC, new Boolean( staticParser ) ); + } + + public void setTarget( File target ) + { + this.target = target; + } + + public void setUnicodeinput( boolean unicodeInput ) + { + optionalAttrs.put( UNICODE_INPUT, new Boolean( unicodeInput ) ); + } + + public void setUsercharstream( boolean userCharStream ) + { + optionalAttrs.put( USER_CHAR_STREAM, new Boolean( userCharStream ) ); + } + + public void setUsertokenmanager( boolean userTokenManager ) + { + optionalAttrs.put( USER_TOKEN_MANAGER, new Boolean( userTokenManager ) ); + } + + public void execute() + throws BuildException + { + + // load command line with optional attributes + Enumeration iter = optionalAttrs.keys(); + while( iter.hasMoreElements() ) + { + String name = ( String )iter.nextElement(); + Object value = optionalAttrs.get( name ); + cmdl.createArgument().setValue( "-" + name + ":" + value.toString() ); + } + + // check the target is a file + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // use the directory containing the target as the output directory + if( outputDirectory == null ) + { + outputDirectory = new File( target.getParent() ); + } + else if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "Outputdir not a directory." ); + } + cmdl.createArgument().setValue( + "-OUTPUT_DIRECTORY:" + outputDirectory.getAbsolutePath() ); + + // determine if the generated java file is up-to-date + final File javaFile = getOutputJavaFile( outputDirectory, target ); + if( javaFile.exists() && target.lastModified() < javaFile.lastModified() ) + { + log( "Target is already built - skipping (" + target + ")", Project.MSG_VERBOSE ); + return; + } + cmdl.createArgument().setValue( target.getAbsolutePath() ); + + if( javaccHome == null || !javaccHome.isDirectory() ) + { + throw new BuildException( "Javacchome not set." ); + } + final Path classpath = cmdl.createClasspath( project ); + classpath.createPathElement().setPath( javaccHome.getAbsolutePath() + + "/JavaCC.zip" ); + classpath.addJavaRuntime(); + + final Commandline.Argument arg = cmdl.createVmArgument(); + arg.setValue( "-mx140M" ); + arg.setValue( "-Dinstall.root=" + javaccHome.getAbsolutePath() ); + + Execute.runCommand( this, cmdl.getCommandline() ); + } + + /** + * Determines the output Java file to be generated by the given grammar + * file. + * + * @param outputdir Description of Parameter + * @param srcfile Description of Parameter + * @return The OutputJavaFile value + */ + private File getOutputJavaFile( File outputdir, File srcfile ) + { + String path = srcfile.getPath(); + + // Extract file's base-name + int startBasename = path.lastIndexOf( File.separator ); + if( startBasename != -1 ) + { + path = path.substring( startBasename + 1 ); + } + + // Replace the file's extension with '.java' + int startExtn = path.lastIndexOf( '.' ); + if( startExtn != -1 ) + { + path = path.substring( 0, startExtn ) + ".java"; + } + else + { + path += ".java"; + } + + // Change the directory + if( outputdir != null ) + { + path = outputdir + File.separator + path; + } + + return new File( path ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java new file mode 100644 index 000000000..c8e6da765 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java @@ -0,0 +1,453 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jdepend; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.PathTokenizer; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteWatchdog; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Ant task to run JDepend tests.

              + * + * JDepend is a tool to generate design quality metrics for each Java package. + * It has been initially created by Mike Clark. JDepend can be found at + * http://www.clarkware.com/software/JDepend.html . The current + * implementation spawn a new Java VM. + * + * @author Jerome Lacoste + * @author Rob Oxspring + */ +public class JDependTask extends Task +{ + + /** + * No problems with this test. + */ + private final static int SUCCESS = 0; + /** + * An error occured. + */ + private final static int ERRORS = 1; + private boolean _haltonerror = false; + private boolean _fork = false; + //private Integer _timeout = null; + + private String _jvm = null; + private String format = "text"; + private Path _compileClasspath; + private File _dir; + + // optional attributes + private File _outputFile; + //private CommandlineJava commandline = new CommandlineJava(); + + // required attributes + private Path _sourcesPath; + + public JDependTask() { } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( _compileClasspath == null ) + { + _compileClasspath = classpath; + } + else + { + _compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * The directory to invoke the VM in. Ignored if no JVM is forked. + * + * @param dir the directory to invoke the JVM from. + * @see #setFork(boolean) + */ + public void setDir( File dir ) + { + _dir = dir; + } + + /** + * Tells whether a JVM should be forked for the task. Default: false. + * + * @param value true if a JVM should be forked, otherwise false + * + */ + public void setFork( boolean value ) + { + _fork = value; + } + + + public void setFormat( FormatAttribute ea ) + { + format = ea.getValue(); + } + + /** + * Halt on Failure? default: false. + * + * @param value The new Haltonerror value + */ + public void setHaltonerror( boolean value ) + { + _haltonerror = value; + } + + /** + * Set a new VM to execute the task. Default is java . Ignored if + * no JVM is forked. + * + * @param value the new VM to use instead of java + * @see #setFork(boolean) + */ + public void setJvm( String value ) + { + _jvm = value; + + } + + /* + * public void setTimeout(Integer value) { + * _timeout = value; + * } + * public Integer getTimeout() { + * return _timeout; + * } + */ + public void setOutputFile( File outputFile ) + { + _outputFile = outputFile; + } + + /** + * Gets the classpath to be used for this compilation. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return _compileClasspath; + } + + public File getDir() + { + return _dir; + } + + public boolean getFork() + { + return _fork; + } + + public boolean getHaltonerror() + { + return _haltonerror; + } + + public File getOutputFile() + { + return _outputFile; + } + + /** + * Gets the sourcepath. + * + * @return The Sourcespath value + */ + public Path getSourcespath() + { + return _sourcesPath; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( _compileClasspath == null ) + { + _compileClasspath = new Path( project ); + } + return _compileClasspath.createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @param commandline Description of Parameter + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg( CommandlineJava commandline ) + { + return commandline.createVmArgument(); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createSourcespath() + { + if( _sourcesPath == null ) + { + _sourcesPath = new Path( project ); + } + return _sourcesPath.createPath(); + } + + public void execute() + throws BuildException + { + + CommandlineJava commandline = new CommandlineJava(); + + if( "text".equals( format ) ) + commandline.setClassname( "jdepend.textui.JDepend" ); + else + if( "xml".equals( format ) ) + commandline.setClassname( "jdepend.xmlui.JDepend" ); + + if( _jvm != null ) + commandline.setVm( _jvm ); + + if( getSourcespath() == null ) + throw new BuildException( "Missing Sourcepath required argument" ); + + // execute the test and get the return code + int exitValue = JDependTask.ERRORS; + boolean wasKilled = false; + if( !getFork() ) + { + exitValue = executeInVM( commandline ); + } + else + { + ExecuteWatchdog watchdog = createWatchdog(); + exitValue = executeAsForked( commandline, watchdog ); + // null watchdog means no timeout, you'd better not check with null + if( watchdog != null ) + { + //info will be used in later version do nothing for now + //wasKilled = watchdog.killedProcess(); + } + } + + // if there is an error/failure and that it should halt, stop everything otherwise + // just log a statement + boolean errorOccurred = exitValue == JDependTask.ERRORS; + + if( errorOccurred ) + { + if( getHaltonerror() ) + throw new BuildException( "JDepend failed", + location ); + else + log( "JDepend FAILED", Project.MSG_ERR ); + } + } + + + /** + * Execute the task by forking a new JVM. The command will block until it + * finishes. To know if the process was destroyed or not, use the + * killedProcess() method of the watchdog class. + * + * @param watchdog the watchdog in charge of cancelling the test if it + * exceeds a certain amount of time. Can be null , in this + * case the test could probably hang forever. + * @param commandline Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + // JL: comment extracted from JUnitTask (and slightly modified) + public int executeAsForked( CommandlineJava commandline, ExecuteWatchdog watchdog ) + throws BuildException + { + // if not set, auto-create the ClassPath from the project + createClasspath(); + + // not sure whether this test is needed but cost nothing to put. + // hope it will be reviewed by anybody competent + if( getClasspath().toString().length() > 0 ) + { + createJvmarg( commandline ).setValue( "-classpath" ); + createJvmarg( commandline ).setValue( getClasspath().toString() ); + } + + if( getOutputFile() != null ) + { + // having a space between the file and its path causes commandline to add quotes " + // around the argument thus making JDepend not taking it into account. Thus we split it in two + commandline.createArgument().setValue( "-file" ); + commandline.createArgument().setValue( _outputFile.getPath() ); + // we have to find a cleaner way to put this output + } + + PathTokenizer sourcesPath = new PathTokenizer( getSourcespath().toString() ); + while( sourcesPath.hasMoreTokens() ) + { + File f = new File( sourcesPath.nextToken() ); + + // not necessary as JDepend would fail, but why loose some time? + if( !f.exists() || !f.isDirectory() ) + throw new BuildException( "\"" + f.getPath() + "\" does not represent a valid directory. JDepend would fail." ); + commandline.createArgument().setValue( f.getPath() ); + } + + Execute execute = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ), watchdog ); + execute.setCommandline( commandline.getCommandline() ); + if( getDir() != null ) + { + execute.setWorkingDirectory( getDir() ); + execute.setAntRun( project ); + } + + if( getOutputFile() != null ) + log( "Output to be stored in " + getOutputFile().getPath() ); + log( "Executing: " + commandline.toString(), Project.MSG_VERBOSE ); + try + { + return execute.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Process fork failed.", e, location ); + } + } + + + // this comment extract from JUnit Task may also apply here + // "in VM is not very nice since it could probably hang the + // whole build. IMHO this method should be avoided and it would be best + // to remove it in future versions. TBD. (SBa)" + + /** + * Execute inside VM. + * + * @param commandline Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public int executeInVM( CommandlineJava commandline ) + throws BuildException + { + jdepend.textui.JDepend jdepend; + + if( "xml".equals( format ) ) + jdepend = new jdepend.xmlui.JDepend(); + else + jdepend = new jdepend.textui.JDepend(); + + if( getOutputFile() != null ) + { + FileWriter fw; + try + { + fw = new FileWriter( getOutputFile().getPath() ); + } + catch( IOException e ) + { + String msg = "JDepend Failed when creating the output file: " + e.getMessage(); + log( msg ); + throw new BuildException( msg ); + } + jdepend.setWriter( new PrintWriter( fw ) ); + log( "Output to be stored in " + getOutputFile().getPath() ); + } + + PathTokenizer sourcesPath = new PathTokenizer( getSourcespath().toString() ); + while( sourcesPath.hasMoreTokens() ) + { + File f = new File( sourcesPath.nextToken() ); + + // not necessary as JDepend would fail, but why loose some time? + if( !f.exists() || !f.isDirectory() ) + { + String msg = "\"" + f.getPath() + "\" does not represent a valid directory. JDepend would fail."; + log( msg ); + throw new BuildException( msg ); + } + try + { + jdepend.addDirectory( f.getPath() ); + } + catch( IOException e ) + { + String msg = "JDepend Failed when adding a source directory: " + e.getMessage(); + log( msg ); + throw new BuildException( msg ); + } + } + jdepend.analyze(); + return SUCCESS; + } + + /** + * @return null if there is a timeout value, otherwise the watchdog + * instance. + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + + return null; + /* + * if (getTimeout() == null){ + * return null; + * } + * return new ExecuteWatchdog(getTimeout().intValue()); + */ + } + + public static class FormatAttribute extends EnumeratedAttribute + { + private String[] formats = new String[]{"xml", "text"}; + + public String[] getValues() + { + return formats; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java new file mode 100644 index 000000000..b1a089810 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Provides a quick and dirty way to determine the true name of a class given + * just an InputStream. Reads in just enough to perform this minimal task only. + * + * @author RT + */ +public class ClassNameReader extends Object +{ + + public static String getClassName( InputStream input ) + throws IOException + { + DataInputStream data = new DataInputStream( input ); + // verify this is a valid class file. + int cookie = data.readInt(); + if( cookie != 0xCAFEBABE ) + { + return null; + } + int version = data.readInt(); + // read the constant pool. + ConstantPool constants = new ConstantPool( data ); + Object[] values = constants.values; + // read access flags and class index. + int accessFlags = data.readUnsignedShort(); + int classIndex = data.readUnsignedShort(); + Integer stringIndex = ( Integer )values[classIndex]; + String className = ( String )values[stringIndex.intValue()]; + return className; + } + +} + +/** + * Reads just enough of a class file to determine the class' full name.

              + * + * Extremely minimal constant pool implementation, mainly to support extracting + * strings from a class file. + * + * @author Patrick C. Beard . + */ +class ConstantPool extends Object +{ + + + final static byte UTF8 = 1, UNUSED = 2, INTEGER = 3, FLOAT = 4, LONG = 5, DOUBLE = 6, + CLASS = 7, STRING = 8, FIELDREF = 9, METHODREF = 10, + INTERFACEMETHODREF = 11, NAMEANDTYPE = 12; + + byte[] types; + + Object[] values; + + ConstantPool( DataInput data ) + throws IOException + { + super(); + + int count = data.readUnsignedShort(); + types = new byte[count]; + values = new Object[count]; + // read in all constant pool entries. + for( int i = 1; i < count; i++ ) + { + byte type = data.readByte(); + types[i] = type; + switch ( type ) + { + case UTF8: + values[i] = data.readUTF(); + break; + case UNUSED: + break; + case INTEGER: + values[i] = new Integer( data.readInt() ); + break; + case FLOAT: + values[i] = new Float( data.readFloat() ); + break; + case LONG: + values[i] = new Long( data.readLong() ); + ++i; + break; + case DOUBLE: + values[i] = new Double( data.readDouble() ); + ++i; + break; + case CLASS: + case STRING: + values[i] = new Integer( data.readUnsignedShort() ); + break; + case FIELDREF: + case METHODREF: + case INTERFACEMETHODREF: + case NAMEANDTYPE: + values[i] = new Integer( data.readInt() ); + break; + } + } + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java new file mode 100644 index 000000000..94d642f0c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; + +/** + * This class defines objects that can link together various jar and zip files. + *

              + * + * It is basically a wrapper for the jlink code written originally by Patrick Beard . The classes + * org.apache.tools.ant.taskdefs.optional.jlink.Jlink and + * org.apache.tools.ant.taskdefs.optional.jlink.ClassNameReader support this + * class.

              + * + * For example: + *

              + * <jlink compress="false" outfile="out.jar"/>
              + *   <mergefiles>
              + *     <pathelement path="${build.dir}/mergefoo.jar"/>
              + *     <pathelement path="${build.dir}/mergebar.jar"/>
              + *   </mergefiles>
              + *   <addfiles>
              + *     <pathelement path="${build.dir}/mac.jar"/>
              + *     <pathelement path="${build.dir}/pc.zip"/>
              + *   </addfiles>
              + * </jlink>
              + * 
              + * + * @author Matthew Kuperus Heun + * + */ +public class JlinkTask extends MatchingTask +{ + + private File outfile = null; + + private Path mergefiles = null; + + private Path addfiles = null; + + private boolean compress = false; + + private String ps = System.getProperty( "path.separator" ); + + /** + * Sets the files to be added into the output. + * + * @param addfiles The new Addfiles value + */ + public void setAddfiles( Path addfiles ) + { + if( this.addfiles == null ) + { + this.addfiles = addfiles; + } + else + { + this.addfiles.append( addfiles ); + } + } + + /** + * Defines whether or not the output should be compacted. + * + * @param compress The new Compress value + */ + public void setCompress( boolean compress ) + { + this.compress = compress; + } + + /** + * Sets the files to be merged into the output. + * + * @param mergefiles The new Mergefiles value + */ + public void setMergefiles( Path mergefiles ) + { + if( this.mergefiles == null ) + { + this.mergefiles = mergefiles; + } + else + { + this.mergefiles.append( mergefiles ); + } + } + + /** + * The output file for this run of jlink. Usually a jar or zip file. + * + * @param outfile The new Outfile value + */ + public void setOutfile( File outfile ) + { + this.outfile = outfile; + } + + /** + * Establishes the object that contains the files to be added to the output. + * + * @return Description of the Returned Value + */ + public Path createAddfiles() + { + if( this.addfiles == null ) + { + this.addfiles = new Path( getProject() ); + } + return this.addfiles.createPath(); + } + + /** + * Establishes the object that contains the files to be merged into the + * output. + * + * @return Description of the Returned Value + */ + public Path createMergefiles() + { + if( this.mergefiles == null ) + { + this.mergefiles = new Path( getProject() ); + } + return this.mergefiles.createPath(); + } + + /** + * Does the adding and merging. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + //Be sure everything has been set. + if( outfile == null ) + { + throw new BuildException( "outfile attribute is required! Please set." ); + } + if( !haveAddFiles() && !haveMergeFiles() ) + { + throw new BuildException( "addfiles or mergefiles required! Please set." ); + } + log( "linking: " + outfile.getPath() ); + log( "compression: " + compress, Project.MSG_VERBOSE ); + jlink linker = new jlink(); + linker.setOutfile( outfile.getPath() ); + linker.setCompression( compress ); + if( haveMergeFiles() ) + { + log( "merge files: " + mergefiles.toString(), Project.MSG_VERBOSE ); + linker.addMergeFiles( mergefiles.list() ); + } + if( haveAddFiles() ) + { + log( "add files: " + addfiles.toString(), Project.MSG_VERBOSE ); + linker.addAddFiles( addfiles.list() ); + } + try + { + linker.link(); + } + catch( Exception ex ) + { + throw new BuildException( ex); + } + } + + private boolean haveAddFiles() + { + return haveEntries( addfiles ); + } + + private boolean haveEntries( Path p ) + { + if( p == null ) + { + return false; + } + if( p.size() > 0 ) + { + return true; + } + return false; + } + + private boolean haveMergeFiles() + { + return haveEntries( mergefiles ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java new file mode 100644 index 000000000..cb9a3fbe8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java @@ -0,0 +1,464 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class jlink extends Object +{ + + private String outfile = null; + + private Vector mergefiles = new Vector( 10 ); + + private Vector addfiles = new Vector( 10 ); + + private boolean compression = false; + + byte[] buffer = new byte[8192]; + + public static void main( String[] args ) + { + // jlink output input1 ... inputN + if( args.length < 2 ) + { + System.out.println( "usage: jlink output input1 ... inputN" ); + System.exit( 1 ); + } + jlink linker = new jlink(); + linker.setOutfile( args[0] ); + //To maintain compatibility with the command-line version, we will only add files to be merged. + for( int i = 1; i < args.length; i++ ) + { + linker.addMergeFile( args[i] ); + } + try + { + linker.link(); + } + catch( Exception ex ) + { + System.err.print( ex.getMessage() ); + } + } + + /** + * Determines whether output will be compressed. + * + * @param compress The new Compression value + */ + public void setCompression( boolean compress ) + { + this.compression = compress; + } + + /** + * The file that will be created by this instance of jlink. + * + * @param outfile The new Outfile value + */ + public void setOutfile( String outfile ) + { + if( outfile == null ) + { + return; + } + this.outfile = outfile; + } + + /** + * Adds a file to be added into the output. + * + * @param addfile The feature to be added to the AddFile attribute + */ + public void addAddFile( String addfile ) + { + if( addfile == null ) + { + return; + } + addfiles.addElement( addfile ); + } + + /** + * Adds several file to be added into the output. + * + * @param addfiles The feature to be added to the AddFiles attribute + */ + public void addAddFiles( String[] addfiles ) + { + if( addfiles == null ) + { + return; + } + for( int i = 0; i < addfiles.length; i++ ) + { + addAddFile( addfiles[i] ); + } + } + + /** + * Adds a file to be merged into the output. + * + * @param mergefile The feature to be added to the MergeFile attribute + */ + public void addMergeFile( String mergefile ) + { + if( mergefile == null ) + { + return; + } + mergefiles.addElement( mergefile ); + } + + /** + * Adds several files to be merged into the output. + * + * @param mergefiles The feature to be added to the MergeFiles attribute + */ + public void addMergeFiles( String[] mergefiles ) + { + if( mergefiles == null ) + { + return; + } + for( int i = 0; i < mergefiles.length; i++ ) + { + addMergeFile( mergefiles[i] ); + } + } + + /** + * Performs the linking of files. Addfiles are added to the output as-is. + * For example, a jar file is added to the output as a jar file. However, + * mergefiles are first examined for their type. If it is a jar or zip file, + * the contents will be extracted from the mergefile and entered into the + * output. If a zip or jar file is encountered in a subdirectory it will be + * added, not merged. If a directory is encountered, it becomes the root + * entry of all the files below it. Thus, you can provide multiple, disjoint + * directories, as addfiles: they will all be added in a rational manner to + * outfile. + * + * @exception Exception Description of Exception + */ + public void link() + throws Exception + { + ZipOutputStream output = new ZipOutputStream( new FileOutputStream( outfile ) ); + if( compression ) + { + output.setMethod( ZipOutputStream.DEFLATED ); + output.setLevel( Deflater.DEFAULT_COMPRESSION ); + } + else + { + output.setMethod( ZipOutputStream.STORED ); + } + Enumeration merges = mergefiles.elements(); + while( merges.hasMoreElements() ) + { + String path = ( String )merges.nextElement(); + File f = new File( path ); + if( f.getName().endsWith( ".jar" ) || f.getName().endsWith( ".zip" ) ) + { + //Do the merge + mergeZipJarContents( output, f ); + } + else + { + //Add this file to the addfiles Vector and add it + //later at the top level of the output file. + addAddFile( path ); + } + } + Enumeration adds = addfiles.elements(); + while( adds.hasMoreElements() ) + { + String name = ( String )adds.nextElement(); + File f = new File( name ); + if( f.isDirectory() ) + { + //System.out.println("in jlink: adding directory contents of " + f.getPath()); + addDirContents( output, f, f.getName() + '/', compression ); + } + else + { + addFile( output, f, "", compression ); + } + } + if( output != null ) + { + try + { + output.close(); + } + catch( IOException ioe ) + {} + } + } + + /* + * Gets the name of an entry in the file. This is the real name + * which for a class is the name of the package with the class + * name appended. + */ + private String getEntryName( File file, String prefix ) + { + String name = file.getName(); + if( !name.endsWith( ".class" ) ) + { + // see if the file is in fact a .class file, and determine its actual name. + try + { + InputStream input = new FileInputStream( file ); + String className = ClassNameReader.getClassName( input ); + input.close(); + if( className != null ) + { + return className.replace( '.', '/' ) + ".class"; + } + } + catch( IOException ioe ) + {} + } + System.out.println( "From " + file.getPath() + " and prefix " + prefix + ", creating entry " + prefix + name ); + return ( prefix + name ); + } + + /* + * Adds contents of a directory to the output. + */ + private void addDirContents( ZipOutputStream output, File dir, String prefix, boolean compress ) + throws IOException + { + String[] contents = dir.list(); + for( int i = 0; i < contents.length; ++i ) + { + String name = contents[i]; + File file = new File( dir, name ); + if( file.isDirectory() ) + { + addDirContents( output, file, prefix + name + '/', compress ); + } + else + { + addFile( output, file, prefix, compress ); + } + } + } + + /* + * Adds a file to the output stream. + */ + private void addFile( ZipOutputStream output, File file, String prefix, boolean compress ) + throws IOException + { + //Make sure file exists + long checksum = 0; + if( !file.exists() ) + { + return; + } + ZipEntry entry = new ZipEntry( getEntryName( file, prefix ) ); + entry.setTime( file.lastModified() ); + entry.setSize( file.length() ); + if( !compress ) + { + entry.setCrc( calcChecksum( file ) ); + } + FileInputStream input = new FileInputStream( file ); + addToOutputStream( output, input, entry ); + } + + /* + * A convenience method that several other methods might call. + */ + private void addToOutputStream( ZipOutputStream output, InputStream input, ZipEntry ze ) + throws IOException + { + try + { + output.putNextEntry( ze ); + } + catch( ZipException zipEx ) + { + //This entry already exists. So, go with the first one. + input.close(); + return; + } + int numBytes = -1; + while( ( numBytes = input.read( buffer ) ) > 0 ) + { + output.write( buffer, 0, numBytes ); + } + output.closeEntry(); + input.close(); + } + + /* + * Necessary in the case where you add a entry that + * is not compressed. + */ + private long calcChecksum( File f ) + throws IOException + { + BufferedInputStream in = new BufferedInputStream( new FileInputStream( f ) ); + return calcChecksum( in, f.length() ); + } + + /* + * Necessary in the case where you add a entry that + * is not compressed. + */ + private long calcChecksum( InputStream in, long size ) + throws IOException + { + CRC32 crc = new CRC32(); + int len = buffer.length; + int count = -1; + int haveRead = 0; + while( ( count = in.read( buffer, 0, len ) ) > 0 ) + { + haveRead += count; + crc.update( buffer, 0, count ); + } + in.close(); + return crc.getValue(); + } + + /* + * Actually performs the merging of f into the output. + * f should be a zip or jar file. + */ + private void mergeZipJarContents( ZipOutputStream output, File f ) + throws IOException + { + //Check to see that the file with name "name" exists. + if( !f.exists() ) + { + return; + } + ZipFile zipf = new ZipFile( f ); + Enumeration entries = zipf.entries(); + while( entries.hasMoreElements() ) + { + ZipEntry inputEntry = ( ZipEntry )entries.nextElement(); + //Ignore manifest entries. They're bound to cause conflicts between + //files that are being merged. User should supply their own + //manifest file when doing the merge. + String inputEntryName = inputEntry.getName(); + int index = inputEntryName.indexOf( "META-INF" ); + if( index < 0 ) + { + //META-INF not found in the name of the entry. Go ahead and process it. + try + { + output.putNextEntry( processEntry( zipf, inputEntry ) ); + } + catch( ZipException ex ) + { + //If we get here, it could be because we are trying to put a + //directory entry that already exists. + //For example, we're trying to write "com", but a previous + //entry from another mergefile was called "com". + //In that case, just ignore the error and go on to the + //next entry. + String mess = ex.getMessage(); + if( mess.indexOf( "duplicate" ) >= 0 ) + { + //It was the duplicate entry. + continue; + } + else + { + //I hate to admit it, but we don't know what happened here. Throw the Exception. + throw ex; + } + } + InputStream in = zipf.getInputStream( inputEntry ); + int len = buffer.length; + int count = -1; + while( ( count = in.read( buffer, 0, len ) ) > 0 ) + { + output.write( buffer, 0, count ); + } + in.close(); + output.closeEntry(); + } + } + zipf.close(); + } + + /* + * A method that does the work on a given entry in a mergefile. + * The big deal is to set the right parameters in the ZipEntry + * on the output stream. + */ + private ZipEntry processEntry( ZipFile zip, ZipEntry inputEntry ) + throws IOException + { + /* + * First, some notes. + * On MRJ 2.2.2, getting the size, compressed size, and CRC32 from the + * ZipInputStream does not work for compressed (deflated) files. Those calls return -1. + * For uncompressed (stored) files, those calls do work. + * However, using ZipFile.getEntries() works for both compressed and + * uncompressed files. + * Now, from some simple testing I did, it seems that the value of CRC-32 is + * independent of the compression setting. So, it should be easy to pass this + * information on to the output entry. + */ + String name = inputEntry.getName(); + if( !( inputEntry.isDirectory() || name.endsWith( ".class" ) ) ) + { + try + { + InputStream input = zip.getInputStream( zip.getEntry( name ) ); + String className = ClassNameReader.getClassName( input ); + input.close(); + if( className != null ) + { + name = className.replace( '.', '/' ) + ".class"; + } + } + catch( IOException ioe ) + {} + } + ZipEntry outputEntry = new ZipEntry( name ); + outputEntry.setTime( inputEntry.getTime() ); + outputEntry.setExtra( inputEntry.getExtra() ); + outputEntry.setComment( inputEntry.getComment() ); + outputEntry.setTime( inputEntry.getTime() ); + if( compression ) + { + outputEntry.setMethod( ZipEntry.DEFLATED ); + //Note, don't need to specify size or crc for compressed files. + } + else + { + outputEntry.setMethod( ZipEntry.STORED ); + outputEntry.setCrc( inputEntry.getCrc() ); + outputEntry.setSize( inputEntry.getSize() ); + } + return outputEntry; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java new file mode 100644 index 000000000..2d91db4ca --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java @@ -0,0 +1,491 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp; +import java.io.File; +import java.util.Date; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.optional.jsp.compilers.CompilerAdapter; +import org.apache.tools.ant.taskdefs.optional.jsp.compilers.CompilerAdapterFactory; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Ant task to run the jsp compiler.

              + * + * This task takes the given jsp files and compiles them into java files. It is + * then up to the user to compile the java files into classes.

              + * + * The task requires the srcdir and destdir attributes to be set. This Task is a + * MatchingTask, so the files to be compiled can be specified using + * includes/excludes attributes or nested include/exclude elements. Optional + * attributes are verbose (set the verbosity level passed to jasper), package + * (name of the destination package for generated java classes and classpath + * (the classpath to use when running the jsp compiler).

              + * + * This task supports the nested elements classpath (A Path) and classpathref (A + * Reference) which can be used in preference to the attribute classpath, if the + * jsp compiler is not already in the ant classpath.

              + * + *

              Notes

              + * + * At present, this task only supports the jasper compiler. In future, other + * compilers will be supported by setting the jsp.compiler property.

              + * + *

              Usage

              + * <jspc srcdir="${basedir}/src/war"
              + *       destdir="${basedir}/gensrc"
              + *       package="com.i3sp.jsp"
              + *       verbose="9">
              + *   <include name="**\/*.jsp" />
              + * </jspc>
              + * 
              + * + * @author Matthew Watson

              + * + * Large Amount of cutting and pasting from the Javac task... + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + * @version $Revision$ $Date$ + */ +public class JspC extends MatchingTask +{ + + private final static String FAIL_MSG + = "Compile failed, messages should have been provided."; + private int verbose = 0; + protected Vector compileList = new Vector(); + protected boolean failOnError = true; + /* + * ------------------------------------------------------------ + */ + private Path classpath; + private File destDir; + private String iepluginid; + private boolean mapped; + private String packageName; + private Path src; + + /** + * -uribase

              The uri directory compilations should be relative to + * (Default is "/") + */ + + private File uribase; + + /** + * -uriroot The root directory that uri files should be resolved + * against, + */ + private File uriroot; + + + /* + * ------------------------------------------------------------ + */ + /** + * Set the classpath to be used for this compilation + * + * @param cp The new Classpath value + */ + public void setClasspath( Path cp ) + { + if( classpath == null ) + classpath = cp; + else + classpath.append( cp ); + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the destination directory into which the JSP source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Throw a BuildException if compilation fails + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Set the ieplugin id + * + * @param iepluginid_ The new Ieplugin value + */ + public void setIeplugin( String iepluginid_ ) + { + iepluginid = iepluginid_; + } + + /** + * set the mapped flag + * + * @param mapped_ The new Mapped value + */ + public void setMapped( boolean mapped_ ) + { + mapped = mapped_; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the name of the package the compiled jsp files should be in + * + * @param pkg The new Package value + */ + public void setPackage( String pkg ) + { + this.packageName = pkg; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the source dirs to find the source JSP files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( Path srcDir ) + { + if( src == null ) + { + src = srcDir; + } + else + { + src.append( srcDir ); + } + } + + /** + * -uribase. the uri context of relative URI references in the JSP pages. If + * it does not exist then it is derived from the location of the file + * relative to the declared or derived value of -uriroot. + * + * @param uribase The new Uribase value + */ + public void setUribase( File uribase ) + { + this.uribase = uribase; + } + + /** + * -uriroot The root directory that uri files should be resolved + * against, (Default is the directory jspc is invoked from) + * + * @param uriroot The new Uribase value + */ + public void setUriroot( File uriroot ) + { + this.uriroot = uriroot; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the verbose level of the compiler + * + * @param i The new Verbose value + */ + public void setVerbose( int i ) + { + verbose = i; + } + + public Path getClasspath() + { + return classpath; + } + + /* + * ------------------------------------------------------------ + */ + public Vector getCompileList() + { + return compileList; + } + + public File getDestdir() + { + return destDir; + } + + /** + * Gets the failonerror flag. + * + * @return The Failonerror value + */ + public boolean getFailonerror() + { + return failOnError; + } + + /* + * ------------------------------------------------------------ + */ + public String getIeplugin() + { + return iepluginid; + } + + public String getPackage() + { + return packageName; + } + + public Path getSrcDir() + { + return src; + } + + public File getUribase() + { + return uriroot; + } + + public File getUriroot() + { + return uriroot; + } + + public int getVerbose() + { + return verbose; + } + + /* + * ------------------------------------------------------------ + */ + public boolean isMapped() + { + return mapped; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + classpath = new Path( project ); + return classpath.createPath(); + } + + /* + * ------------------------------------------------------------ + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + if( src == null ) + { + throw new BuildException( "srcdir attribute must be set!", + location ); + } + String[] list = src.list(); + if( list.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", + location ); + } + + if( destDir != null && !destDir.isDirectory() ) + { + throw new + BuildException( "destination directory \"" + destDir + + "\" does not exist or is not a directory", + location ); + } + + // calculate where the files will end up: + File dest = null; + if( packageName == null ) + dest = destDir; + else + { + String path = destDir.getPath() + File.separatorChar + + packageName.replace( '.', File.separatorChar ); + dest = new File( path ); + } + + // scan source directories and dest directory to build up both copy + // lists and compile lists + resetFileLists(); + int filecount = 0; + for( int i = 0; i < list.length; i++ ) + { + File srcDir = ( File )project.resolveFile( list[i] ); + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir \"" + srcDir.getPath() + + "\" does not exist!", location ); + } + + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + filecount = files.length; + scanDir( srcDir, dest, files ); + } + + // compile the source files + + String compiler = project.getProperty( "jsp.compiler" ); + if( compiler == null ) + { + compiler = "jasper"; + } + log( "compiling " + compileList.size() + " files", Project.MSG_VERBOSE ); + + if( compileList.size() > 0 ) + { + + CompilerAdapter adapter = + CompilerAdapterFactory.getCompiler( compiler, this ); + log( "Compiling " + compileList.size() + + " source file" + + ( compileList.size() == 1 ? "" : "s" ) + + ( destDir != null ? " to " + destDir : "" ) ); + + // now we need to populate the compiler adapter + adapter.setJspc( this ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + if( failOnError ) + { + throw new BuildException( FAIL_MSG, location ); + } + else + { + log( FAIL_MSG, Project.MSG_ERR ); + } + } + } + else + { + if( filecount == 0 ) + { + log( "there were no files to compile", Project.MSG_INFO ); + } + else + { + log( "all files are up to date", Project.MSG_VERBOSE ); + } + } + } + + /* + * ------------------------------------------------------------ + */ + /** + * Clear the list of files to be compiled and copied.. + */ + protected void resetFileLists() + { + compileList.removeAllElements(); + } + + /* + * ------------------------------------------------------------ + */ + /** + * Scans the directory looking for source files to be compiled. The results + * are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, File destDir, String files[] ) + { + + long now = ( new Date() ).getTime(); + + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".jsp" ) ) + { + // drop leading path (if any) + int fileStart = + files[i].lastIndexOf( File.separatorChar ) + 1; + File javaFile = new File( destDir, files[i].substring( fileStart, + files[i].indexOf( ".jsp" ) ) + ".java" ); + + if( srcFile.lastModified() > now ) + { + log( "Warning: file modified in the future: " + + files[i], Project.MSG_WARN ); + } + + if( !javaFile.exists() || + srcFile.lastModified() > javaFile.lastModified() ) + { + if( !javaFile.exists() ) + { + log( "Compiling " + srcFile.getPath() + + " because java file " + + javaFile.getPath() + " does not exist", + Project.MSG_DEBUG ); + } + else + { + log( "Compiling " + srcFile.getPath() + + " because it is out of date with respect to " + + javaFile.getPath(), Project.MSG_DEBUG ); + } + compileList.addElement( srcFile.getAbsolutePath() ); + } + } + } + } + /* + * ------------------------------------------------------------ + */ +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java new file mode 100644 index 000000000..20d7a9176 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp;//java imports +import java.io.File; +import java.util.Date; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java;//apache/ant imports +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; + +/** + * Class to precompile JSP's using weblogic's jsp compiler (weblogic.jspc) + * + * @author Avik Sengupta + * http://www.webteksoftware.com Tested only on Weblogic 4.5.1 - NT4.0 and + * Solaris 5.7 required attributes src : root of source tree for JSP, ie, + * the document root for your weblogic server dest : root of destination + * directory, what you have set as WorkingDir in the weblogic properties + * package : start package name under which your JSP's would be compiled + * other attributes classpath A classpath should be set which contains the + * weblogic classes as well as all application classes referenced by the + * JSP. The system classpath is also appended when the jspc is called, so + * you may choose to put everything in the classpath while calling Ant. + * However, since presumably the JSP's will reference classes being build + * by Ant, it would be better to explicitly add the classpath in the task + * The task checks timestamps on the JSP's and the generated classes, and + * compiles only those files that have changed. It follows the weblogic + * naming convention of putting classes in _dirName/_fileName.class for + * dirname/fileName.jsp Limitation: It compiles the files thru the + * Classic compiler only. Limitation: Since it is my experience that + * weblogic jspc throws out of memory error on being given too many files + * at one go, it is called multiple times with one jsp file each.
              + * example
              + * <target name="jspcompile" depends="compile">
              + *   <wljspc src="c:\\weblogic\\myserver\\public_html" dest="c:\\weblogic\\myserver\\serverclasses" package="myapp.jsp">
              + *   <classpath>
              + *          <pathelement location="${weblogic.classpath}" />
              + *           <pathelement path="${compile.dest}" />
              + *      </classpath>
              + *
              + *   </wljspc>
              + * </target>
              + * 
              + */ + +public class WLJspc extends MatchingTask +{//classpath used to compile the jsp files. + //private String compilerPath; //fully qualified name for the compiler executable + + private String pathToPackage = ""; + private Vector filesToDo = new Vector();//package under which resultant classes will reside + private Path compileClasspath; + //TODO Test on other versions of weblogic + //TODO add more attributes to the task, to take care of all jspc options + //TODO Test on Unix + + private File destinationDirectory;// root of source files tree + private String destinationPackage;//root of compiled files tree + private File sourceDirectory; + + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Set the directory containing the source jsp's + * + * @param dirName the directory containg the source jsp's + */ + public void setDest( File dirName ) + { + + destinationDirectory = dirName; + } + + /** + * Set the package under which the compiled classes go + * + * @param packageName the package name for the clases + */ + public void setPackage( String packageName ) + { + + destinationPackage = packageName; + } + + /** + * Set the directory containing the source jsp's + * + * @param dirName the directory containg the source jsp's + */ + public void setSrc( File dirName ) + { + + sourceDirectory = dirName; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath; + } + + public void execute() + throws BuildException + { + if( !destinationDirectory.isDirectory() ) + { + throw new BuildException( "destination directory " + destinationDirectory.getPath() + + " is not valid" ); + } + + if( !sourceDirectory.isDirectory() ) + { + throw new BuildException( "src directory " + sourceDirectory.getPath() + + " is not valid" ); + } + + if( destinationPackage == null ) + { + throw new BuildException( "package attribute must be present.", location ); + } + + String systemClassPath = System.getProperty( "java.class.path" ); + + pathToPackage = this.destinationPackage.replace( '.', File.separatorChar ); + // get all the files in the sourceDirectory + DirectoryScanner ds = super.getDirectoryScanner( sourceDirectory ); + + //use the systemclasspath as well, to include the ant jar + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + + compileClasspath.append( Path.systemClasspath ); + String[] files = ds.getIncludedFiles(); + + //Weblogic.jspc calls System.exit() ... have to fork + // Therefore, takes loads of time + // Can pass directories at a time (*.jsp) but easily runs out of memory on hefty dirs + // (even on a Sun) + Java helperTask = ( Java )project.createTask( "java" ); + helperTask.setFork( true ); + helperTask.setClassname( "weblogic.jspc" ); + helperTask.setTaskName( getTaskName() ); + String[] args = new String[12]; + + File jspFile = null; + String parents = ""; + String arg = ""; + int j = 0; + //XXX this array stuff is a remnant of prev trials.. gotta remove. + args[j++] = "-d"; + args[j++] = destinationDirectory.getAbsolutePath().trim(); + args[j++] = "-docroot"; + args[j++] = sourceDirectory.getAbsolutePath().trim(); + args[j++] = "-keepgenerated";//TODO: Parameterise ?? + //Call compiler as class... dont want to fork again + //Use classic compiler -- can be parameterised? + args[j++] = "-compilerclass"; + args[j++] = "sun.tools.javac.Main"; + //Weblogic jspc does not seem to work unless u explicitly set this... + // Does not take the classpath from the env.... + // Am i missing something about the Java task?? + args[j++] = "-classpath"; + args[j++] = compileClasspath.toString(); + + this.scanDir( files ); + log( "Compiling " + filesToDo.size() + " JSP files" ); + + for( int i = 0; i < filesToDo.size(); i++ ) + { + //XXX + // All this to get package according to weblogic standards + // Can be written better... this is too hacky! + // Careful.. similar code in scanDir , but slightly different!! + jspFile = new File( ( String )filesToDo.elementAt( i ) ); + args[j] = "-package"; + parents = jspFile.getParent(); + if( ( parents != null ) && ( !( "" ).equals( parents ) ) ) + { + parents = this.replaceString( parents, File.separator, "_." ); + args[j + 1] = destinationPackage + "." + "_" + parents; + } + else + { + args[j + 1] = destinationPackage; + } + + args[j + 2] = sourceDirectory + File.separator + ( String )filesToDo.elementAt( i ); + arg = ""; + + for( int x = 0; x < 12; x++ ) + { + arg += " " + args[x]; + } + + System.out.println( "arg = " + arg ); + + helperTask.clearArgs(); + helperTask.setArgs( arg ); + helperTask.setClasspath( compileClasspath ); + if( helperTask.executeJava() != 0 ) + { + log( files[i] + " failed to compile", Project.MSG_WARN ); + } + } + } + + + protected String replaceString( String inpString, String escapeChars, String replaceChars ) + { + String localString = ""; + int numTokens = 0; + StringTokenizer st = new StringTokenizer( inpString, escapeChars, true ); + numTokens = st.countTokens(); + for( int i = 0; i < numTokens; i++ ) + { + String test = st.nextToken(); + test = ( test.equals( escapeChars ) ? replaceChars : test ); + localString += test; + } + return localString; + } + + + protected void scanDir( String files[] ) + { + + long now = ( new Date() ).getTime(); + File jspFile = null; + String parents = null; + String pack = ""; + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( this.sourceDirectory, files[i] ); + //XXX + // All this to convert source to destination directory according to weblogic standards + // Can be written better... this is too hacky! + jspFile = new File( files[i] ); + parents = jspFile.getParent(); + int loc = 0; + + if( ( parents != null ) && ( !( "" ).equals( parents ) ) ) + { + parents = this.replaceString( parents, File.separator, "_/" ); + pack = pathToPackage + File.separator + "_" + parents; + } + else + { + pack = pathToPackage; + } + + String filePath = pack + File.separator + "_"; + int startingIndex + = files[i].lastIndexOf( File.separator ) != -1 ? files[i].lastIndexOf( File.separator ) + 1 : 0; + int endingIndex = files[i].indexOf( ".jsp" ); + if( endingIndex == -1 ) + { + break; + } + + filePath += files[i].substring( startingIndex, endingIndex ); + filePath += ".class"; + File classFile = new File( this.destinationDirectory, filePath ); + + if( srcFile.lastModified() > now ) + { + log( "Warning: file modified in the future: " + + files[i], Project.MSG_WARN ); + } + if( srcFile.lastModified() > classFile.lastModified() ) + { + //log("Files are" + srcFile.getAbsolutePath()+" " +classFile.getAbsolutePath()); + filesToDo.addElement( files[i] ); + log( "Recompiling File " + files[i], Project.MSG_VERBOSE ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java new file mode 100644 index 000000000..62465e8d8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; + +/** + * The interface that all jsp compiler adapters must adher to.

              + * + * A compiler adapter is an adapter that interprets the jspc's parameters in + * preperation to be passed off to the compier this adapter represents. As all + * the necessary values are stored in the Jspc task itself, the only thing all + * adapters need is the jsp task, the execute command and a parameterless + * constructor (for reflection).

              + * + * @author Jay Dickon Glanville + * jayglanville@home.com + * @author Matthew Watson mattw@i3sp.com + */ + +public interface CompilerAdapter +{ + + /** + * Sets the compiler attributes, which are stored in the Jspc task. + * + * @param attributes The new Jspc value + */ + void setJspc( JspC attributes ); + + /** + * Executes the task. + * + * @return has the compilation been successful + * @exception BuildException Description of Exception + */ + boolean execute() + throws BuildException; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java new file mode 100644 index 000000000..2e648a5ac --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Creates the necessary compiler adapter, given basic criteria. + * + * @author J D Glanville + * @author Matthew Watson mattw@i3sp.com + */ +public class CompilerAdapterFactory +{ + + /** + * This is a singlton -- can't create instances!! + */ + private CompilerAdapterFactory() { } + + /** + * Based on the parameter passed in, this method creates the necessary + * factory desired. The current mapping for compiler names are as follows: + * + *
                + *
              • jasper = jasper compiler (the default) + *
              • a fully quallified classname = the name of a jsp compiler + * adapter + *
              + * + * + * @param compilerType either the name of the desired compiler, or the full + * classname of the compiler's adapter. + * @param task a task to log through. + * @return The Compiler value + * @throws BuildException if the compiler type could not be resolved into a + * compiler adapter. + */ + public static CompilerAdapter getCompiler( String compilerType, Task task ) + throws BuildException + { + /* + * If I've done things right, this should be the extent of the + * conditional statements required. + */ + if( compilerType.equalsIgnoreCase( "jasper" ) ) + { + return new JasperC(); + } + return resolveClassName( compilerType ); + } + + /** + * Tries to resolve the given classname into a compiler adapter. Throws a + * fit if it can't. + * + * @param className The fully qualified classname to be created. + * @return Description of the Returned Value + * @throws BuildException This is the fit that is thrown if className isn't + * an instance of CompilerAdapter. + */ + private static CompilerAdapter resolveClassName( String className ) + throws BuildException + { + try + { + Class c = Class.forName( className ); + Object o = c.newInstance(); + return ( CompilerAdapter )o; + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( className + " can\'t be found.", cnfe ); + } + catch( ClassCastException cce ) + { + throw new BuildException( className + " isn\'t the classname of " + + "a compiler adapter.", cce ); + } + catch( Throwable t ) + { + // for all other possibilities + throw new BuildException( className + " caused an interesting " + + "exception.", t ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java new file mode 100644 index 000000000..6c54419d4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; +import org.apache.tools.ant.types.Commandline; + +/** + * This is the default implementation for the CompilerAdapter interface. This is + * currently very light on the ground since only one compiler type is supported. + * + * @author Matthew Watson mattw@i3sp.com + */ +public abstract class DefaultCompilerAdapter + implements CompilerAdapter +{ + /* + * ------------------------------------------------------------ + */ + private static String lSep = System.getProperty( "line.separator" ); + /* + * ------------------------------------------------------------ + */ + protected JspC attributes; + + public void setJspc( JspC attributes ) + { + this.attributes = attributes; + } + + public JspC getJspc() + { + return attributes; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param jspc Description of Parameter + * @param compileList Description of Parameter + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( JspC jspc, + Vector compileList, + Commandline cmd ) + { + jspc.log( "Compilation args: " + cmd.toString(), Project.MSG_VERBOSE ); + + StringBuffer niceSourceList = new StringBuffer( "File" ); + if( compileList.size() != 1 ) + { + niceSourceList.append( "s" ); + } + niceSourceList.append( " to be compiled:" ); + + niceSourceList.append( lSep ); + + Enumeration enum = compileList.elements(); + while( enum.hasMoreElements() ) + { + String arg = ( String )enum.nextElement(); + cmd.createArgument().setValue( arg ); + niceSourceList.append( " " + arg + lSep ); + } + + jspc.log( niceSourceList.toString(), Project.MSG_VERBOSE ); + } + /* + * ------------------------------------------------------------ + */ +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java new file mode 100644 index 000000000..4f0985bbf --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the jasper compiler. This is a cut-and-paste of the + * original Jspc task. + * + * @author Matthew Watson mattw@i3sp.com + */ +public class JasperC extends DefaultCompilerAdapter +{ + /* + * ------------------------------------------------------------ + */ + public boolean execute() + throws BuildException + { + getJspc().log( "Using jasper compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupJasperCommand(); + + try + { + // Create an instance of the compiler, redirecting output to + // the project log + Java java = ( Java )( getJspc().getProject() ).createTask( "java" ); + if( getJspc().getClasspath() != null ) + java.setClasspath( getJspc().getClasspath() ); + java.setClassname( "org.apache.jasper.JspC" ); + String args[] = cmd.getArguments(); + for( int i = 0; i < args.length; i++ ) + java.createArg().setValue( args[i] ); + java.setFailonerror( true ); + java.execute(); + return true; + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error running jsp compiler: ", + ex, getJspc().getLocation() ); + } + } + } + + /* + * ------------------------------------------------------------ + */ + private Commandline setupJasperCommand() + { + Commandline cmd = new Commandline(); + JspC jspc = getJspc(); + if( jspc.getDestdir() != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( jspc.getDestdir() ); + } + if( jspc.getPackage() != null ) + { + cmd.createArgument().setValue( "-p" ); + cmd.createArgument().setValue( jspc.getPackage() ); + } + if( jspc.getVerbose() != 0 ) + { + cmd.createArgument().setValue( "-v" + jspc.getVerbose() ); + } + if( jspc.isMapped() ) + { + cmd.createArgument().setValue( "-mapped" ); + } + if( jspc.getIeplugin() != null ) + { + cmd.createArgument().setValue( "-ieplugin" ); + cmd.createArgument().setValue( jspc.getIeplugin() ); + } + if( jspc.getUriroot() != null ) + { + cmd.createArgument().setValue( "-uriroot" ); + cmd.createArgument().setValue( jspc.getUriroot().toString() ); + } + if( jspc.getUribase() != null ) + { + cmd.createArgument().setValue( "-uribase" ); + cmd.createArgument().setValue( jspc.getUribase().toString() ); + } + logAndAddFilesToCompile( getJspc(), getJspc().getCompileList(), cmd ); + return cmd; + } + /* + * ------------------------------------------------------------ + */ +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java new file mode 100644 index 000000000..0d1e7e9d7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + + + +/** + * Transform a JUnit xml report. The default transformation generates an html + * report in either framed or non-framed style. The non-framed style is + * convenient to have a concise report via mail, the framed report is much more + * convenient if you want to browse into different packages or testcases since + * it is a Javadoc like report. + * + * @author Stephane Bailliez + */ +public class AggregateTransformer +{ + + public final static String FRAMES = "frames"; + + public final static String NOFRAMES = "noframes"; + + /** + * XML Parser factory + */ + protected final static DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); + + /** + * the xml document to process + */ + protected Document document; + + /** + * the format to use for the report. Must be FRAMES or NOFRAMES + * + */ + protected String format; + + /** + * the style directory. XSLs should be read from here if necessary + */ + protected File styleDir; + + /** + * Task + */ + protected Task task; + + /** + * the destination directory, this is the root from where html should be + * generated + */ + protected File toDir; + + public AggregateTransformer( Task task ) + { + this.task = task; + } + + /** + * set the extension of the output files + * + * @param ext The new Extension value + */ + public void setExtension( String ext ) + { + task.log( "extension is not used anymore", Project.MSG_WARN ); + } + + public void setFormat( Format format ) + { + this.format = format.getValue(); + } + + /** + * set the style directory. It is optional and will override the default xsl + * used. + * + * @param styledir the directory containing the xsl files if the user would + * like to override with its own style. + */ + public void setStyledir( File styledir ) + { + this.styleDir = styledir; + } + + /** + * set the destination directory + * + * @param todir The new Todir value + */ + public void setTodir( File todir ) + { + this.toDir = todir; + } + + public void setXmlDocument( Document doc ) + { + this.document = doc; + } + + public void transform() + throws BuildException + { + checkOptions(); + final long t0 = System.currentTimeMillis(); + try + { + Element root = document.getDocumentElement(); + XalanExecutor executor = XalanExecutor.newInstance( this ); + executor.execute(); + } + catch( Exception e ) + { + throw new BuildException( "Errors while applying transformations", e ); + } + final long dt = System.currentTimeMillis() - t0; + task.log( "Transform time: " + dt + "ms" ); + } + + /** + * Set the xml file to be processed. This is a helper if you want to set the + * file directly. Much more for testing purposes. + * + * @param xmlfile xml file to be processed + * @exception BuildException Description of Exception + */ + protected void setXmlfile( File xmlfile ) + throws BuildException + { + try + { + DocumentBuilder builder = dbfactory.newDocumentBuilder(); + InputStream in = new FileInputStream( xmlfile ); + try + { + Document doc = builder.parse( in ); + setXmlDocument( doc ); + } + finally + { + in.close(); + } + } + catch( Exception e ) + { + throw new BuildException( "Error while parsing document: " + xmlfile, e ); + } + } + + /** + * Get the systemid of the appropriate stylesheet based on its name and + * styledir. If no styledir is defined it will load it as a java resource in + * the xsl child package, otherwise it will get it from the given directory. + * + * @return The StylesheetSystemId value + * @throws IOException thrown if the requested stylesheet does not exist. + */ + protected String getStylesheetSystemId() + throws IOException + { + String xslname = "junit-frames.xsl"; + if( NOFRAMES.equals( format ) ) + { + xslname = "junit-noframes.xsl"; + } + URL url = null; + if( styleDir == null ) + { + url = getClass().getResource( "xsl/" + xslname ); + if( url == null ) + { + throw new FileNotFoundException( "Could not find jar resource " + xslname ); + } + } + else + { + File file = new File( styleDir, xslname ); + if( !file.exists() ) + { + throw new FileNotFoundException( "Could not find file '" + file + "'" ); + } + url = new URL( "file", "", file.getAbsolutePath() ); + } + return url.toExternalForm(); + } + + /** + * check for invalid options + * + * @exception BuildException Description of Exception + */ + protected void checkOptions() + throws BuildException + { + // set the destination directory relative from the project if needed. + if( toDir == null ) + { + toDir = task.getProject().resolveFile( "." ); + } + else if( !toDir.isAbsolute() ) + { + toDir = task.getProject().resolveFile( toDir.getPath() ); + } + } + + public static class Format extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{FRAMES, NOFRAMES}; + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java new file mode 100644 index 000000000..b2173d3e8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.util.Vector; + +/** + * Baseclass for BatchTest and JUnitTest. + * + * @author Stefan Bodewig + * @author Stephane Bailliez + */ +public abstract class BaseTest +{ + protected boolean haltOnError = false; + protected boolean haltOnFail = false; + protected boolean filtertrace = true; + protected boolean fork = false; + protected String ifProperty = null; + protected String unlessProperty = null; + protected Vector formatters = new Vector(); + /** + * destination directory + */ + protected File destDir = null; + protected String errorProperty; + + protected String failureProperty; + + public void setErrorProperty( String errorProperty ) + { + this.errorProperty = errorProperty; + } + + public void setFailureProperty( String failureProperty ) + { + this.failureProperty = failureProperty; + } + + public void setFiltertrace( boolean value ) + { + filtertrace = value; + } + + public void setFork( boolean value ) + { + fork = value; + } + + public void setHaltonerror( boolean value ) + { + haltOnError = value; + } + + public void setHaltonfailure( boolean value ) + { + haltOnFail = value; + } + + public void setIf( String propertyName ) + { + ifProperty = propertyName; + } + + /** + * Sets the destination directory. + * + * @param destDir The new Todir value + */ + public void setTodir( File destDir ) + { + this.destDir = destDir; + } + + public void setUnless( String propertyName ) + { + unlessProperty = propertyName; + } + + public java.lang.String getErrorProperty() + { + return errorProperty; + } + + public java.lang.String getFailureProperty() + { + return failureProperty; + } + + public boolean getFiltertrace() + { + return filtertrace; + } + + public boolean getFork() + { + return fork; + } + + public boolean getHaltonerror() + { + return haltOnError; + } + + public boolean getHaltonfailure() + { + return haltOnFail; + } + + /** + * @return the destination directory as an absolute path if it exists + * otherwise return null + */ + public String getTodir() + { + if( destDir != null ) + { + return destDir.getAbsolutePath(); + } + return null; + } + + public void addFormatter( FormatterElement elem ) + { + formatters.addElement( elem ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java new file mode 100644 index 000000000..115e2c1a3 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; + +/** + *

              + * + * Create then run JUnitTest's based on the list of files given by + * the fileset attribute.

              + * + * Every .java or .class file in the fileset is + * assumed to be a testcase. A JUnitTest is created for each of + * these named classes with basic setup inherited from the parent BatchTest + * . + * + * @author Jeff Martin + * @author Stefan Bodewig + * @author Stephane Bailliez + * @see JUnitTest + */ +public final class BatchTest extends BaseTest +{ + + /** + * the list of filesets containing the testcase filename rules + */ + private Vector filesets = new Vector(); + + /** + * the reference to the project + */ + private Project project; + + /** + * create a new batchtest instance + * + * @param project the project it depends on. + */ + public BatchTest( Project project ) + { + this.project = project; + } + + /** + * Convenient method to convert a pathname without extension to a fully + * qualified classname. For example org/apache/Whatever will be + * converted to org.apache.Whatever + * + * @param filename the filename to "convert" to a classname. + * @return the classname matching the filename. + */ + public final static String javaToClass( String filename ) + { + return filename.replace( File.separatorChar, '.' ); + } + + /** + * Return all JUnitTest instances obtain by applying the fileset + * rules. + * + * @return an enumeration of all elements of this batchtest that are a + * JUnitTest instance. + */ + public final Enumeration elements() + { + JUnitTest[] tests = createAllJUnitTest(); + return Enumerations.fromArray( tests ); + } + + /** + * Add a new fileset instance to this batchtest. Whatever the fileset is, + * only filename that are .java or .class will be + * considered as 'candidates'. + * + * @param fs the new fileset containing the rules to get the testcases. + */ + public void addFileSet( FileSet fs ) + { + filesets.addElement( fs ); + } + + /** + * Convenient method to merge the JUnitTest s of this batchtest to + * a Vector . + * + * @param v the vector to which should be added all individual tests of this + * batch test. + */ + final void addTestsTo( Vector v ) + { + JUnitTest[] tests = createAllJUnitTest(); + v.ensureCapacity( v.size() + tests.length ); + for( int i = 0; i < tests.length; i++ ) + { + v.addElement( tests[i] ); + } + } + + /** + * Iterate over all filesets and return the filename of all files that end + * with .java or .class . This is to avoid wrapping a + * JUnitTest over an xml file for example. A Testcase is obviously a + * java file (compiled or not). + * + * @return an array of filenames without their extension. As they should + * normally be taken from their root, filenames should match their + * fully qualified class name (If it is not the case it will fail when + * running the test). For the class org/apache/Whatever.class + * it will return org/apache/Whatever . + */ + private String[] getFilenames() + { + Vector v = new Vector(); + final int size = this.filesets.size(); + for( int j = 0; j < size; j++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( j ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + for( int k = 0; k < f.length; k++ ) + { + String pathname = f[k]; + if( pathname.endsWith( ".java" ) ) + { + v.addElement( pathname.substring( 0, pathname.length() - ".java".length() ) ); + } + else if( pathname.endsWith( ".class" ) ) + { + v.addElement( pathname.substring( 0, pathname.length() - ".class".length() ) ); + } + } + } + + String[] files = new String[v.size()]; + v.copyInto( files ); + return files; + } + + /** + * Create all JUnitTest s based on the filesets. Each instance is + * configured to match this instance properties. + * + * @return the array of all JUnitTest s that belongs to this batch. + */ + private JUnitTest[] createAllJUnitTest() + { + String[] filenames = getFilenames(); + JUnitTest[] tests = new JUnitTest[filenames.length]; + for( int i = 0; i < tests.length; i++ ) + { + String classname = javaToClass( filenames[i] ); + tests[i] = createJUnitTest( classname ); + } + return tests; + } + + /** + * Create a JUnitTest that has the same property as this + * BatchTest instance. + * + * @param classname the name of the class that should be run as a + * JUnitTest . It must be a fully qualified name. + * @return the JUnitTest over the given classname. + */ + private JUnitTest createJUnitTest( String classname ) + { + JUnitTest test = new JUnitTest(); + test.setName( classname ); + test.setHaltonerror( this.haltOnError ); + test.setHaltonfailure( this.haltOnFail ); + test.setFiltertrace( this.filtertrace ); + test.setFork( this.fork ); + test.setIf( this.ifProperty ); + test.setUnless( this.unlessProperty ); + test.setTodir( this.destDir ); + test.setFailureProperty( failureProperty ); + test.setErrorProperty( errorProperty ); + Enumeration list = this.formatters.elements(); + while( list.hasMoreElements() ) + { + test.addFormatter( ( FormatterElement )list.nextElement() ); + } + return test; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java new file mode 100644 index 000000000..81ee9ac8f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import org.apache.tools.ant.BuildException; + +/** + * Prints plain text output of the test to a specified Writer. Inspired by the + * PlainJUnitResultFormatter. + * + * @author Robert Watkins + * @see FormatterElement + * @see PlainJUnitResultFormatter + */ +public class BriefJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private java.text.NumberFormat m_numberFormat = java.text.NumberFormat.getInstance(); + + /** + * Output suite has written to System.out + */ + private String systemOutput = null; + + /** + * Output suite has written to System.err + */ + private String systemError = null; + + /** + * Where to write the log to. + */ + private java.io.OutputStream m_out; + + /** + * Used for writing the results. + */ + private java.io.PrintWriter m_output; + + /** + * Used for writing formatted results to. + */ + private java.io.PrintWriter m_resultWriter; + + /** + * Used as part of formatting the results. + */ + private java.io.StringWriter m_results; + + public BriefJUnitResultFormatter() + { + m_results = new java.io.StringWriter(); + m_resultWriter = new java.io.PrintWriter( m_results ); + } + + /** + * Sets the stream the formatter is supposed to write its results to. + * + * @param out The new Output value + */ + public void setOutput( java.io.OutputStream out ) + { + m_out = out; + m_output = new java.io.PrintWriter( out ); + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * A test caused an error. + * + * @param test The feature to be added to the Error attribute + * @param error The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable error ) + { + formatError( "\tCaused an ERROR", test, error ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( "\tFAILED", test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * A test ended. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Testsuite: " ); + sb.append( suite.getName() ); + sb.append( newLine ); + sb.append( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( m_numberFormat.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + sb.append( newLine ); + + // append the err and output streams to the log + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "------------- Standard Output ---------------" ) + .append( newLine ) + .append( systemOutput ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "------------- Standard Error -----------------" ) + .append( newLine ) + .append( systemError ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( output() != null ) + { + try + { + output().write( sb.toString() ); + resultWriter().close(); + output().write( m_results.toString() ); + output().flush(); + } + finally + { + if( m_out != ( Object )System.out && + m_out != ( Object )System.err ) + { + try + { + m_out.close(); + } + catch( java.io.IOException e ) + {} + } + } + } + } + + /** + * A test started. + * + * @param test Description of Parameter + */ + public void startTest( Test test ) { } + + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void startTestSuite( JUnitTest suite ) + throws BuildException { } + + /** + * Format an error and print it. + * + * @param type Description of Parameter + * @param test Description of Parameter + * @param error Description of Parameter + */ + protected synchronized void formatError( String type, Test test, + Throwable error ) + { + if( test != null ) + { + endTest( test ); + } + + resultWriter().println( formatTest( test ) + type ); + resultWriter().println( error.getMessage() ); + String strace = JUnitTestRunner.getFilteredTrace( error ); + resultWriter().println( strace ); + resultWriter().println( "" ); + } + + /** + * Format the test for printing.. + * + * @param test Description of Parameter + * @return Description of the Returned Value + */ + protected String formatTest( Test test ) + { + if( test == null ) + { + return "Null Test: "; + } + else + { + return "Testcase: " + test.toString() + ":"; + } + } + + protected java.io.PrintWriter output() + { + return m_output; + } + + protected java.io.PrintWriter resultWriter() + { + return m_resultWriter; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java new file mode 100644 index 000000000..3fab8d7be --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Vector; +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; + +/** + * Some utilities that might be useful when manipulating DOM trees. + * + * @author Stephane Bailliez + */ +public final class DOMUtil +{ + + /** + * unused constructor + */ + private DOMUtil() { } + + + /** + * Iterate over the children of a given node and return the first node that + * has a specific name. + * + * @param parent the node to search child from. Can be null . + * @param tagname the child name we are looking for. Cannot be null + * . + * @return the first child that matches the given name or null if + * the parent is null or if a child does not match the given + * name. + */ + public static Element getChildByTagName( Node parent, String tagname ) + { + if( parent == null ) + { + return null; + } + NodeList childList = parent.getChildNodes(); + final int len = childList.getLength(); + for( int i = 0; i < len; i++ ) + { + Node child = childList.item( i ); + if( child != null && child.getNodeType() == Node.ELEMENT_NODE && + child.getNodeName().equals( tagname ) ) + { + return ( Element )child; + } + } + return null; + } + + /** + * return the attribute value of an element. + * + * @param node the node to get the attribute from. + * @param name the name of the attribute we are looking for the value. + * @return the value of the requested attribute or null if the + * attribute was not found or if node is not an Element + * . + */ + public static String getNodeAttribute( Node node, String name ) + { + if( node instanceof Element ) + { + Element element = ( Element )node; + return element.getAttribute( name ); + } + return null; + } + + /** + * Simple tree walker that will clone recursively a node. This is to avoid + * using parser-specific API such as Sun's changeNodeOwner when we + * are dealing with DOM L1 implementations since cloneNode(boolean) + * will not change the owner document. changeNodeOwner is much + * faster and avoid the costly cloning process. importNode is in + * the DOM L2 interface. + * + * @param parent the node parent to which we should do the import to. + * @param child the node to clone recursively. Its clone will be appended to + * parent . + * @return the cloned node that is appended to parent + */ + public final static Node importNode( Node parent, Node child ) + { + Node copy = null; + final Document doc = parent.getOwnerDocument(); + + switch ( child.getNodeType() ) + { + case Node.CDATA_SECTION_NODE: + copy = doc.createCDATASection( ( ( CDATASection )child ).getData() ); + break; + case Node.COMMENT_NODE: + copy = doc.createComment( ( ( Comment )child ).getData() ); + break; + case Node.DOCUMENT_FRAGMENT_NODE: + copy = doc.createDocumentFragment(); + break; + case Node.ELEMENT_NODE: + final Element elem = doc.createElement( ( ( Element )child ).getTagName() ); + copy = elem; + final NamedNodeMap attributes = child.getAttributes(); + if( attributes != null ) + { + final int size = attributes.getLength(); + for( int i = 0; i < size; i++ ) + { + final Attr attr = ( Attr )attributes.item( i ); + elem.setAttribute( attr.getName(), attr.getValue() ); + } + } + break; + case Node.ENTITY_REFERENCE_NODE: + copy = doc.createEntityReference( child.getNodeName() ); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + final ProcessingInstruction pi = ( ProcessingInstruction )child; + copy = doc.createProcessingInstruction( pi.getTarget(), pi.getData() ); + break; + case Node.TEXT_NODE: + copy = doc.createTextNode( ( ( Text )child ).getData() ); + break; + default: + // this should never happen + throw new IllegalStateException( "Invalid node type: " + child.getNodeType() ); + } + + // okay we have a copy of the child, now the child becomes the parent + // and we are iterating recursively over its children. + try + { + final NodeList children = child.getChildNodes(); + if( children != null ) + { + final int size = children.getLength(); + for( int i = 0; i < size; i++ ) + { + final Node newChild = children.item( i ); + if( newChild != null ) + { + importNode( copy, newChild ); + } + } + } + } + catch( DOMException ignored ) + { + } + + // bingo append it. (this should normally not be done here) + parent.appendChild( copy ); + return copy; + } + + /** + * list a set of node that match a specific filter. The list can be made + * recursively or not. + * + * @param parent the parent node to search from + * @param filter the filter that children should match. + * @param recurse true if you want the list to be made recursively + * otherwise false . + * @return Description of the Returned Value + */ + public static NodeList listChildNodes( Node parent, NodeFilter filter, boolean recurse ) + { + NodeListImpl matches = new NodeListImpl(); + NodeList children = parent.getChildNodes(); + if( children != null ) + { + final int len = children.getLength(); + for( int i = 0; i < len; i++ ) + { + Node child = children.item( i ); + if( filter.accept( child ) ) + { + matches.addElement( child ); + } + if( recurse ) + { + NodeList recmatches = listChildNodes( child, filter, recurse ); + final int reclength = matches.getLength(); + for( int j = 0; j < reclength; j++ ) + { + matches.addElement( recmatches.item( i ) ); + } + } + } + } + return matches; + } + + /** + * Filter interface to be applied when iterating over a DOM tree. Just think + * of it like a FileFilter clone. + * + * @author RT + */ + public interface NodeFilter + { + /** + * @param node the node to check for acceptance. + * @return true if the node is accepted by this filter, + * otherwise false + */ + boolean accept( Node node ); + } + + /** + * custom implementation of a nodelist + * + * @author RT + */ + public static class NodeListImpl extends Vector implements NodeList + { + public int getLength() + { + return size(); + } + + public Node item( int i ) + { + try + { + return ( Node )elementAt( i ); + } + catch( ArrayIndexOutOfBoundsException e ) + { + return null;// conforming to NodeList interface + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java new file mode 100644 index 000000000..bc75a4e5a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * A couple of methods related to enumerations that might be useful. This class + * should probably disappear once the required JDK is set to 1.2 instead of 1.1. + * + * @author Stephane Bailliez + */ +public final class Enumerations +{ + + private Enumerations() { } + + /** + * creates an enumeration from an array of objects. + * + * @param array the array of object to enumerate. + * @return the enumeration over the array of objects. + */ + public static Enumeration fromArray( Object[] array ) + { + return new ArrayEnumeration( array ); + } + + /** + * creates an enumeration from an array of enumeration. The created + * enumeration will sequentially enumerate over all elements of each + * enumeration and skip null enumeration elements in the array. + * + * @param enums the array of enumerations. + * @return the enumeration over the array of enumerations. + */ + public static Enumeration fromCompound( Enumeration[] enums ) + { + return new CompoundEnumeration( enums ); + } + +} + + +/** + * Convenient enumeration over an array of objects. + * + * @author Stephane Bailliez + */ +class ArrayEnumeration implements Enumeration +{ + + /** + * object array + */ + private Object[] array; + + /** + * current index + */ + private int pos; + + /** + * Initialize a new enumeration that wraps an array. + * + * @param array the array of object to enumerate. + */ + public ArrayEnumeration( Object[] array ) + { + this.array = array; + this.pos = 0; + } + + /** + * Tests if this enumeration contains more elements. + * + * @return true if and only if this enumeration object contains + * at least one more element to provide; false otherwise. + */ + public boolean hasMoreElements() + { + return ( pos < array.length ); + } + + /** + * Returns the next element of this enumeration if this enumeration object + * has at least one more element to provide. + * + * @return the next element of this enumeration. + * @throws NoSuchElementException if no more elements exist. + */ + public Object nextElement() + throws NoSuchElementException + { + if( hasMoreElements() ) + { + Object o = array[pos]; + pos++; + return o; + } + throw new NoSuchElementException(); + } +} + +/** + * Convenient enumeration over an array of enumeration. For example:

              + * Enumeration e1 = v1.elements();
              + * while (e1.hasMoreElements()){
              + *    // do something
              + * }
              + * Enumeration e2 = v2.elements();
              + * while (e2.hasMoreElements()){
              + *    // do the same thing
              + * }
              + * 
              can be written as:
              + * Enumeration[] enums = { v1.elements(), v2.elements() };
              + * Enumeration e = Enumerations.fromCompound(enums);
              + * while (e.hasMoreElements()){
              + *    // do something
              + * }
              + * 
              Note that the enumeration will skip null elements in the array. The + * following is thus possible:
              + * Enumeration[] enums = { v1.elements(), null, v2.elements() }; // a null enumeration in the array
              + * Enumeration e = Enumerations.fromCompound(enums);
              + * while (e.hasMoreElements()){
              + *    // do something
              + * }
              + * 
              + * + * @author Stephane Bailliez + */ +class CompoundEnumeration implements Enumeration +{ + + /** + * index in the enums array + */ + private int index = 0; + + /** + * enumeration array + */ + private Enumeration[] enumArray; + + public CompoundEnumeration( Enumeration[] enumarray ) + { + this.enumArray = enumarray; + } + + /** + * Tests if this enumeration contains more elements. + * + * @return true if and only if this enumeration object contains + * at least one more element to provide; false otherwise. + */ + public boolean hasMoreElements() + { + while( index < enumArray.length ) + { + if( enumArray[index] != null && enumArray[index].hasMoreElements() ) + { + return true; + } + index++; + } + return false; + } + + /** + * Returns the next element of this enumeration if this enumeration object + * has at least one more element to provide. + * + * @return the next element of this enumeration. + * @throws NoSuchElementException if no more elements exist. + */ + public Object nextElement() + throws NoSuchElementException + { + if( hasMoreElements() ) + { + return enumArray[index].nextElement(); + } + throw new NoSuchElementException(); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java new file mode 100644 index 000000000..b7db0aa61 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + *

              + * + * A wrapper for the implementations of JUnitResultFormatter. In + * particular, used as a nested <formatter> element in a + * <junit> task.

              + * + * For example,

              + *       <junit printsummary="no" haltonfailure="yes" fork="false">
              + *           <formatter type="plain" usefile="false" />
              + *           <test name="org.apache.ecs.InternationalCharTest" />
              + *       </junit>
              adds a plain type + * implementation (PlainJUnitResultFormatter) to display the + * results of the test.

              + * + * Either the type or the classname attribute must be + * set. + * + * @author Stefan Bodewig + * @see JUnitTask + * @see XMLJUnitResultFormatter + * @see BriefJUnitResultFormatter + * @see PlainJUnitResultFormatter + * @see JUnitResultFormatter + */ +public class FormatterElement +{ + private OutputStream out = System.out; + private boolean useFile = true; + + private String classname; + private String extension; + private File outFile; + + /** + *

              + * + * Set name of class to be used as the formatter.

              + * + * This class must implement JUnitResultFormatter + * + * @param classname The new Classname value + */ + public void setClassname( String classname ) + { + this.classname = classname; + } + + public void setExtension( String ext ) + { + this.extension = ext; + } + + /** + *

              + * + * Set output stream for formatter to use.

              + * + * Defaults to standard out. + * + * @param out The new Output value + */ + public void setOutput( OutputStream out ) + { + this.out = out; + } + + /** + *

              + * + * Quick way to use a standard formatter.

              + * + * At the moment, there are three supported standard formatters. + *

                + *
              • The xml type uses a XMLJUnitResultFormatter + * . + *
              • The brief type uses a BriefJUnitResultFormatter + * . + *
              • The plain type (the default) uses a PlainJUnitResultFormatter + * . + *
              + *

              + * + * Sets classname attribute - so you can't use that attribute + * if you use this one. + * + * @param type The new Type value + */ + public void setType( TypeAttribute type ) + { + if( "xml".equals( type.getValue() ) ) + { + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter" ); + setExtension( ".xml" ); + } + else + { + if( "brief".equals( type.getValue() ) ) + { + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.BriefJUnitResultFormatter" ); + } + else + {// must be plain, ensured by TypeAttribute + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.PlainJUnitResultFormatter" ); + } + setExtension( ".txt" ); + } + } + + /** + * Set whether the formatter should log to file. + * + * @param useFile The new UseFile value + */ + public void setUseFile( boolean useFile ) + { + this.useFile = useFile; + } + + /** + * Get name of class to be used as the formatter. + * + * @return The Classname value + */ + public String getClassname() + { + return classname; + } + + public String getExtension() + { + return extension; + } + + /** + *

              + * + * Set the file which the formatte should log to.

              + * + * Note that logging to file must be enabled . + * + * @param out The new Outfile value + */ + void setOutfile( File out ) + { + this.outFile = out; + } + + /** + * Get whether the formatter should log to file. + * + * @return The UseFile value + */ + boolean getUseFile() + { + return useFile; + } + + JUnitResultFormatter createFormatter() + throws BuildException + { + if( classname == null ) + { + throw new BuildException( "you must specify type or classname" ); + } + + Class f = null; + try + { + f = Class.forName( classname ); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( e ); + } + + Object o = null; + try + { + o = f.newInstance(); + } + catch( InstantiationException e ) + { + throw new BuildException( e ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( e ); + } + + if( !( o instanceof JUnitResultFormatter ) ) + { + throw new BuildException( classname + " is not a JUnitResultFormatter" ); + } + + JUnitResultFormatter r = ( JUnitResultFormatter )o; + + if( useFile && outFile != null ) + { + try + { + out = new FileOutputStream( outFile ); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + r.setOutput( out ); + return r; + } + + /** + *

              + * + * Enumerated attribute with the values "plain", "xml" and "brief".

              + * + * Use to enumerate options for type attribute. + * + * @author RT + */ + public static class TypeAttribute extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"plain", "xml", "brief"}; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java new file mode 100644 index 000000000..7f5f3a4ed --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import junit.framework.TestListener; +import org.apache.tools.ant.BuildException; + +/** + * This Interface describes classes that format the results of a JUnit testrun. + * + * @author Stefan Bodewig + */ +public interface JUnitResultFormatter extends TestListener +{ + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + void startTestSuite( JUnitTest suite ) + throws BuildException; + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + void endTestSuite( JUnitTest suite ) + throws BuildException; + + /** + * Sets the stream the formatter is supposed to write its results to. + * + * @param out The new Output value + */ + void setOutput( OutputStream out ); + + /** + * This is what the test has written to System.out + * + * @param out The new SystemOutput value + */ + void setSystemOutput( String out ); + + /** + * This is what the test has written to System.err + * + * @param err The new SystemError value + */ + void setSystemError( String err ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java new file mode 100644 index 000000000..b53a45da3 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java @@ -0,0 +1,812 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteWatchdog; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; + +/** + * Ant task to run JUnit tests.

              + * + * JUnit is a framework to create unit test. It has been initially created by + * Erich Gamma and Kent Beck. JUnit can be found at http://www.junit.org .

              + * + * JUnitTask can run a single specific JUnitTest using + * the test element. For example, the following target

              + *   <target name="test-int-chars" depends="jar-test">
              + *       <echo message="testing international characters"/>
              + *       <junit printsummary="no" haltonfailure="yes" fork="false">
              + *           <classpath refid="classpath"/>
              + *           <formatter type="plain" usefile="false" />
              + *           <test name="org.apache.ecs.InternationalCharTest" />
              + *       </junit>
              + *   </target>
              + * 
              runs a single junit test (org.apache.ecs.InternationalCharTest + * ) in the current VM using the path with id classpath as + * classpath and presents the results formatted using the standard plain + * formatter on the command line.

              + * + * This task can also run batches of tests. The batchtest element + * creates a BatchTest based on a fileset. This allows, for + * example, all classes found in directory to be run as testcases. For example, + *

              + * <target name="run-tests" depends="dump-info,compile-tests" if="junit.present">
              + *   <junit printsummary="no" haltonfailure="yes" fork="${junit.fork}">
              + *     <jvmarg value="-classic"/>
              + *     <classpath refid="tests-classpath"/>
              + *     <sysproperty key="build.tests" value="${build.tests}"/>
              + *     <formatter type="brief" usefile="false" />
              + *     <batchtest>
              + *       <fileset dir="${tests.dir}">
              + *         <include name="**/*Test*" />
              + *       </fileset>
              + *     </batchtest>
              + *   </junit>
              + * </target>
              + * 
              this target finds any classes with a test + * directory anywhere in their path (under the top ${tests.dir}, of + * course) and creates JUnitTest's for each one.

              + * + * Of course, <junit> and <batch> elements + * can be combined for more complex tests. For an example, see the ant build.xml + * target run-tests (the second example is an edited version).

              + * + * To spawn a new Java VM to prevent interferences between different testcases, + * you need to enable fork. A number of attributes and elements + * allow you to set up how this JVM runs. + *

                + *
              • {@link #setTimeout} property sets the maximum time allowed before a + * test is 'timed out' + *
              • {@link #setMaxmemory} property sets memory assignment for the forked + * jvm + *
              • {@link #setJvm} property allows the jvm to be specified + *
              • The <jvmarg> element sets arguements to be passed + * to the forked jvm + *
              + * + * + * @author Thomas Haas + * @author Stefan Bodewig + * @author Stephane Bailliez + * @author Gerrit Riessen + * @author Erik Hatcher + * @see JUnitTest + * @see BatchTest + */ +public class JUnitTask extends Task +{ + + private CommandlineJava commandline = new CommandlineJava(); + private Vector tests = new Vector(); + private Vector batchTests = new Vector(); + private Vector formatters = new Vector(); + private File dir = null; + + private Integer timeout = null; + private boolean summary = false; + private String summaryValue = ""; + private boolean filtertrace = true; + private JUnitTestRunner runner = null; + + /** + * Creates a new JUnitRunner and enables fork of a new Java VM. + * + * @exception Exception Description of Exception + */ + public JUnitTask() + throws Exception + { + commandline.setClassname( "org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" ); + } + + /** + * The directory to invoke the VM in. Ignored if no JVM is forked. + * + * @param dir the directory to invoke the JVM from. + * @see #setFork(boolean) + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Tells this task to set the named property to "true" when there is a error + * in a test. This property is applied on all BatchTest (batchtest) and + * JUnitTest (test), however, it can possibly be overriden by their own + * properties. + * + * @param propertyName The new ErrorProperty value + */ + public void setErrorProperty( String propertyName ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setErrorProperty( propertyName ); + } + } + + /** + * Tells this task to set the named property to "true" when there is a + * failure in a test. This property is applied on all BatchTest (batchtest) + * and JUnitTest (test), however, it can possibly be overriden by their own + * properties. + * + * @param propertyName The new FailureProperty value + */ + public void setFailureProperty( String propertyName ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFailureProperty( propertyName ); + } + } + + /** + * Tells this task whether to smartly filter the stack frames of JUnit + * testcase errors and failures before reporting them. This property is + * applied on all BatchTest (batchtest) and JUnitTest (test) however it can + * possibly be overridden by their own properties. + * + * @param value false if it should not filter, otherwise true + * + */ + public void setFiltertrace( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFiltertrace( value ); + } + } + + /** + * Tells whether a JVM should be forked for each testcase. It avoids + * interference between testcases and possibly avoids hanging the build. + * this property is applied on all BatchTest (batchtest) and JUnitTest + * (test) however it can possibly be overridden by their own properties. + * + * @param value true if a JVM should be forked, otherwise false + * + * @see #setTimeout + */ + public void setFork( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFork( value ); + } + } + + /** + * Tells this task to halt when there is an error in a test. this property + * is applied on all BatchTest (batchtest) and JUnitTest (test) however it + * can possibly be overridden by their own properties. + * + * @param value true if it should halt, otherwise false + */ + public void setHaltonerror( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setHaltonerror( value ); + } + } + + /** + * Tells this task to halt when there is a failure in a test. this property + * is applied on all BatchTest (batchtest) and JUnitTest (test) however it + * can possibly be overridden by their own properties. + * + * @param value true if it should halt, otherwise false + */ + public void setHaltonfailure( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setHaltonfailure( value ); + } + } + + /** + * Set a new VM to execute the testcase. Default is java . Ignored + * if no JVM is forked. + * + * @param value the new VM to use instead of java + * @see #setFork(boolean) + */ + public void setJvm( String value ) + { + commandline.setVm( value ); + } + + /** + * Set the maximum memory to be used by all forked JVMs. + * + * @param max the value as defined by -mx or -Xmx in the + * java command line options. + */ + public void setMaxmemory( String max ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + createJvmarg().setValue( "-mx" + max ); + } + else + { + createJvmarg().setValue( "-Xmx" + max ); + } + } + + /** + * Tells whether the task should print a short summary of the task. + * + * @param value true to print a summary, withOutAndErr to + * include the test's output as well, false otherwise. + * @see SummaryJUnitResultFormatter + */ + public void setPrintsummary( SummaryAttribute value ) + { + summaryValue = value.getValue(); + summary = value.asBoolean(); + } + + /** + * Set the timeout value (in milliseconds). If the test is running for more + * than this value, the test will be canceled. (works only when in 'fork' + * mode). + * + * @param value the maximum time (in milliseconds) allowed before declaring + * the test as 'timed-out' + * @see #setFork(boolean) + */ + public void setTimeout( Integer value ) + { + timeout = value; + } + + /** + * Add a new formatter to all tests of this task. + * + * @param fe The feature to be added to the Formatter attribute + */ + public void addFormatter( FormatterElement fe ) + { + formatters.addElement( fe ); + } + + /** + * Add a nested sysproperty element. This might be useful to tranfer Ant + * properties to the testcases when JVM forking is not enabled. + * + * @param sysp The feature to be added to the Sysproperty attribute + */ + public void addSysproperty( Environment.Variable sysp ) + { + commandline.addSysproperty( sysp ); + } + + /** + * Add a new single testcase. + * + * @param test a new single testcase + * @see JUnitTest + */ + public void addTest( JUnitTest test ) + { + tests.addElement( test ); + } + + /** + * Create a new set of testcases (also called ..batchtest) and add it to the + * list. + * + * @return a new instance of a batch test. + * @see BatchTest + */ + public BatchTest createBatchTest() + { + BatchTest test = new BatchTest( project ); + batchTests.addElement( test ); + return test; + } + + /** + * <classpath> allows classpath to be set for tests. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return commandline.createClasspath( project ).createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg() + { + return commandline.createVmArgument(); + } + + /** + * Runs the testcase. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Enumeration list = getIndividualTests(); + while( list.hasMoreElements() ) + { + JUnitTest test = ( JUnitTest )list.nextElement(); + if( test.shouldRun( project ) ) + { + execute( test ); + } + } + } + + /** + * Adds the jars or directories containing Ant, this task and JUnit to the + * classpath - this should make the forked JVM work without having to + * specify them directly. + */ + public void init() + { + addClasspathEntry( "/junit/framework/TestCase.class" ); + addClasspathEntry( "/org/apache/tools/ant/Task.class" ); + addClasspathEntry( "/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.class" ); + } + + /** + * Get the default output for a formatter. + * + * @return The DefaultOutput value + */ + protected OutputStream getDefaultOutput() + { + return new LogOutputStream( this, Project.MSG_INFO ); + } + + /** + * Merge all individual tests from the batchtest with all individual tests + * and return an enumeration over all JUnitTest . + * + * @return The IndividualTests value + */ + protected Enumeration getIndividualTests() + { + Enumeration[] enums = new Enumeration[batchTests.size() + 1]; + for( int i = 0; i < batchTests.size(); i++ ) + { + BatchTest batchtest = ( BatchTest )batchTests.elementAt( i ); + enums[i] = batchtest.elements(); + } + enums[enums.length - 1] = tests.elements(); + return Enumerations.fromCompound( enums ); + } + + /** + * return the file or null if does not use a file + * + * @param fe Description of Parameter + * @param test Description of Parameter + * @return The Output value + */ + protected File getOutput( FormatterElement fe, JUnitTest test ) + { + if( fe.getUseFile() ) + { + String filename = test.getOutfile() + fe.getExtension(); + File destFile = new File( test.getTodir(), filename ); + String absFilename = destFile.getAbsolutePath(); + return project.resolveFile( absFilename ); + } + return null; + } + + /** + * Search for the given resource and add the directory or archive that + * contains it to the classpath.

              + * + * Doesn't work for archives in JDK 1.1 as the URL returned by getResource + * doesn't contain the name of the archive.

              + * + * @param resource The feature to be added to the ClasspathEntry attribute + */ + protected void addClasspathEntry( String resource ) + { + URL url = getClass().getResource( resource ); + if( url != null ) + { + String u = url.toString(); + if( u.startsWith( "jar:file:" ) ) + { + int pling = u.indexOf( "!" ); + String jarName = u.substring( 9, pling ); + log( "Implicitly adding " + jarName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( jarName ) ).getAbsolutePath() ) ); + } + else if( u.startsWith( "file:" ) ) + { + int tail = u.indexOf( resource ); + String dirName = u.substring( 5, tail ); + log( "Implicitly adding " + dirName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( dirName ) ).getAbsolutePath() ) ); + } + else + { + log( "Don\'t know how to handle resource URL " + u, + Project.MSG_DEBUG ); + } + } + else + { + log( "Couldn\'t find " + resource, Project.MSG_DEBUG ); + } + } + + protected Enumeration allTests() + { + Enumeration[] enums = {tests.elements(), batchTests.elements()}; + return Enumerations.fromCompound( enums ); + } + + /** + * @return null if there is a timeout value, otherwise the watchdog + * instance. + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + if( timeout == null ) + { + return null; + } + return new ExecuteWatchdog( timeout.intValue() ); + } + + /** + * Run the tests. + * + * @param test Description of Parameter + * @exception BuildException Description of Exception + */ + protected void execute( JUnitTest test ) + throws BuildException + { + // set the default values if not specified + //@todo should be moved to the test class instead. + if( test.getTodir() == null ) + { + test.setTodir( project.resolveFile( "." ) ); + } + + if( test.getOutfile() == null ) + { + test.setOutfile( "TEST-" + test.getName() ); + } + + // execute the test and get the return code + int exitValue = JUnitTestRunner.ERRORS; + boolean wasKilled = false; + if( !test.getFork() ) + { + exitValue = executeInVM( test ); + } + else + { + ExecuteWatchdog watchdog = createWatchdog(); + exitValue = executeAsForked( test, watchdog ); + // null watchdog means no timeout, you'd better not check with null + if( watchdog != null ) + { + wasKilled = watchdog.killedProcess(); + } + } + + // if there is an error/failure and that it should halt, stop everything otherwise + // just log a statement + boolean errorOccurredHere = exitValue == JUnitTestRunner.ERRORS; + boolean failureOccurredHere = exitValue != JUnitTestRunner.SUCCESS; + if( errorOccurredHere || failureOccurredHere ) + { + if( errorOccurredHere && test.getHaltonerror() + || failureOccurredHere && test.getHaltonfailure() ) + { + throw new BuildException( "Test " + test.getName() + " failed" + + ( wasKilled ? " (timeout)" : "" ), + location ); + } + else + { + log( "TEST " + test.getName() + " FAILED" + + ( wasKilled ? " (timeout)" : "" ), Project.MSG_ERR ); + if( errorOccurredHere && test.getErrorProperty() != null ) + { + project.setProperty( test.getErrorProperty(), "true" ); + } + if( failureOccurredHere && test.getFailureProperty() != null ) + { + project.setProperty( test.getFailureProperty(), "true" ); + } + } + } + } + + protected void handleErrorOutput( String line ) + { + if( runner != null ) + { + runner.handleErrorOutput( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + // in VM is not very nice since it could probably hang the + // whole build. IMHO this method should be avoided and it would be best + // to remove it in future versions. TBD. (SBa) + + + protected void handleOutput( String line ) + { + if( runner != null ) + { + runner.handleOutput( line ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Execute a testcase by forking a new JVM. The command will block until it + * finishes. To know if the process was destroyed or not, use the + * killedProcess() method of the watchdog class. + * + * @param test the testcase to execute. + * @param watchdog the watchdog in charge of cancelling the test if it + * exceeds a certain amount of time. Can be null , in this + * case the test could probably hang forever. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int executeAsForked( JUnitTest test, ExecuteWatchdog watchdog ) + throws BuildException + { + CommandlineJava cmd = ( CommandlineJava )commandline.clone(); + + cmd.setClassname( "org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" ); + cmd.createArgument().setValue( test.getName() ); + cmd.createArgument().setValue( "filtertrace=" + test.getFiltertrace() ); + cmd.createArgument().setValue( "haltOnError=" + test.getHaltonerror() ); + cmd.createArgument().setValue( "haltOnFailure=" + test.getHaltonfailure() ); + if( summary ) + { + log( "Running " + test.getName(), Project.MSG_INFO ); + cmd.createArgument().setValue( "formatter=org.apache.tools.ant.taskdefs.optional.junit.SummaryJUnitResultFormatter" ); + } + + StringBuffer formatterArg = new StringBuffer( 128 ); + final FormatterElement[] feArray = mergeFormatters( test ); + for( int i = 0; i < feArray.length; i++ ) + { + FormatterElement fe = feArray[i]; + formatterArg.append( "formatter=" ); + formatterArg.append( fe.getClassname() ); + File outFile = getOutput( fe, test ); + if( outFile != null ) + { + formatterArg.append( "," ); + formatterArg.append( outFile ); + } + cmd.createArgument().setValue( formatterArg.toString() ); + formatterArg.setLength( 0 ); + } + + // Create a temporary file to pass the Ant properties to the forked test + File propsFile = new File( "junit" + ( new Random( System.currentTimeMillis() ) ).nextLong() + ".properties" ); + cmd.createArgument().setValue( "propsfile=" + propsFile.getAbsolutePath() ); + Hashtable p = project.getProperties(); + Properties props = new Properties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + try + { + FileOutputStream outstream = new FileOutputStream( propsFile ); + props.save( outstream, "Ant JUnitTask generated properties file" ); + outstream.close(); + } + catch( java.io.IOException e ) + { + throw new BuildException( "Error creating temporary properties file.", e, location ); + } + + Execute execute = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ), watchdog ); + execute.setCommandline( cmd.getCommandline() ); + execute.setAntRun( project ); + if( dir != null ) + { + execute.setWorkingDirectory( dir ); + } + + log( "Executing: " + cmd.toString(), Project.MSG_VERBOSE ); + int retVal; + try + { + retVal = execute.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Process fork failed.", e, location ); + } + finally + { + if( !propsFile.delete() ) + throw new BuildException( "Could not delete temporary properties file." ); + } + + return retVal; + } + + /** + * Execute inside VM. + * + * @param test Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int executeInVM( JUnitTest test ) + throws BuildException + { + test.setProperties( project.getProperties() ); + if( dir != null ) + { + log( "dir attribute ignored if running in the same VM", Project.MSG_WARN ); + } + + CommandlineJava.SysProperties sysProperties = commandline.getSystemProperties(); + if( sysProperties != null ) + { + sysProperties.setSystem(); + } + try + { + log( "Using System properties " + System.getProperties(), Project.MSG_VERBOSE ); + AntClassLoader cl = null; + Path classpath = commandline.getClasspath(); + if( classpath != null ) + { + log( "Using CLASSPATH " + classpath, Project.MSG_VERBOSE ); + + cl = new AntClassLoader( null, project, classpath, false ); + // make sure the test will be accepted as a TestCase + cl.addSystemPackageRoot( "junit" ); + // will cause trouble in JDK 1.1 if omitted + cl.addSystemPackageRoot( "org.apache.tools.ant" ); + } + runner = new JUnitTestRunner( test, test.getHaltonerror(), test.getFiltertrace(), test.getHaltonfailure(), cl ); + if( summary ) + { + log( "Running " + test.getName(), Project.MSG_INFO ); + + SummaryJUnitResultFormatter f = + new SummaryJUnitResultFormatter(); + f.setWithOutAndErr( "withoutanderr".equalsIgnoreCase( summaryValue ) ); + f.setOutput( getDefaultOutput() ); + runner.addFormatter( f ); + } + + final FormatterElement[] feArray = mergeFormatters( test ); + for( int i = 0; i < feArray.length; i++ ) + { + FormatterElement fe = feArray[i]; + File outFile = getOutput( fe, test ); + if( outFile != null ) + { + fe.setOutfile( outFile ); + } + else + { + fe.setOutput( getDefaultOutput() ); + } + runner.addFormatter( fe.createFormatter() ); + } + + runner.run(); + return runner.getRetCode(); + } + finally + { + if( sysProperties != null ) + { + sysProperties.restoreSystem(); + } + } + } + + private FormatterElement[] mergeFormatters( JUnitTest test ) + { + Vector feVector = ( Vector )formatters.clone(); + test.addFormattersTo( feVector ); + FormatterElement[] feArray = new FormatterElement[feVector.size()]; + feVector.copyInto( feArray ); + return feArray; + } + + /** + * Print summary enumeration values. + * + * @author RT + */ + public static class SummaryAttribute extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"true", "yes", "false", "no", + "on", "off", "withOutAndErr"}; + } + + public boolean asBoolean() + { + return "true".equals( value ) + || "on".equals( value ) + || "yes".equals( value ) + || "withOutAndErr".equals( value ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java new file mode 100644 index 000000000..93c2b2d01 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.Project; + +/** + *

              + * + * Run a single JUnit test.

              + * + * The JUnit test is actually run by {@link JUnitTestRunner}. So read the doc + * comments for that class :) + * + * @author Thomas Haas + * @author Stefan Bodewig , + * @author Stephane Bailliez + * @see JUnitTask + * @see JUnitTestRunner + */ +public class JUnitTest extends BaseTest +{ + + /** + * the name of the test case + */ + private String name = null; + + /** + * the name of the result file + */ + private String outfile = null; + + // Snapshot of the system properties + private Properties props = null; + private long runTime; + + // @todo this is duplicating TestResult information. Only the time is not + // part of the result. So we'd better derive a new class from TestResult + // and deal with it. (SB) + private long runs, failures, errors; + + public JUnitTest() { } + + public JUnitTest( String name ) + { + this.name = name; + } + + public JUnitTest( String name, boolean haltOnError, boolean haltOnFailure, boolean filtertrace ) + { + this.name = name; + this.haltOnError = haltOnError; + this.haltOnFail = haltOnFailure; + this.filtertrace = filtertrace; + } + + public void setCounts( long runs, long failures, long errors ) + { + this.runs = runs; + this.failures = failures; + this.errors = errors; + } + + /** + * Set the name of the test class. + * + * @param value The new Name value + */ + public void setName( String value ) + { + name = value; + } + + /** + * Set the name of the output file. + * + * @param value The new Outfile value + */ + public void setOutfile( String value ) + { + outfile = value; + } + + public void setProperties( Hashtable p ) + { + props = new Properties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + } + + public void setRunTime( long runTime ) + { + this.runTime = runTime; + } + + public FormatterElement[] getFormatters() + { + FormatterElement[] fes = new FormatterElement[formatters.size()]; + formatters.copyInto( fes ); + return fes; + } + + /** + * Get the name of the test class. + * + * @return The Name value + */ + public String getName() + { + return name; + } + + /** + * Get the name of the output file + * + * @return the name of the output file. + */ + public String getOutfile() + { + return outfile; + } + + public Properties getProperties() + { + return props; + } + + public long getRunTime() + { + return runTime; + } + + public long errorCount() + { + return errors; + } + + public long failureCount() + { + return failures; + } + + public long runCount() + { + return runs; + } + + public boolean shouldRun( Project p ) + { + if( ifProperty != null && p.getProperty( ifProperty ) == null ) + { + return false; + } + else if( unlessProperty != null && + p.getProperty( unlessProperty ) != null ) + { + return false; + } + + return true; + } + + /** + * Convenient method to add formatters to a vector + * + * @param v The feature to be added to the FormattersTo attribute + */ + void addFormattersTo( Vector v ) + { + final int count = formatters.size(); + for( int i = 0; i < count; i++ ) + { + v.addElement( formatters.elementAt( i ) ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java new file mode 100644 index 000000000..29170e402 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java @@ -0,0 +1,637 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Vector; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + +/** + * Simple Testrunner for JUnit that runs all tests of a testsuite.

              + * + * This TestRunner expects a name of a TestCase class as its argument. If this + * class provides a static suite() method it will be called and the resulting + * Test will be run. So, the signature should be

              
              + *     public static junit.framework.Test suite()
              + * 

              + * + * If no such method exists, all public methods starting with "test" and taking + * no argument will be run.

              + * + * Summary output is generated at the end. + * + * @author Stefan Bodewig + * @author Erik Hatcher + */ + +public class JUnitTestRunner implements TestListener +{ + + /** + * No problems with this test. + */ + public final static int SUCCESS = 0; + + /** + * Some tests failed. + */ + public final static int FAILURES = 1; + + /** + * An error occured. + */ + public final static int ERRORS = 2; + + /** + * Do we filter junit.*.* stack frames out of failure and error exceptions. + */ + private static boolean filtertrace = true; + + private final static String[] DEFAULT_TRACE_FILTERS = new String[]{ + "junit.framework.TestCase", + "junit.framework.TestResult", + "junit.framework.TestSuite", + "junit.framework.Assert.", // don't filter AssertionFailure + "junit.swingui.TestRunner", + "junit.awtui.TestRunner", + "junit.textui.TestRunner", + "java.lang.reflect.Method.invoke(", + "org.apache.tools.ant." + }; + + private static Vector fromCmdLine = new Vector(); + + /** + * Holds the registered formatters. + */ + private Vector formatters = new Vector(); + + /** + * Do we stop on errors. + */ + private boolean haltOnError = false; + + /** + * Do we stop on test failures. + */ + private boolean haltOnFailure = false; + + /** + * The corresponding testsuite. + */ + private Test suite = null; + + /** + * Returncode + */ + private int retCode = SUCCESS; + + /** + * Exception caught in constructor. + */ + private Exception exception; + + /** + * The TestSuite we are currently running. + */ + private JUnitTest junitTest; + + /** + * Collects TestResults. + */ + private TestResult res; + + /** + * output written during the test + */ + private PrintStream systemError; + + /** + * Error output during the test + */ + private PrintStream systemOut; + + /** + * Constructor for fork=true or when the user hasn't specified a classpath. + * + * @param test Description of Parameter + * @param haltOnError Description of Parameter + * @param filtertrace Description of Parameter + * @param haltOnFailure Description of Parameter + */ + public JUnitTestRunner( JUnitTest test, boolean haltOnError, boolean filtertrace, + boolean haltOnFailure ) + { + this( test, haltOnError, filtertrace, haltOnFailure, null ); + } + + /** + * Constructor to use when the user has specified a classpath. + * + * @param test Description of Parameter + * @param haltOnError Description of Parameter + * @param filtertrace Description of Parameter + * @param haltOnFailure Description of Parameter + * @param loader Description of Parameter + */ + public JUnitTestRunner( JUnitTest test, boolean haltOnError, boolean filtertrace, + boolean haltOnFailure, ClassLoader loader ) + { + //JUnitTestRunner.filtertrace = filtertrace; + this.filtertrace = filtertrace; + this.junitTest = test; + this.haltOnError = haltOnError; + this.haltOnFailure = haltOnFailure; + + try + { + Class testClass = null; + if( loader == null ) + { + testClass = Class.forName( test.getName() ); + } + else + { + testClass = loader.loadClass( test.getName() ); + AntClassLoader.initializeClass( testClass ); + } + + Method suiteMethod = null; + try + { + // check if there is a suite method + suiteMethod = testClass.getMethod( "suite", new Class[0] ); + } + catch( Exception e ) + { + // no appropriate suite method found. We don't report any + // error here since it might be perfectly normal. We don't + // know exactly what is the cause, but we're doing exactly + // the same as JUnit TestRunner do. We swallow the exceptions. + } + if( suiteMethod != null ) + { + // if there is a suite method available, then try + // to extract the suite from it. If there is an error + // here it will be caught below and reported. + suite = ( Test )suiteMethod.invoke( null, new Class[0] ); + } + else + { + // try to extract a test suite automatically + // this will generate warnings if the class is no suitable Test + suite = new TestSuite( testClass ); + } + + } + catch( Exception e ) + { + retCode = ERRORS; + exception = e; + } + } + + /** + * Returns a filtered stack trace. This is ripped out of + * junit.runner.BaseTestRunner. Scott M. Stirling. + * + * @param t Description of Parameter + * @return The FilteredTrace value + */ + public static String getFilteredTrace( Throwable t ) + { + String trace = StringUtils.getStackTrace( t ); + return JUnitTestRunner.filterStack( trace ); + } + + /** + * Filters stack frames from internal JUnit and Ant classes + * + * @param stack Description of Parameter + * @return Description of the Returned Value + */ + public static String filterStack( String stack ) + { + if( !filtertrace ) + { + return stack; + } + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter( sw ); + StringReader sr = new StringReader( stack ); + BufferedReader br = new BufferedReader( sr ); + + String line; + try + { + while( ( line = br.readLine() ) != null ) + { + if( !filterLine( line ) ) + { + pw.println( line ); + } + } + } + catch( Exception IOException ) + { + return stack;// return the stack unfiltered + } + return sw.toString(); + } + + /** + * Entry point for standalone (forked) mode. Parameters: testcaseclassname + * plus parameters in the format key=value, none of which is required. + * + * + * + * + * + * + * key + * + * + * + * description + * + * + * + * default value + * + * + * + * + * + * + * + * haltOnError + * + * + * + * halt test on errors? + * + * + * + * false + * + * + * + * + * + * + * + * haltOnFailure + * + * + * + * halt test on failures? + * + * + * + * false + * + * + * + * + * + * + * + * formatter + * + * + * + * A JUnitResultFormatter given as classname,filename. If filename is + * ommitted, System.out is assumed. + * + * + * + * none + * + * + * + * + * + * + * + * @param args The command line arguments + * @exception IOException Description of Exception + */ + public static void main( String[] args ) + throws IOException + { + boolean exitAtEnd = true; + boolean haltError = false; + boolean haltFail = false; + boolean stackfilter = true; + Properties props = new Properties(); + + if( args.length == 0 ) + { + System.err.println( "required argument TestClassName missing" ); + System.exit( ERRORS ); + } + + for( int i = 1; i < args.length; i++ ) + { + if( args[i].startsWith( "haltOnError=" ) ) + { + haltError = Project.toBoolean( args[i].substring( 12 ) ); + } + else if( args[i].startsWith( "haltOnFailure=" ) ) + { + haltFail = Project.toBoolean( args[i].substring( 14 ) ); + } + else if( args[i].startsWith( "filtertrace=" ) ) + { + stackfilter = Project.toBoolean( args[i].substring( 12 ) ); + } + else if( args[i].startsWith( "formatter=" ) ) + { + try + { + createAndStoreFormatter( args[i].substring( 10 ) ); + } + catch( BuildException be ) + { + System.err.println( be.getMessage() ); + System.exit( ERRORS ); + } + } + else if( args[i].startsWith( "propsfile=" ) ) + { + FileInputStream in = new FileInputStream( args[i].substring( 10 ) ); + props.load( in ); + in.close(); + } + } + + JUnitTest t = new JUnitTest( args[0] ); + + // Add/overlay system properties on the properties from the Ant project + Hashtable p = System.getProperties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + t.setProperties( props ); + + JUnitTestRunner runner = new JUnitTestRunner( t, haltError, stackfilter, haltFail ); + transferFormatters( runner ); + runner.run(); + System.exit( runner.getRetCode() ); + } + + /** + * Line format is: formatter=(, + * + * )? + * + * @param line Description of Parameter + * @exception BuildException Description of Exception + */ + private static void createAndStoreFormatter( String line ) + throws BuildException + { + FormatterElement fe = new FormatterElement(); + int pos = line.indexOf( ',' ); + if( pos == -1 ) + { + fe.setClassname( line ); + } + else + { + fe.setClassname( line.substring( 0, pos ) ); + fe.setOutfile( new File( line.substring( pos + 1 ) ) ); + } + fromCmdLine.addElement( fe.createFormatter() ); + } + + private static boolean filterLine( String line ) + { + for( int i = 0; i < DEFAULT_TRACE_FILTERS.length; i++ ) + { + if( line.indexOf( DEFAULT_TRACE_FILTERS[i] ) > 0 ) + { + return true; + } + } + return false; + } + + private static void transferFormatters( JUnitTestRunner runner ) + { + for( int i = 0; i < fromCmdLine.size(); i++ ) + { + runner.addFormatter( ( JUnitResultFormatter )fromCmdLine.elementAt( i ) ); + } + } + + /** + * Returns what System.exit() would return in the standalone version. + * + * @return 2 if errors occurred, 1 if tests failed else 0. + */ + public int getRetCode() + { + return retCode; + } + + /** + * Interface TestListener.

              + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + if( haltOnError ) + { + res.stop(); + } + } + + /** + * Interface TestListener for JUnit <= 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + if( haltOnFailure ) + { + res.stop(); + } + } + + /** + * Interface TestListener for JUnit > 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + public void addFormatter( JUnitResultFormatter f ) + { + formatters.addElement( f ); + } + + /** + * Interface TestListener.

              + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + public void run() + { + res = new TestResult(); + res.addListener( this ); + for( int i = 0; i < formatters.size(); i++ ) + { + res.addListener( ( TestListener )formatters.elementAt( i ) ); + } + + long start = System.currentTimeMillis(); + + fireStartTestSuite(); + if( exception != null ) + {// had an exception in the constructor + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( TestListener )formatters.elementAt( i ) ).addError( null, + exception ); + } + junitTest.setCounts( 1, 0, 1 ); + junitTest.setRunTime( 0 ); + } + else + { + + ByteArrayOutputStream errStrm = new ByteArrayOutputStream(); + systemError = new PrintStream( errStrm ); + + ByteArrayOutputStream outStrm = new ByteArrayOutputStream(); + systemOut = new PrintStream( outStrm ); + + try + { + suite.run( res ); + } + finally + { + systemError.close(); + systemError = null; + systemOut.close(); + systemOut = null; + sendOutAndErr( new String( outStrm.toByteArray() ), + new String( errStrm.toByteArray() ) ); + + junitTest.setCounts( res.runCount(), res.failureCount(), + res.errorCount() ); + junitTest.setRunTime( System.currentTimeMillis() - start ); + } + } + fireEndTestSuite(); + + if( retCode != SUCCESS || res.errorCount() != 0 ) + { + retCode = ERRORS; + } + else if( res.failureCount() != 0 ) + { + retCode = FAILURES; + } + } + + /** + * Interface TestListener.

              + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) { } + + protected void handleErrorOutput( String line ) + { + if( systemError != null ) + { + systemError.println( line ); + } + } + + protected void handleOutput( String line ) + { + if( systemOut != null ) + { + systemOut.println( line ); + } + } + + private void fireEndTestSuite() + { + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( JUnitResultFormatter )formatters.elementAt( i ) ).endTestSuite( junitTest ); + } + } + + private void fireStartTestSuite() + { + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( JUnitResultFormatter )formatters.elementAt( i ) ).startTestSuite( junitTest ); + } + } + + private void sendOutAndErr( String out, String err ) + { + for( int i = 0; i < formatters.size(); i++ ) + { + JUnitResultFormatter formatter = + ( ( JUnitResultFormatter )formatters.elementAt( i ) ); + + formatter.setSystemOutput( out ); + formatter.setSystemError( err ); + } + } + +}// JUnitTestRunner diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java new file mode 100644 index 000000000..082af4960 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.lang.reflect.Method; +import junit.framework.Test; +import junit.framework.TestCase; + +/** + * Work around for some changes to the public JUnit API between different JUnit + * releases. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class JUnitVersionHelper +{ + + private static Method testCaseName = null; + static + { + try + { + testCaseName = TestCase.class.getMethod( "getName", new Class[0] ); + } + catch( NoSuchMethodException e ) + { + // pre JUnit 3.7 + try + { + testCaseName = TestCase.class.getMethod( "name", new Class[0] ); + } + catch( NoSuchMethodException e2 ) + {} + } + } + + /** + * JUnit 3.7 introduces TestCase.getName() and subsequent versions of JUnit + * remove the old name() method. This method provides access to the name of + * a TestCase via reflection that is supposed to work with version before + * and after JUnit 3.7. + * + * @param t Description of Parameter + * @return The TestCaseName value + */ + public static String getTestCaseName( Test t ) + { + if( t instanceof TestCase && testCaseName != null ) + { + try + { + return ( String )testCaseName.invoke( t, new Object[0] ); + } + catch( Throwable e ) + {} + } + return "unknown"; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java new file mode 100644 index 000000000..239da0895 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.NumberFormat; +import java.util.Hashtable; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import org.apache.tools.ant.BuildException; + +/** + * Prints plain text output of the test to a specified Writer. + * + * @author Stefan Bodewig + */ + +public class PlainJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private NumberFormat nf = NumberFormat.getInstance(); + /** + * Timing helper. + */ + private Hashtable testStarts = new Hashtable(); + /** + * Suppress endTest if testcase failed. + */ + private Hashtable failed = new Hashtable(); + + private String systemOutput = null; + private String systemError = null; + /** + * Helper to store intermediate output. + */ + private StringWriter inner; + /** + * Where to write the log to. + */ + private OutputStream out; + /** + * Convenience layer on top of {@link #inner inner}. + */ + private PrintWriter wri; + + public PlainJUnitResultFormatter() + { + inner = new StringWriter(); + wri = new PrintWriter( inner ); + } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * Interface TestListener.

              + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + formatError( "\tCaused an ERROR", test, t ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( "\tFAILED", test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Interface TestListener.

              + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) + { + synchronized( wri ) + { + wri.print( "Testcase: " + + JUnitVersionHelper.getTestCaseName( test ) ); + if( Boolean.TRUE.equals( failed.get( test ) ) ) + { + return; + } + Long l = ( Long )testStarts.get( test ); + wri.println( " took " + + nf.format( ( System.currentTimeMillis() - l.longValue() ) + / 1000.0 ) + + " sec" ); + } + } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Testsuite: " ); + sb.append( suite.getName() ); + sb.append( newLine ); + sb.append( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( nf.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + + // append the err and output streams to the log + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "------------- Standard Output ---------------" ) + .append( newLine ) + .append( systemOutput ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "------------- Standard Error -----------------" ) + .append( newLine ) + .append( systemError ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + sb.append( newLine ); + + if( out != null ) + { + try + { + out.write( sb.toString().getBytes() ); + wri.close(); + out.write( inner.toString().getBytes() ); + out.flush(); + } + catch( IOException ioex ) + { + throw new BuildException( "Unable to write output", ioex ); + } + finally + { + if( out != System.out && out != System.err ) + { + try + { + out.close(); + } + catch( IOException e ) + {} + } + } + } + } + + /** + * Interface TestListener.

              + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) + { + testStarts.put( t, new Long( System.currentTimeMillis() ) ); + failed.put( t, Boolean.FALSE ); + } + + /** + * Empty. + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) { } + + private void formatError( String type, Test test, Throwable t ) + { + synchronized( wri ) + { + if( test != null ) + { + endTest( test ); + failed.put( test, Boolean.TRUE ); + } + + wri.println( type ); + wri.println( t.getMessage() ); + String strace = JUnitTestRunner.getFilteredTrace( t ); + wri.print( strace ); + wri.println( "" ); + } + } + +}// PlainJUnitResultFormatter diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java new file mode 100644 index 000000000..2932da2d0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.text.NumberFormat; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import org.apache.tools.ant.BuildException; + +/** + * Prints short summary output of the test to Ant's logging system. + * + * @author Stefan Bodewig + */ + +public class SummaryJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private NumberFormat nf = NumberFormat.getInstance(); + + private boolean withOutAndErr = false; + private String systemOutput = null; + private String systemError = null; + /** + * OutputStream to write to. + */ + private OutputStream out; + + /** + * Empty + */ + public SummaryJUnitResultFormatter() { } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * Should the output to System.out and System.err be written to the summary. + * + * @param value The new WithOutAndErr value + */ + public void setWithOutAndErr( boolean value ) + { + withOutAndErr = value; + } + + /** + * Empty + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) { } + + /** + * Empty + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) { } + + /** + * Interface TestListener for JUnit > 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Empty + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( nf.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + + if( withOutAndErr ) + { + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "Output:" ).append( newLine ).append( systemOutput ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "Error: " ).append( newLine ).append( systemError ) + .append( newLine ); + } + } + + try + { + out.write( sb.toString().getBytes() ); + out.flush(); + } + catch( IOException ioex ) + { + throw new BuildException( "Unable to write summary output", ioex ); + } + finally + { + if( out != System.out && out != System.err ) + { + try + { + out.close(); + } + catch( IOException e ) + {} + } + } + } + + /** + * Empty + * + * @param t Description of Parameter + */ + public void startTest( Test t ) { } + + /** + * Empty + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) { } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java new file mode 100644 index 000000000..53a2d1bf5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; + +/** + *

              + * + * Interface groups XML constants. Interface that groups all constants used + * throughout the XML documents that are generated by the + * XMLJUnitResultFormatter As of now the DTD is:

              + * <-----------------
              + *
              + * @author Stephane Bailliez
              + * @see XMLJUnitResultFormatter
              + * @see XMLResultAggregator
              + * @todo describe DTDs ----------------------> 
              + */ +public interface XMLConstants +{ + /** + * the testsuites element for the aggregate document + */ + String TESTSUITES = "testsuites"; + + /** + * the testsuite element + */ + String TESTSUITE = "testsuite"; + + /** + * the testcase element + */ + String TESTCASE = "testcase"; + + /** + * the error element + */ + String ERROR = "error"; + + /** + * the failure element + */ + String FAILURE = "failure"; + + /** + * the system-err element + */ + String SYSTEM_ERR = "system-err"; + + /** + * the system-out element + */ + String SYSTEM_OUT = "system-out"; + + /** + * package attribute for the aggregate document + */ + String ATTR_PACKAGE = "package"; + + /** + * name attribute for property, testcase and testsuite elements + */ + String ATTR_NAME = "name"; + + /** + * time attribute for testcase and testsuite elements + */ + String ATTR_TIME = "time"; + + /** + * errors attribute for testsuite elements + */ + String ATTR_ERRORS = "errors"; + + /** + * failures attribute for testsuite elements + */ + String ATTR_FAILURES = "failures"; + + /** + * tests attribute for testsuite elements + */ + String ATTR_TESTS = "tests"; + + /** + * type attribute for failure and error elements + */ + String ATTR_TYPE = "type"; + + /** + * message attribute for failure elements + */ + String ATTR_MESSAGE = "message"; + + /** + * the properties element + */ + String PROPERTIES = "properties"; + + /** + * the property element + */ + String PROPERTY = "property"; + + /** + * value attribute for property elements + */ + String ATTR_VALUE = "value"; + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java new file mode 100644 index 000000000..97501dc2c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.DOMElementWriter; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; + +/** + * Prints XML output of the test to a specified Writer. + * + * @author Stefan Bodewig + * @author Erik Hatcher + * @see FormatterElement + */ + +public class XMLJUnitResultFormatter implements JUnitResultFormatter, XMLConstants +{ + /** + * Element for the current test. + */ + private Hashtable testElements = new Hashtable(); + /** + * Timing helper. + */ + private Hashtable testStarts = new Hashtable(); + + /** + * The XML document. + */ + private Document doc; + /** + * Where to write the log to. + */ + private OutputStream out; + /** + * The wrapper for the whole testsuite. + */ + private Element rootElement; + + public XMLJUnitResultFormatter() { } + + private static DocumentBuilder getDocumentBuilder() + { + try + { + return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } + catch( Exception exc ) + { + throw new ExceptionInInitializerError( exc ); + } + } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String out ) + { + formatOutput( SYSTEM_ERR, out ); + } + + public void setSystemOutput( String out ) + { + formatOutput( SYSTEM_OUT, out ); + } + + /** + * Interface TestListener.

              + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + formatError( ERROR, test, t ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( FAILURE, test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Interface TestListener.

              + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) + { + Element currentTest = ( Element )testElements.get( test ); + Long l = ( Long )testStarts.get( test ); + currentTest.setAttribute( ATTR_TIME, + "" + ( ( System.currentTimeMillis() - l.longValue() ) + / 1000.0 ) ); + } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + rootElement.setAttribute( ATTR_TESTS, "" + suite.runCount() ); + rootElement.setAttribute( ATTR_FAILURES, "" + suite.failureCount() ); + rootElement.setAttribute( ATTR_ERRORS, "" + suite.errorCount() ); + rootElement.setAttribute( ATTR_TIME, "" + ( suite.getRunTime() / 1000.0 ) ); + if( out != null ) + { + Writer wri = null; + try + { + wri = new OutputStreamWriter( out, "UTF8" ); + wri.write( "\n" ); + ( new DOMElementWriter() ).write( rootElement, wri, 0, " " ); + wri.flush(); + } + catch( IOException exc ) + { + throw new BuildException( "Unable to write log file", exc ); + } + finally + { + if( out != System.out && out != System.err ) + { + if( wri != null ) + { + try + { + wri.close(); + } + catch( IOException e ) + {} + } + } + } + } + } + + /** + * Interface TestListener.

              + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) + { + testStarts.put( t, new Long( System.currentTimeMillis() ) ); + + Element currentTest = doc.createElement( TESTCASE ); + currentTest.setAttribute( ATTR_NAME, + JUnitVersionHelper.getTestCaseName( t ) ); + rootElement.appendChild( currentTest ); + testElements.put( t, currentTest ); + } + + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) + { + doc = getDocumentBuilder().newDocument(); + rootElement = doc.createElement( TESTSUITE ); + rootElement.setAttribute( ATTR_NAME, suite.getName() ); + + // Output properties + Element propsElement = doc.createElement( PROPERTIES ); + rootElement.appendChild( propsElement ); + Properties props = suite.getProperties(); + if( props != null ) + { + Enumeration e = props.propertyNames(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + Element propElement = doc.createElement( PROPERTY ); + propElement.setAttribute( ATTR_NAME, name ); + propElement.setAttribute( ATTR_VALUE, props.getProperty( name ) ); + propsElement.appendChild( propElement ); + } + } + } + + private void formatError( String type, Test test, Throwable t ) + { + if( test != null ) + { + endTest( test ); + } + + Element nested = doc.createElement( type ); + Element currentTest = null; + if( test != null ) + { + currentTest = ( Element )testElements.get( test ); + } + else + { + currentTest = rootElement; + } + + currentTest.appendChild( nested ); + + String message = t.getMessage(); + if( message != null && message.length() > 0 ) + { + nested.setAttribute( ATTR_MESSAGE, t.getMessage() ); + } + nested.setAttribute( ATTR_TYPE, t.getClass().getName() ); + + String strace = JUnitTestRunner.getFilteredTrace( t ); + Text trace = doc.createTextNode( strace ); + nested.appendChild( trace ); + } + + private void formatOutput( String type, String output ) + { + Element nested = doc.createElement( type ); + rootElement.appendChild( nested ); + Text content = doc.createTextNode( output ); + nested.appendChild( content ); + } + +}// XMLJUnitResultFormatter diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java new file mode 100644 index 000000000..cc72e239f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Enumeration; +import java.util.Vector; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.DOMElementWriter; +import org.apache.tools.ant.util.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + + +/** + *

              + * + * This is an helper class that will aggregate all testsuites under a specific + * directory and create a new single document. It is not particulary clean but + * should be helpful while I am thinking about another technique.

              + * + * The main problem is due to the fact that a JVM can be forked for a testcase + * thus making it impossible to aggregate all testcases since the listener is + * (obviously) in the forked JVM. A solution could be to write a TestListener + * that will receive events from the TestRunner via sockets. This is IMHO the + * simplest way to do it to avoid this file hacking thing. + * + * @author Stephane Bailliez + */ +public class XMLResultAggregator extends Task implements XMLConstants +{ + + /** + * the default directory: . . It is resolved from the project + * directory + */ + public final static String DEFAULT_DIR = "."; + + /** + * the default file name: TESTS-TestSuites.xml + */ + public final static String DEFAULT_FILENAME = "TESTS-TestSuites.xml"; + + /** + * the list of all filesets, that should contains the xml to aggregate + */ + protected Vector filesets = new Vector(); + + protected Vector transformers = new Vector(); + + /** + * the directory to write the file to + */ + protected File toDir; + + /** + * the name of the result file + */ + protected String toFile; + + /** + * Create a new document builder. Will issue an + * ExceptionInitializerError if something is going wrong. It is fatal + * anyway. + * + * @return a new document builder to create a DOM + * @todo factorize this somewhere else. It is duplicated code. + */ + private static DocumentBuilder getDocumentBuilder() + { + try + { + return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } + catch( Exception exc ) + { + throw new ExceptionInInitializerError( exc ); + } + } + + /** + * Set the destination directory where the results should be written. If not + * set if will use {@link #DEFAULT_DIR}. When given a relative directory it + * will resolve it from the project directory. + * + * @param value the directory where to write the results, absolute or + * relative. + */ + public void setTodir( File value ) + { + toDir = value; + } + + /** + * Set the name of the file aggregating the results. It must be relative + * from the todir attribute. If not set it will use {@link + * #DEFAULT_FILENAME} + * + * @param value the name of the file. + * @see #setTodir(File) + */ + public void setTofile( String value ) + { + toFile = value; + } + + /** + * Add a new fileset containing the xml results to aggregate + * + * @param fs the new fileset of xml results. + */ + public void addFileSet( FileSet fs ) + { + filesets.addElement( fs ); + } + + + public AggregateTransformer createReport() + { + AggregateTransformer transformer = new AggregateTransformer( this ); + transformers.addElement( transformer ); + return transformer; + } + + /** + * Aggregate all testsuites into a single document and write it to the + * specified directory and file. + * + * @throws BuildException thrown if there is a serious error while writing + * the document. + */ + public void execute() + throws BuildException + { + Element rootElement = createDocument(); + File destFile = getDestinationFile(); + // write the document + try + { + writeDOMTree( rootElement.getOwnerDocument(), destFile ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to write test aggregate to '" + destFile + "'", e ); + } + // apply transformation + Enumeration enum = transformers.elements(); + while( enum.hasMoreElements() ) + { + AggregateTransformer transformer = + ( AggregateTransformer )enum.nextElement(); + transformer.setXmlDocument( rootElement.getOwnerDocument() ); + transformer.transform(); + } + } + + /** + * Get the full destination file where to write the result. It is made of + * the todir and tofile attributes. + * + * @return the destination file where should be written the result file. + */ + protected File getDestinationFile() + { + if( toFile == null ) + { + toFile = DEFAULT_FILENAME; + } + if( toDir == null ) + { + toDir = project.resolveFile( DEFAULT_DIR ); + } + return new File( toDir, toFile ); + } + + /** + * Get all .xml files in the fileset. + * + * @return all files in the fileset that end with a '.xml'. + */ + protected File[] getFiles() + { + Vector v = new Vector(); + final int size = filesets.size(); + for( int i = 0; i < size; i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + for( int j = 0; j < f.length; j++ ) + { + String pathname = f[j]; + if( pathname.endsWith( ".xml" ) ) + { + File file = new File( ds.getBasedir(), pathname ); + file = project.resolveFile( file.getPath() ); + v.addElement( file ); + } + } + } + + File[] files = new File[v.size()]; + v.copyInto( files ); + return files; + } + + /** + *

              + * + * Add a new testsuite node to the document. The main difference is that it + * split the previous fully qualified name into a package and a name.

              + * + * For example: org.apache.Whatever will be split into + * org.apache and Whatever . + * + * @param root the root element to which the testsuite node should + * be appended. + * @param testsuite the element to append to the given root. It will + * slightly modify the original node to change the name attribute and + * add a package one. + */ + protected void addTestSuite( Element root, Element testsuite ) + { + String fullclassname = testsuite.getAttribute( ATTR_NAME ); + int pos = fullclassname.lastIndexOf( '.' ); + + // a missing . might imply no package at all. Don't get fooled. + String pkgName = ( pos == -1 ) ? "" : fullclassname.substring( 0, pos ); + String classname = ( pos == -1 ) ? fullclassname : fullclassname.substring( pos + 1 ); + Element copy = ( Element )DOMUtil.importNode( root, testsuite ); + + // modify the name attribute and set the package + copy.setAttribute( ATTR_NAME, classname ); + copy.setAttribute( ATTR_PACKAGE, pkgName ); + } + + /** + *

              + * + * Create a DOM tree. Has 'testsuites' as firstchild and aggregates all + * testsuite results that exists in the base directory. + * + * @return the root element of DOM tree that aggregates all testsuites. + */ + protected Element createDocument() + { + // create the dom tree + DocumentBuilder builder = getDocumentBuilder(); + Document doc = builder.newDocument(); + Element rootElement = doc.createElement( TESTSUITES ); + doc.appendChild( rootElement ); + + // get all files and add them to the document + File[] files = getFiles(); + for( int i = 0; i < files.length; i++ ) + { + try + { + log( "Parsing file: '" + files[i] + "'", Project.MSG_VERBOSE ); + //XXX there seems to be a bug in xerces 1.3.0 that doesn't like file object + // will investigate later. It does not use the given directory but + // the vm dir instead ? Works fine with crimson. + Document testsuiteDoc = builder.parse( "file:///" + files[i].getAbsolutePath() ); + Element elem = testsuiteDoc.getDocumentElement(); + // make sure that this is REALLY a testsuite. + if( TESTSUITE.equals( elem.getNodeName() ) ) + { + addTestSuite( rootElement, elem ); + } + else + { + // issue a warning. + log( "the file " + files[i] + " is not a valid testsuite XML document", Project.MSG_WARN ); + } + } + catch( SAXException e ) + { + // a testcase might have failed and write a zero-length document, + // It has already failed, but hey.... mm. just put a warning + log( "The file " + files[i] + " is not a valid XML document. It is possibly corrupted.", Project.MSG_WARN ); + log( StringUtils.getStackTrace( e ), Project.MSG_DEBUG ); + } + catch( IOException e ) + { + log( "Error while accessing file " + files[i] + ": " + e.getMessage(), Project.MSG_ERR ); + } + } + return rootElement; + } + + //----- from now, the methods are all related to DOM tree manipulation + + /** + * Write the DOM tree to a file. + * + * @param doc the XML document to dump to disk. + * @param file the filename to write the document to. Should obviouslly be a + * .xml file. + * @throws IOException thrown if there is an error while writing the + * content. + */ + protected void writeDOMTree( Document doc, File file ) + throws IOException + { + OutputStream out = new FileOutputStream( file ); + PrintWriter wri = new PrintWriter( new OutputStreamWriter( out, "UTF8" ) ); + wri.write( "\n" ); + ( new DOMElementWriter() ).write( doc.getDocumentElement(), wri, 0, " " ); + wri.flush(); + wri.close(); + // writers do not throw exceptions, so check for them. + if( wri.checkError() ) + { + throw new IOException( "Error while writing DOM content" ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java new file mode 100644 index 000000000..5f426d55e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import org.apache.xalan.xslt.XSLTInputSource; +import org.apache.xalan.xslt.XSLTProcessor; +import org.apache.xalan.xslt.XSLTProcessorFactory; +import org.apache.xalan.xslt.XSLTResultTarget; + +/** + * Xalan 1 executor. It will need a lot of things in the classpath: xerces for + * the serialization, xalan and bsf for the extension. + * + * @author RT + * @todo do everything via reflection to avoid compile problems ? + */ +public class Xalan1Executor extends XalanExecutor +{ + void execute() + throws Exception + { + XSLTProcessor processor = XSLTProcessorFactory.getProcessor(); + // need to quote otherwise it breaks because of "extra illegal tokens" + processor.setStylesheetParam( "output.dir", "'" + caller.toDir.getAbsolutePath() + "'" ); + XSLTInputSource xml_src = new XSLTInputSource( caller.document ); + String system_id = caller.getStylesheetSystemId(); + XSLTInputSource xsl_src = new XSLTInputSource( system_id ); + OutputStream os = getOutputStream(); + XSLTResultTarget target = new XSLTResultTarget( os ); + processor.process( xml_src, xsl_src, target ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java new file mode 100644 index 000000000..617424290 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * Xalan executor via JAXP. Nothing special must exists in the classpath besides + * of course, a parser, jaxp and xalan. + * + * @author RT + */ +public class Xalan2Executor extends XalanExecutor +{ + void execute() + throws Exception + { + TransformerFactory tfactory = TransformerFactory.newInstance(); + String system_id = caller.getStylesheetSystemId(); + Source xsl_src = new StreamSource( system_id ); + Transformer tformer = tfactory.newTransformer( xsl_src ); + Source xml_src = new DOMSource( caller.document ); + OutputStream os = getOutputStream(); + tformer.setParameter( "output.dir", caller.toDir.getAbsolutePath() ); + Result result = new StreamResult( os ); + tformer.transform( xml_src, result ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java new file mode 100644 index 000000000..0b95c3426 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import org.apache.tools.ant.BuildException; + +/** + * Command class that encapsulate specific behavior for each Xalan version. The + * right executor will be instantiated at runtime via class lookup. For + * instance, it will check first for Xalan2, then for Xalan1. + * + * @author RT + */ +abstract class XalanExecutor +{ + /** + * the transformer caller + */ + protected AggregateTransformer caller; + + /** + * Create a valid Xalan executor. It checks first if Xalan2 is present, if + * not it checks for xalan1. If none is available, it fails. + * + * @param caller object containing the transformation information. + * @return Description of the Returned Value + * @throws BuildException thrown if it could not find a valid xalan + * executor. + */ + static XalanExecutor newInstance( AggregateTransformer caller ) + throws BuildException + { + Class procVersion = null; + XalanExecutor executor = null; + try + { + procVersion = Class.forName( "org.apache.xalan.processor.XSLProcessorVersion" ); + executor = new Xalan2Executor(); + } + catch( Exception xalan2missing ) + { + try + { + procVersion = Class.forName( "org.apache.xalan.xslt.XSLProcessorVersion" ); + executor = ( XalanExecutor )Class.forName( + "org.apache.tools.ant.taskdefs.optional.junit.Xalan1Executor" ).newInstance(); + } + catch( Exception xalan1missing ) + { + throw new BuildException( "Could not find xalan2 nor xalan1 in the classpath. Check http://xml.apache.org/xalan-j" ); + } + } + String version = getXalanVersion( procVersion ); + caller.task.log( "Using Xalan version: " + version ); + executor.setCaller( caller ); + return executor; + } + + /** + * pretty useful data (Xalan version information) to display. + * + * @param procVersion Description of Parameter + * @return The XalanVersion value + */ + private static String getXalanVersion( Class procVersion ) + { + try + { + Field f = procVersion.getField( "S_VERSION" ); + return f.get( null ).toString(); + } + catch( Exception e ) + { + return "?"; + } + } + + /** + * get the appropriate stream based on the format (frames/noframes) + * + * @return The OutputStream value + * @exception IOException Description of Exception + */ + protected OutputStream getOutputStream() + throws IOException + { + if( caller.FRAMES.equals( caller.format ) ) + { + // dummy output for the framed report + // it's all done by extension... + return new ByteArrayOutputStream(); + } + else + { + return new FileOutputStream( new File( caller.toDir, "junit-noframes.html" ) ); + } + } + + /** + * override to perform transformation + * + * @exception Exception Description of Exception + */ + abstract void execute() + throws Exception; + + /** + * set the caller for this object. + * + * @param caller The new Caller value + */ + private final void setCaller( AggregateTransformer caller ) + { + this.caller = caller; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java new file mode 100644 index 000000000..500cddfd7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.metamata; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; + +/** + * Somewhat abstract framework to be used for other metama 2.0 tasks. This + * should include, audit, metrics, cover and mparse. For more information, visit + * the website at www.metamata.com + * + * @author Stephane Bailliez + */ +public abstract class AbstractMetamataTask extends Task +{ + + //--------------------------- ATTRIBUTES ----------------------------------- + + /** + * The user classpath to be provided. It matches the -classpath of the + * command line. The classpath must includes both the .class and + * the .java files for accurate audit. + */ + protected Path classPath = null; + + /** + * the path to the source file + */ + protected Path sourcePath = null; + + /** + * Metamata home directory. It will be passed as a metamata.home + * property and should normally matches the environment property + * META_HOME set by the Metamata installer. + */ + protected File metamataHome = null; + + /** + * the command line used to run MAudit + */ + protected CommandlineJava cmdl = new CommandlineJava(); + + /** + * the set of files to be audited + */ + protected Vector fileSets = new Vector(); + + /** + * the options file where are stored the command line options + */ + protected File optionsFile = null; + + // this is used to keep track of which files were included. It will + // be set when calling scanFileSets(); + protected Hashtable includedFiles = null; + + public AbstractMetamataTask() { } + + /** + * initialize the task with the classname of the task to run + * + * @param className Description of Parameter + */ + protected AbstractMetamataTask( String className ) + { + cmdl.setVm( "java" ); + cmdl.setClassname( className ); + } + + /** + * convenient method for JDK 1.1. Will copy all elements from src to dest + * + * @param dest The feature to be added to the AllVector attribute + * @param files The feature to be added to the AllVector attribute + */ + protected final static void addAllVector( Vector dest, Enumeration files ) + { + while( files.hasMoreElements() ) + { + dest.addElement( files.nextElement() ); + } + } + + protected final static File createTmpFile() + { + // must be compatible with JDK 1.1 !!!! + final long rand = ( new Random( System.currentTimeMillis() ) ).nextLong(); + File file = new File( "metamata" + rand + ".tmp" ); + return file; + } + + /** + * -mx or -Xmx depending on VM version + * + * @param max The new Maxmemory value + */ + public void setMaxmemory( String max ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + createJvmarg().setValue( "-mx" + max ); + } + else + { + createJvmarg().setValue( "-Xmx" + max ); + } + } + + /** + * the metamata.home property to run all tasks. + * + * @param metamataHome The new Metamatahome value + */ + public void setMetamatahome( final File metamataHome ) + { + this.metamataHome = metamataHome; + } + + + /** + * The java files or directory to be audited + * + * @param fs The feature to be added to the FileSet attribute + */ + public void addFileSet( FileSet fs ) + { + fileSets.addElement( fs ); + } + + /** + * user classpath + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classPath == null ) + { + classPath = new Path( project ); + } + return classPath; + } + + /** + * Creates a nested jvmarg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createJvmarg() + { + return cmdl.createVmArgument(); + } + + /** + * create the source path for this task + * + * @return Description of the Returned Value + */ + public Path createSourcepath() + { + if( sourcePath == null ) + { + sourcePath = new Path( project ); + } + return sourcePath; + } + + /** + * execute the command line + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + setUp(); + ExecuteStreamHandler handler = createStreamHandler(); + execute0( handler ); + } + finally + { + cleanUp(); + } + } + + //--------------------- PRIVATE/PROTECTED METHODS -------------------------- + + /** + * check the options and build the command line + * + * @exception BuildException Description of Exception + */ + protected void setUp() + throws BuildException + { + checkOptions(); + + // set the classpath as the jar file + File jar = getMetamataJar( metamataHome ); + final Path classPath = cmdl.createClasspath( project ); + classPath.createPathElement().setLocation( jar ); + + // set the metamata.home property + final Commandline.Argument vmArgs = cmdl.createVmArgument(); + vmArgs.setValue( "-Dmetamata.home=" + metamataHome.getAbsolutePath() ); + + // retrieve all the files we want to scan + includedFiles = scanFileSets(); + log( includedFiles.size() + " files added for audit", Project.MSG_VERBOSE ); + + // write all the options to a temp file and use it ro run the process + Vector options = getOptions(); + optionsFile = createTmpFile(); + generateOptionsFile( optionsFile, options ); + Commandline.Argument args = cmdl.createArgument(); + args.setLine( "-arguments " + optionsFile.getAbsolutePath() ); + } + + /** + * return the location of the jar file used to run + * + * @param home Description of Parameter + * @return The MetamataJar value + */ + protected final File getMetamataJar( File home ) + { + return new File( new File( home.getAbsolutePath() ), "lib/metamata.jar" ); + } + + + protected Hashtable getFileMapping() + { + return includedFiles; + } + + /** + * return all options of the command line as string elements + * + * @return The Options value + */ + protected abstract Vector getOptions(); + + /** + * validate options set + * + * @exception BuildException Description of Exception + */ + protected void checkOptions() + throws BuildException + { + // do some validation first + if( metamataHome == null || !metamataHome.exists() ) + { + throw new BuildException( "'metamatahome' must point to Metamata home directory." ); + } + metamataHome = project.resolveFile( metamataHome.getPath() ); + File jar = getMetamataJar( metamataHome ); + if( !jar.exists() ) + { + throw new BuildException( jar + " does not exist. Check your metamata installation." ); + } + } + + /** + * clean up all the mess that we did with temporary objects + */ + protected void cleanUp() + { + if( optionsFile != null ) + { + optionsFile.delete(); + optionsFile = null; + } + } + + /** + * create a stream handler that will be used to get the output since + * metamata tools do not report with convenient files such as XML. + * + * @return Description of the Returned Value + */ + protected abstract ExecuteStreamHandler createStreamHandler(); + + + /** + * execute the process with a specific handler + * + * @param handler Description of Parameter + * @exception BuildException Description of Exception + */ + protected void execute0( ExecuteStreamHandler handler ) + throws BuildException + { + final Execute process = new Execute( handler ); + log( cmdl.toString(), Project.MSG_VERBOSE ); + process.setCommandline( cmdl.getCommandline() ); + try + { + if( process.execute() != 0 ) + { + throw new BuildException( "Metamata task failed." ); + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to launch Metamata task: " + e ); + } + } + + + protected void generateOptionsFile( File tofile, Vector options ) + throws BuildException + { + FileWriter fw = null; + try + { + fw = new FileWriter( tofile ); + PrintWriter pw = new PrintWriter( fw ); + final int size = options.size(); + for( int i = 0; i < size; i++ ) + { + pw.println( options.elementAt( i ) ); + } + pw.flush(); + } + catch( IOException e ) + { + throw new BuildException( "Error while writing options file " + tofile, e ); + } + finally + { + if( fw != null ) + { + try + { + fw.close(); + } + catch( IOException ignored ) + {} + } + } + } + + /** + * @return the list of .java files (as their absolute path) that should be + * audited. + */ + protected Hashtable scanFileSets() + { + Hashtable files = new Hashtable(); + for( int i = 0; i < fileSets.size(); i++ ) + { + FileSet fs = ( FileSet )fileSets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + log( i + ") Adding " + f.length + " files from directory " + ds.getBasedir(), Project.MSG_VERBOSE ); + for( int j = 0; j < f.length; j++ ) + { + String pathname = f[j]; + if( pathname.endsWith( ".java" ) ) + { + File file = new File( ds.getBasedir(), pathname ); +// file = project.resolveFile(file.getAbsolutePath()); + String classname = pathname.substring( 0, pathname.length() - ".java".length() ); + classname = classname.replace( File.separatorChar, '.' ); + files.put( file.getAbsolutePath(), classname );// it's a java file, add it. + } + } + } + return files; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java new file mode 100644 index 000000000..80c5415bb --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.metamata; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Path; + +/** + * Metamata Audit evaluates Java code for programming errors, weaknesses, and + * style violation.

              + * + * Metamata Audit exists in three versions: + *

                + *
              • The Lite version evaluates about 15 built-in rules.
              • + *
              • The Pro version evaluates about 50 built-in rules.
              • + *
              • The Enterprise version allows you to add your own customized rules via + * the API.
              • + *
                  For more information, visit the website at www.metamata.com + * + * @author Stephane Bailliez + */ +public class MAudit extends AbstractMetamataTask +{ + + /* + * As of Metamata 2.0, the command line of MAudit is as follows: + * Usage + * maudit + */ +public class DOMElementWriter +{ + + private static String lSep = System.getProperty( "line.separator" ); + private StringBuffer sb = new StringBuffer(); + + /** + * Don't try to be too smart but at least recognize the predefined entities. + */ + protected String[] knownEntities = {"gt", "amp", "lt", "apos", "quot"}; + + /** + * Is the given argument a character or entity reference? + * + * @param ent Description of Parameter + * @return The Reference value + */ + public boolean isReference( String ent ) + { + if( !( ent.charAt( 0 ) == '&' ) || !ent.endsWith( ";" ) ) + { + return false; + } + + if( ent.charAt( 1 ) == '#' ) + { + if( ent.charAt( 2 ) == 'x' ) + { + try + { + Integer.parseInt( ent.substring( 3, ent.length() - 1 ), 16 ); + return true; + } + catch( NumberFormatException nfe ) + { + return false; + } + } + else + { + try + { + Integer.parseInt( ent.substring( 2, ent.length() - 1 ) ); + return true; + } + catch( NumberFormatException nfe ) + { + return false; + } + } + } + + String name = ent.substring( 1, ent.length() - 1 ); + for( int i = 0; i < knownEntities.length; i++ ) + { + if( name.equals( knownEntities[i] ) ) + { + return true; + } + } + return false; + } + + /** + * Escape <, > & ' and " as their entities. + * + * @param value Description of Parameter + * @return Description of the Returned Value + */ + public String encode( String value ) + { + sb.setLength( 0 ); + for( int i = 0; i < value.length(); i++ ) + { + char c = value.charAt( i ); + switch ( c ) + { + case '<': + sb.append( "<" ); + break; + case '>': + sb.append( ">" ); + break; + case '\'': + sb.append( "'" ); + break; + case '\"': + sb.append( """ ); + break; + case '&': + int nextSemi = value.indexOf( ";", i ); + if( nextSemi < 0 + || !isReference( value.substring( i, nextSemi + 1 ) ) ) + { + sb.append( "&" ); + } + else + { + sb.append( '&' ); + } + break; + default: + sb.append( c ); + break; + } + } + return sb.toString(); + } + + /** + * Writes a DOM tree to a stream. + * + * @param element the Root DOM element of the tree + * @param out where to send the output + * @param indent number of + * @param indentWith strings, that should be used to indent the + * corresponding tag. + * @exception IOException Description of Exception + */ + public void write( Element element, Writer out, int indent, + String indentWith ) + throws IOException + { + + // Write indent characters + for( int i = 0; i < indent; i++ ) + { + out.write( indentWith ); + } + + // Write element + out.write( "<" ); + out.write( element.getTagName() ); + + // Write attributes + NamedNodeMap attrs = element.getAttributes(); + for( int i = 0; i < attrs.getLength(); i++ ) + { + Attr attr = ( Attr )attrs.item( i ); + out.write( " " ); + out.write( attr.getName() ); + out.write( "=\"" ); + out.write( encode( attr.getValue() ) ); + out.write( "\"" ); + } + out.write( ">" ); + + // Write child elements and text + boolean hasChildren = false; + NodeList children = element.getChildNodes(); + for( int i = 0; i < children.getLength(); i++ ) + { + Node child = children.item( i ); + + switch ( child.getNodeType() ) + { + + case Node.ELEMENT_NODE: + if( !hasChildren ) + { + out.write( lSep ); + hasChildren = true; + } + write( ( Element )child, out, indent + 1, indentWith ); + break; + case Node.TEXT_NODE: + out.write( encode( child.getNodeValue() ) ); + break; + case Node.CDATA_SECTION_NODE: + out.write( "" ); + break; + case Node.ENTITY_REFERENCE_NODE: + out.write( '&' ); + out.write( child.getNodeName() ); + out.write( ';' ); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + out.write( " 0 ) + { + out.write( ' ' ); + out.write( data ); + } + out.write( "?>" ); + break; + } + } + + // If we had child elements, we need to indent before we close + // the element, otherwise we're on the same line and don't need + // to indent + if( hasChildren ) + { + for( int i = 0; i < indent; i++ ) + { + out.write( indentWith ); + } + } + + // Write element close + out.write( "" ); + out.write( lSep ); + out.flush(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileNameMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileNameMapper.java new file mode 100644 index 000000000..33ae65096 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileNameMapper.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Interface to be used by SourceFileScanner.

                  + * + * Used to find the name of the target file(s) corresponding to a source file. + *

                  + * + * The rule by which the file names are transformed is specified via the setFrom + * and setTo methods. The exact meaning of these is implementation dependent. + *

                  + * + * @author
                  Stefan Bodewig + */ +public interface FileNameMapper +{ + + /** + * Sets the from part of the transformation rule. + * + * @param from The new From value + */ + void setFrom( String from ); + + /** + * Sets the to part of the transformation rule. + * + * @param to The new To value + */ + void setTo( String to ); + + /** + * Returns an array containing the target filename(s) for the given source + * file.

                  + * + * if the given rule doesn't apply to the source file, implementation must + * return null. SourceFileScanner will then omit the source file in + * question.

                  + * + * @param sourceFileName the name of the source file relative to some given + * basedirectory. + * @return Description of the Returned Value + */ + String[] mapFileName( String sourceFileName ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileUtils.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileUtils.java new file mode 100644 index 000000000..a27ab6f63 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileUtils.java @@ -0,0 +1,674 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.text.DecimalFormat; +import java.util.Random; +import java.util.Stack; +import java.util.StringTokenizer; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FilterSetCollection; + +/** + * This class also encapsulates methods which allow Files to be refered to using + * abstract path names which are translated to native system file paths at + * runtime as well as copying files or setting there last modification time. + * + * @author duncan@x180.com + * @author Conor MacNeill + * @author Stefan Bodewig + * @version $Revision$ + */ + +public class FileUtils +{ + private static Random rand = new Random( System.currentTimeMillis() ); + private static Object lockReflection = new Object(); + private static java.lang.reflect.Method setLastModified = null; + + /** + * Empty constructor. + */ + protected FileUtils() { } + + /** + * Factory method. + * + * @return Description of the Returned Value + */ + public static FileUtils newFileUtils() + { + return new FileUtils(); + } + + /** + * Calls File.setLastModified(long time) in a Java 1.1 compatible way. + * + * @param file The new FileLastModified value + * @param time The new FileLastModified value + * @exception BuildException Description of Exception + */ + public void setFileLastModified( File file, long time ) + throws BuildException + { + if( Project.getJavaVersion() == Project.JAVA_1_1 ) + { + return; + } + Long[] times = new Long[1]; + if( time < 0 ) + { + times[0] = new Long( System.currentTimeMillis() ); + } + else + { + times[0] = new Long( time ); + } + + try + { + getSetLastModified().invoke( file, times ); + } + catch( java.lang.reflect.InvocationTargetException ite ) + { + Throwable nested = ite.getTargetException(); + throw new BuildException( "Exception setting the modification time " + + "of " + file, nested ); + } + catch( Throwable other ) + { + throw new BuildException( "Exception setting the modification time " + + "of " + file, other ); + } + } + + /** + * Emulation of File.getParentFile for JDK 1.1 + * + * @param f Description of Parameter + * @return The ParentFile value + * @since 1.10 + */ + public File getParentFile( File f ) + { + if( f != null ) + { + String p = f.getParent(); + if( p != null ) + { + return new File( p ); + } + } + return null; + } + + /** + * Compares the contents of two files. + * + * @param f1 Description of Parameter + * @param f2 Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + * @since 1.9 + */ + public boolean contentEquals( File f1, File f2 ) + throws IOException + { + if( f1.exists() != f2.exists() ) + { + return false; + } + + if( !f1.exists() ) + { + // two not existing files are equal + return true; + } + + if( f1.isDirectory() || f2.isDirectory() ) + { + // don't want to compare directory contents for now + return false; + } + + InputStream in1 = null; + InputStream in2 = null; + try + { + in1 = new BufferedInputStream( new FileInputStream( f1 ) ); + in2 = new BufferedInputStream( new FileInputStream( f2 ) ); + + int expectedByte = in1.read(); + while( expectedByte != -1 ) + { + if( expectedByte != in2.read() ) + { + return false; + } + expectedByte = in1.read(); + } + if( in2.read() != -1 ) + { + return false; + } + return true; + } + finally + { + if( in1 != null ) + { + try + { + in1.close(); + } + catch( IOException e ) + {} + } + if( in2 != null ) + { + try + { + in2.close(); + } + catch( IOException e ) + {} + } + } + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), null, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters, + boolean overwrite ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, + overwrite, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, + overwrite, preserveLastModified ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile ) + throws IOException + { + copyFile( sourceFile, destFile, null, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters ) + throws IOException + { + copyFile( sourceFile, destFile, filters, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters, + boolean overwrite ) + throws IOException + { + copyFile( sourceFile, destFile, filters, overwrite, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + + if( overwrite || !destFile.exists() || + destFile.lastModified() < sourceFile.lastModified() ) + { + + if( destFile.exists() && destFile.isFile() ) + { + destFile.delete(); + } + + // ensure that parent dir of dest file exists! + // not using getParentFile method to stay 1.1 compat + File parent = getParentFile( destFile ); + if( !parent.exists() ) + { + parent.mkdirs(); + } + + if( filters != null && filters.hasFilters() ) + { + BufferedReader in = new BufferedReader( new FileReader( sourceFile ) ); + BufferedWriter out = new BufferedWriter( new FileWriter( destFile ) ); + + int length; + String newline = null; + String line = in.readLine(); + while( line != null ) + { + if( line.length() == 0 ) + { + out.newLine(); + } + else + { + newline = filters.replaceTokens( line ); + out.write( newline ); + out.newLine(); + } + line = in.readLine(); + } + + out.close(); + in.close(); + } + else + { + FileInputStream in = new FileInputStream( sourceFile ); + FileOutputStream out = new FileOutputStream( destFile ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + + in.close(); + out.close(); + } + + if( preserveLastModified ) + { + setFileLastModified( destFile, sourceFile.lastModified() ); + } + } + } + + /** + * Create a temporary file in a given directory.

                  + * + * The file denoted by the returned abstract pathname did not exist before + * this method was invoked, any subsequent invocation of this method will + * yield a different file name.

                  + * + * This method is different to File.createTempFile of JDK 1.2 as it doesn't + * create the file itself and doesn't use platform specific temporary + * directory when the parentDir attribute is null.

                  + * + * @param parentDir Directory to create the temporary file in - current + * working directory will be assumed if this parameter is null. + * @param prefix Description of Parameter + * @param suffix Description of Parameter + * @return Description of the Returned Value + * @since 1.8 + */ + public File createTempFile( String prefix, String suffix, File parentDir ) + { + + File result = null; + String parent = null; + if( parentDir != null ) + { + parent = parentDir.getPath(); + } + DecimalFormat fmt = new DecimalFormat( "#####" ); + synchronized( rand ) + { + do + { + result = new File( parent, + prefix + fmt.format( rand.nextInt() ) + + suffix ); + }while ( result.exists() ); + } + return result; + } + + /** + * "normalize" the given absolute path.

                  + * + * This includes: + *

                    + *
                  • Uppercase the drive letter if there is one.
                  • + *
                  • Remove redundant slashes after the drive spec.
                  • + *
                  • resolve all ./, .\, ../ and ..\ sequences.
                  • + *
                  • DOS style paths that start with a drive letter will have \ as the + * separator.
                  • + *
                  + * + * + * @param path Description of Parameter + * @return Description of the Returned Value + * @throws java.lang.NullPointerException if the file path is equal to null. + */ + public File normalize( String path ) + { + String orig = path; + + path = path.replace( '/', File.separatorChar ) + .replace( '\\', File.separatorChar ); + + // make sure we are dealing with an absolute path + if( !path.startsWith( File.separator ) && + !( path.length() >= 2 && + Character.isLetter( path.charAt( 0 ) ) && + path.charAt( 1 ) == ':' ) + ) + { + String msg = path + " is not an absolute path"; + throw new BuildException( msg ); + } + + boolean dosWithDrive = false; + String root = null; + // Eliminate consecutive slashes after the drive spec + if( path.length() >= 2 && + Character.isLetter( path.charAt( 0 ) ) && + path.charAt( 1 ) == ':' ) + { + + dosWithDrive = true; + + char[] ca = path.replace( '/', '\\' ).toCharArray(); + StringBuffer sb = new StringBuffer(); + sb.append( Character.toUpperCase( ca[0] ) ).append( ':' ); + + for( int i = 2; i < ca.length; i++ ) + { + if( ( ca[i] != '\\' ) || + ( ca[i] == '\\' && ca[i - 1] != '\\' ) + ) + { + sb.append( ca[i] ); + } + } + + path = sb.toString().replace( '\\', File.separatorChar ); + if( path.length() == 2 ) + { + root = path; + path = ""; + } + else + { + root = path.substring( 0, 3 ); + path = path.substring( 3 ); + } + + } + else + { + if( path.length() == 1 ) + { + root = File.separator; + path = ""; + } + else if( path.charAt( 1 ) == File.separatorChar ) + { + // UNC drive + root = File.separator + File.separator; + path = path.substring( 2 ); + } + else + { + root = File.separator; + path = path.substring( 1 ); + } + } + + Stack s = new Stack(); + s.push( root ); + StringTokenizer tok = new StringTokenizer( path, File.separator ); + while( tok.hasMoreTokens() ) + { + String thisToken = tok.nextToken(); + if( ".".equals( thisToken ) ) + { + continue; + } + else if( "..".equals( thisToken ) ) + { + if( s.size() < 2 ) + { + throw new BuildException( "Cannot resolve path " + orig ); + } + else + { + s.pop(); + } + } + else + {// plain component + s.push( thisToken ); + } + } + + StringBuffer sb = new StringBuffer(); + for( int i = 0; i < s.size(); i++ ) + { + if( i > 1 ) + { + // not before the filesystem root and not after it, since root + // already contains one + sb.append( File.separatorChar ); + } + sb.append( s.elementAt( i ) ); + } + + path = sb.toString(); + if( dosWithDrive ) + { + path = path.replace( '/', '\\' ); + } + return new File( path ); + } + + /** + * Interpret the filename as a file relative to the given file - unless the + * filename already represents an absolute filename. + * + * @param file the "reference" file for relative paths. This instance must + * be an absolute file and must not contain "./" or + * "../" sequences (same for \ instead of /). If it is null, + * this call is equivalent to new java.io.File(filename). + * @param filename a file name + * @return an absolute file that doesn't contain "./" or + * "../" sequences and uses the correct separator for the + * current platform. + */ + public File resolveFile( File file, String filename ) + { + filename = filename.replace( '/', File.separatorChar ) + .replace( '\\', File.separatorChar ); + + // deal with absolute files + if( filename.startsWith( File.separator ) || + ( filename.length() >= 2 && + Character.isLetter( filename.charAt( 0 ) ) && + filename.charAt( 1 ) == ':' ) + ) + { + return normalize( filename ); + } + + if( file == null ) + { + return new File( filename ); + } + + File helpFile = new File( file.getAbsolutePath() ); + StringTokenizer tok = new StringTokenizer( filename, File.separator ); + while( tok.hasMoreTokens() ) + { + String part = tok.nextToken(); + if( part.equals( ".." ) ) + { + helpFile = getParentFile( helpFile ); + if( helpFile == null ) + { + String msg = "The file or path you specified (" + + filename + ") is invalid relative to " + + file.getPath(); + throw new BuildException( msg ); + } + } + else if( part.equals( "." ) ) + { + // Do nothing here + } + else + { + helpFile = new File( helpFile, part ); + } + } + + return new File( helpFile.getAbsolutePath() ); + } + + /** + * see whether we have a setLastModified method in File and return it. + * + * @return The SetLastModified value + */ + protected final Method getSetLastModified() + { + if( Project.getJavaVersion() == Project.JAVA_1_1 ) + { + return null; + } + if( setLastModified == null ) + { + synchronized( lockReflection ) + { + if( setLastModified == null ) + { + try + { + setLastModified = + java.io.File.class.getMethod( "setLastModified", + new Class[]{Long.TYPE} ); + } + catch( NoSuchMethodException nse ) + { + throw new BuildException( "File.setlastModified not in JDK > 1.1?", + nse ); + } + } + } + } + return setLastModified; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FlatFileNameMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FlatFileNameMapper.java new file mode 100644 index 000000000..37c6e5bad --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FlatFileNameMapper.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the source file name + * without any leading directory information.

                  + * + * This is the default FileNameMapper for the copy and move tasks if the flatten + * attribute has been set.

                  + * + * @author Stefan Bodewig + */ +public class FlatFileNameMapper implements FileNameMapper +{ + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Ignored. + * + * @param to The new To value + */ + public void setTo( String to ) { } + + /** + * Returns an one-element array containing the source file name without any + * leading directory information. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return new String[]{new java.io.File( sourceFileName ).getName()}; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/GlobPatternMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/GlobPatternMapper.java new file mode 100644 index 000000000..f9cb24277 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/GlobPatternMapper.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that does simple wildcard pattern + * replacements.

                  + * + * This does simple translations like *.foo -> *.bar where the prefix to .foo + * will be left unchanged. It only handles a single * character, use regular + * expressions for more complicated situations.

                  + * + * This is one of the more useful Mappers, it is used by javac for example.

                  + * + * @author Stefan Bodewig + */ +public class GlobPatternMapper implements FileNameMapper +{ + /** + * Part of "from" pattern before the *. + */ + protected String fromPrefix = null; + + /** + * Part of "from" pattern after the *. + */ + protected String fromPostfix = null; + + /** + * Part of "to" pattern before the *. + */ + protected String toPrefix = null; + + /** + * Part of "to" pattern after the *. + */ + protected String toPostfix = null; + + /** + * Length of the postfix ("from" pattern). + */ + protected int postfixLength; + + /** + * Length of the prefix ("from" pattern). + */ + protected int prefixLength; + + /** + * Sets the "from" pattern. Required. + * + * @param from The new From value + */ + public void setFrom( String from ) + { + int index = from.lastIndexOf( "*" ); + if( index == -1 ) + { + fromPrefix = from; + fromPostfix = ""; + } + else + { + fromPrefix = from.substring( 0, index ); + fromPostfix = from.substring( index + 1 ); + } + prefixLength = fromPrefix.length(); + postfixLength = fromPostfix.length(); + } + + /** + * Sets the "to" pattern. Required. + * + * @param to The new To value + */ + public void setTo( String to ) + { + int index = to.lastIndexOf( "*" ); + if( index == -1 ) + { + toPrefix = to; + toPostfix = ""; + } + else + { + toPrefix = to.substring( 0, index ); + toPostfix = to.substring( index + 1 ); + } + } + + /** + * Returns null if the source file name doesn't match the "from" + * pattern, an one-element array containing the translated file otherwise. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + if( fromPrefix == null + || !sourceFileName.startsWith( fromPrefix ) + || !sourceFileName.endsWith( fromPostfix ) ) + { + return null; + } + return new String[]{toPrefix + + extractVariablePart( sourceFileName ) + + toPostfix}; + } + + /** + * Returns the part of the given string that matches the * in the + * "from" pattern. + * + * @param name Description of Parameter + * @return Description of the Returned Value + */ + protected String extractVariablePart( String name ) + { + return name.substring( prefixLength, + name.length() - postfixLength ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/IdentityMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/IdentityMapper.java new file mode 100644 index 000000000..a93007e83 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/IdentityMapper.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the source file name. + *

                  + * + * This is the default FileNameMapper for the copy and move tasks.

                  + * + * @author Stefan Bodewig + */ +public class IdentityMapper implements FileNameMapper +{ + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Ignored. + * + * @param to The new To value + */ + public void setTo( String to ) { } + + /** + * Returns an one-element array containing the source file name. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return new String[]{sourceFileName}; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/MergingMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/MergingMapper.java new file mode 100644 index 000000000..d9c0b866e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/MergingMapper.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the same target file + * name.

                  + * + * This is the default FileNameMapper for the archiving tasks and uptodate.

                  + * + * @author Stefan Bodewig + */ +public class MergingMapper implements FileNameMapper +{ + protected String[] mergedFile = null; + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Sets the name of the merged file. + * + * @param to The new To value + */ + public void setTo( String to ) + { + mergedFile = new String[]{to}; + } + + /** + * Returns an one-element array containing the file name set via setTo. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return mergedFile; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/RegexpPatternMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/RegexpPatternMapper.java new file mode 100644 index 000000000..df29877a4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/RegexpPatternMapper.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.regexp.RegexpMatcher; +import org.apache.tools.ant.util.regexp.RegexpMatcherFactory; + +/** + * Implementation of FileNameMapper that does regular expression replacements. + * + * @author Stefan Bodewig + */ +public class RegexpPatternMapper implements FileNameMapper +{ + protected RegexpMatcher reg = null; + protected char[] to = null; + protected StringBuffer result = new StringBuffer(); + + public RegexpPatternMapper() + throws BuildException + { + reg = ( new RegexpMatcherFactory() ).newRegexpMatcher(); + } + + /** + * Sets the "from" pattern. Required. + * + * @param from The new From value + * @exception BuildException Description of Exception + */ + public void setFrom( String from ) + throws BuildException + { + try + { + reg.setPattern( from ); + } + catch( NoClassDefFoundError e ) + { + // depending on the implementation the actual RE won't + // get instantiated in the constructor. + throw new BuildException( "Cannot load regular expression matcher", + e ); + } + } + + /** + * Sets the "to" pattern. Required. + * + * @param to The new To value + */ + public void setTo( String to ) + { + this.to = to.toCharArray(); + } + + /** + * Returns null if the source file name doesn't match the "from" + * pattern, an one-element array containing the translated file otherwise. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + if( reg == null || to == null + || !reg.matches( sourceFileName ) ) + { + return null; + } + return new String[]{replaceReferences( sourceFileName )}; + } + + /** + * Replace all backreferences in the to pattern with the matched groups of + * the source. + * + * @param source Description of Parameter + * @return Description of the Returned Value + */ + protected String replaceReferences( String source ) + { + Vector v = reg.getGroups( source ); + + result.setLength( 0 ); + for( int i = 0; i < to.length; i++ ) + { + if( to[i] == '\\' ) + { + if( ++i < to.length ) + { + int value = Character.digit( to[i], 10 ); + if( value > -1 ) + { + result.append( ( String )v.elementAt( value ) ); + } + else + { + result.append( to[i] ); + } + } + else + { + // XXX - should throw an exception instead? + result.append( '\\' ); + } + } + else + { + result.append( to[i] ); + } + } + return result.toString(); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/SourceFileScanner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/SourceFileScanner.java new file mode 100644 index 000000000..4106357ac --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/SourceFileScanner.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; + +/** + * Utility class that collects the functionality of the various scanDir methods + * that have been scattered in several tasks before.

                  + * + * The only method returns an array of source files. The array is a subset of + * the files given as a parameter and holds only those that are newer than their + * corresponding target files.

                  + * + * @author Stefan Bodewig + */ +public class SourceFileScanner +{ + + protected Task task; + + private FileUtils fileUtils; + + /** + * @param task The task we should log messages through + */ + public SourceFileScanner( Task task ) + { + this.task = task; + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Restrict the given set of files to those that are newer than their + * corresponding target files. + * + * @param files the original set of files + * @param srcDir all files are relative to this directory + * @param destDir target files live here. if null file names returned by the + * mapper are assumed to be absolute. + * @param mapper knows how to construct a target file names from source file + * names. + * @return Description of the Returned Value + */ + public String[] restrict( String[] files, File srcDir, File destDir, + FileNameMapper mapper ) + { + + long now = ( new java.util.Date() ).getTime(); + StringBuffer targetList = new StringBuffer(); + + /* + * If we're on Windows, we have to munge the time up to 2 secs to + * be able to check file modification times. + * (Windows has a max resolution of two secs for modification times) + * Actually this is a feature of the FAT file system, NTFS does + * not have it, so if we could reliably passively test for an NTFS + * file systems we could turn this off... + */ + if( Os.isFamily( "windows" ) ) + { + now += 2000; + } + + Vector v = new Vector(); + for( int i = 0; i < files.length; i++ ) + { + + String[] targets = mapper.mapFileName( files[i] ); + if( targets == null || targets.length == 0 ) + { + task.log( files[i] + " skipped - don\'t know how to handle it", + Project.MSG_VERBOSE ); + continue; + } + + File src = fileUtils.resolveFile( srcDir, files[i] ); + + if( src.lastModified() > now ) + { + task.log( "Warning: " + files[i] + " modified in the future.", + Project.MSG_WARN ); + } + + boolean added = false; + targetList.setLength( 0 ); + for( int j = 0; !added && j < targets.length; j++ ) + { + File dest = fileUtils.resolveFile( destDir, targets[j] ); + + if( !dest.exists() ) + { + task.log( files[i] + " added as " + dest.getAbsolutePath() + " doesn\'t exist.", + Project.MSG_VERBOSE ); + v.addElement( files[i] ); + added = true; + } + else if( src.lastModified() > dest.lastModified() ) + { + task.log( files[i] + " added as " + dest.getAbsolutePath() + " is outdated.", + Project.MSG_VERBOSE ); + v.addElement( files[i] ); + added = true; + } + else + { + if( targetList.length() > 0 ) + { + targetList.append( ", " ); + } + targetList.append( dest.getAbsolutePath() ); + } + } + + if( !added ) + { + task.log( files[i] + " omitted as " + targetList.toString() + + ( targets.length == 1 ? " is" : " are " ) + + " up to date.", Project.MSG_VERBOSE ); + } + + } + String[] result = new String[v.size()]; + v.copyInto( result ); + return result; + } + + /** + * Convinience layer on top of restrict that returns the source files as + * File objects (containing absolute paths if srcDir is absolute). + * + * @param files Description of Parameter + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param mapper Description of Parameter + * @return Description of the Returned Value + */ + public File[] restrictAsFiles( String[] files, File srcDir, File destDir, + FileNameMapper mapper ) + { + String[] res = restrict( files, srcDir, destDir, mapper ); + File[] result = new File[res.length]; + for( int i = 0; i < res.length; i++ ) + { + result[i] = new File( srcDir, res[i] ); + } + return result; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/StringUtils.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/StringUtils.java new file mode 100644 index 000000000..6881490c0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/StringUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * A set of helper methods related to string manipulation. + * + * @author Stephane Bailliez + */ +public final class StringUtils +{ + + /** + * the line separator for this OS + */ + public final static String LINE_SEP = System.getProperty( "line.separator" ); + + /** + * Convenient method to retrieve the full stacktrace from a given exception. + * + * @param t the exception to get the stacktrace from. + * @return the stacktrace from the given exception. + */ + public static String getStackTrace( Throwable t ) + { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter( sw, true ); + t.printStackTrace( pw ); + pw.flush(); + pw.close(); + return sw.toString(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Dependencies.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Dependencies.java new file mode 100644 index 000000000..b7808ff0c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Dependencies.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.depend; +import java.io.*; +import java.util.*; +import org.apache.bcel.*; +import org.apache.bcel.classfile.*; + + +public class Dependencies implements Visitor +{ + private boolean verbose = false; + private Set dependencies = new HashSet(); + private ConstantPool constantPool; + + private JavaClass javaClass; + + public static void applyFilter( Collection collection, Filter filter ) + { + Iterator i = collection.iterator(); + while( i.hasNext() ) + { + Object next = i.next(); + if( !filter.accept( next ) ) + { + i.remove(); + } + } + } + + public static void main( String[] args ) + { + try + { + Dependencies visitor = new Dependencies(); + + Set set = new TreeSet(); + Set newSet = new HashSet(); + + int o = 0; + String arg = null; + if( "-base".equals( args[0] ) ) + { + arg = args[1]; + if( !arg.endsWith( File.separator ) ) + { + arg = arg + File.separator; + } + o = 2; + } + final String base = arg; + + for( int i = o; i < args.length; i++ ) + { + String fileName = args[i].substring( 0, args[i].length() - ".class".length() ); + if( base != null && fileName.startsWith( base ) ) + fileName = fileName.substring( base.length() ); + newSet.add( fileName ); + } + set.addAll( newSet ); + + do + { + Iterator i = newSet.iterator(); + while( i.hasNext() ) + { + String fileName = i.next() + ".class"; + + if( base != null ) + { + fileName = base + fileName; + } + + JavaClass javaClass = new ClassParser( fileName ).parse(); + javaClass.accept( visitor ); + } + newSet.clear(); + newSet.addAll( visitor.getDependencies() ); + visitor.clearDependencies(); + + applyFilter( newSet, + new Filter() + { + public boolean accept( Object object ) + { + String fileName = object + ".class"; + if( base != null ) + fileName = base + fileName; + return new File( fileName ).exists(); + } + } ); + newSet.removeAll( set ); + set.addAll( newSet ); + }while ( newSet.size() > 0 ); + + Iterator i = set.iterator(); + while( i.hasNext() ) + { + System.out.println( i.next() ); + } + } + catch( Exception e ) + { + System.err.println( e.getMessage() ); + e.printStackTrace( System.err ); + } + } + + public Set getDependencies() + { + return dependencies; + } + + public void clearDependencies() + { + dependencies.clear(); + } + + public void visitCode( Code obj ) { } + + public void visitCodeException( CodeException obj ) { } + + public void visitConstantClass( ConstantClass obj ) + { + if( verbose ) + { + System.out.println( "visit ConstantClass" ); + System.out.println( obj.getConstantValue( constantPool ) ); + } + dependencies.add( "" + obj.getConstantValue( constantPool ) ); + } + + public void visitConstantDouble( ConstantDouble obj ) { } + + public void visitConstantFieldref( ConstantFieldref obj ) { } + + public void visitConstantFloat( ConstantFloat obj ) { } + + public void visitConstantInteger( ConstantInteger obj ) { } + + public void visitConstantInterfaceMethodref( ConstantInterfaceMethodref obj ) { } + + public void visitConstantLong( ConstantLong obj ) { } + + public void visitConstantMethodref( ConstantMethodref obj ) { } + + public void visitConstantNameAndType( ConstantNameAndType obj ) { } + + public void visitConstantPool( ConstantPool obj ) + { + if( verbose ) + System.out.println( "visit ConstantPool" ); + this.constantPool = obj; + + // visit constants + for( int idx = 0; idx < constantPool.getLength(); idx++ ) + { + Constant c = constantPool.getConstant( idx ); + if( c != null ) + { + c.accept( this ); + } + } + } + + public void visitConstantString( ConstantString obj ) { } + + public void visitConstantUtf8( ConstantUtf8 obj ) { } + + public void visitConstantValue( ConstantValue obj ) { } + + public void visitDeprecated( Deprecated obj ) { } + + public void visitExceptionTable( ExceptionTable obj ) { } + + public void visitField( Field obj ) + { + if( verbose ) + { + System.out.println( "visit Field" ); + System.out.println( obj.getSignature() ); + } + addClasses( obj.getSignature() ); + } + + public void visitInnerClass( InnerClass obj ) { } + + public void visitInnerClasses( InnerClasses obj ) { } + + public void visitJavaClass( JavaClass obj ) + { + if( verbose ) + { + System.out.println( "visit JavaClass" ); + } + + this.javaClass = obj; + dependencies.add( javaClass.getClassName().replace( '.', '/' ) ); + + // visit constant pool + javaClass.getConstantPool().accept( this ); + + // visit fields + Field[] fields = obj.getFields(); + for( int i = 0; i < fields.length; i++ ) + { + fields[i].accept( this ); + } + + // visit methods + Method[] methods = obj.getMethods(); + for( int i = 0; i < methods.length; i++ ) + { + methods[i].accept( this ); + } + } + + public void visitLineNumber( LineNumber obj ) { } + + public void visitLineNumberTable( LineNumberTable obj ) { } + + public void visitLocalVariable( LocalVariable obj ) { } + + public void visitLocalVariableTable( LocalVariableTable obj ) { } + + public void visitMethod( Method obj ) + { + if( verbose ) + { + System.out.println( "visit Method" ); + System.out.println( obj.getSignature() ); + } + String signature = obj.getSignature(); + int pos = signature.indexOf( ")" ); + addClasses( signature.substring( 1, pos ) ); + addClasses( signature.substring( pos + 1 ) ); + } + + public void visitSourceFile( SourceFile obj ) { } + + public void visitStackMap( StackMap obj ) { } + + public void visitStackMapEntry( StackMapEntry obj ) { } + + public void visitSynthetic( Synthetic obj ) { } + + public void visitUnknown( Unknown obj ) { } + + void addClass( String string ) + { + int pos = string.indexOf( 'L' ); + if( pos != -1 ) + { + dependencies.add( string.substring( pos + 1 ) ); + } + } + + void addClasses( String string ) + { + StringTokenizer tokens = new StringTokenizer( string, ";" ); + while( tokens.hasMoreTokens() ) + { + addClass( tokens.nextToken() ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Filter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Filter.java new file mode 100644 index 000000000..2cd26d2aa --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Filter.java @@ -0,0 +1,14 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.depend; +import java.util.*; + +public interface Filter +{ + boolean accept( Object object ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java new file mode 100644 index 000000000..1421fb167 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for Jakarta-ORO. + * + * @author Stefan Bodewig + * @author Matthew Inger + */ +public class JakartaOroMatcher implements RegexpMatcher +{ + protected final Perl5Compiler compiler = new Perl5Compiler(); + protected final Perl5Matcher matcher = new Perl5Matcher(); + + private String pattern; + + public JakartaOroMatcher() { } + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String input, int options ) + throws BuildException + { + if( !matches( input, options ) ) + { + return null; + } + Vector v = new Vector(); + MatchResult mr = matcher.getMatch(); + int cnt = mr.groups(); + for( int i = 0; i < cnt; i++ ) + { + v.addElement( mr.group( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return this.pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + Pattern p = getCompiledPattern( options ); + return matcher.contains( input, p ); + } + + /** + * Get a compiled representation of the regexp pattern + * + * @param options Description of Parameter + * @return The CompiledPattern value + * @exception BuildException Description of Exception + */ + protected Pattern getCompiledPattern( int options ) + throws BuildException + { + try + { + // compute the compiler options based on the input options first + Pattern p = compiler.compile( pattern, getCompilerOptions( options ) ); + return p; + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = Perl5Compiler.DEFAULT_MASK; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + { + cOptions |= Perl5Compiler.CASE_INSENSITIVE_MASK; + } + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + { + cOptions |= Perl5Compiler.MULTILINE_MASK; + } + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + { + cOptions |= Perl5Compiler.SINGLELINE_MASK; + } + + return cOptions; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java new file mode 100644 index 000000000..88e8f31f6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.oro.text.regex.Perl5Substitution; +import org.apache.oro.text.regex.Substitution; +import org.apache.oro.text.regex.Util; +import org.apache.tools.ant.BuildException; + + +/** + * Regular expression implementation using the Jakarta Oro package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaOroRegexp extends JakartaOroMatcher implements Regexp +{ + + public JakartaOroRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + // translate \1 to $1 so that the Perl5Substitution will work + StringBuffer subst = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + subst.append( "$" ).append( value ); + } + else + { + subst.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + subst.append( '\\' ); + } + } + else + { + subst.append( c ); + } + } + + // Do the substitution + Substitution s = + new Perl5Substitution( subst.toString(), + Perl5Substitution.INTERPOLATE_ALL ); + return Util.substitute( matcher, + getCompiledPattern( options ), + s, + input, + getSubsOptions( options ) ); + } + + protected int getSubsOptions( int options ) + { + boolean replaceAll = RegexpUtil.hasFlag( options, REPLACE_ALL ); + int subsOptions = 1; + if( replaceAll ) + { + subsOptions = Util.SUBSTITUTE_ALL; + } + return subsOptions; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java new file mode 100644 index 000000000..a51a8ff3a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.regexp.RE; +import org.apache.regexp.RESyntaxException; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for Jakarta-Regexp. + * + * @author Stefan Bodewig + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaRegexpMatcher implements RegexpMatcher +{ + + private String pattern; + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + public Vector getGroups( String input, int options ) + throws BuildException + { + RE reg = getCompiledPattern( options ); + if( !matches( input, reg ) ) + { + return null; + } + Vector v = new Vector(); + int cnt = reg.getParenCount(); + for( int i = 0; i < cnt; i++ ) + { + v.addElement( reg.getParen( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + return matches( input, getCompiledPattern( options ) ); + } + + protected RE getCompiledPattern( int options ) + throws BuildException + { + int cOptions = getCompilerOptions( options ); + try + { + RE reg = new RE( pattern ); + reg.setMatchFlags( cOptions ); + return reg; + } + catch( RESyntaxException e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = RE.MATCH_NORMAL; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + cOptions |= RE.MATCH_CASEINDEPENDENT; + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + cOptions |= RE.MATCH_MULTILINE; + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + cOptions |= RE.MATCH_SINGLELINE; + + return cOptions; + } + + private boolean matches( String input, RE reg ) + { + return reg.match( input ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java new file mode 100644 index 000000000..3f41e50e5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.regexp.RE; +import org.apache.tools.ant.BuildException; + +/** + * Regular expression implementation using the Jakarta Regexp package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaRegexpRegexp extends JakartaRegexpMatcher implements Regexp +{ + + public JakartaRegexpRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + Vector v = getGroups( input, options ); + + // replace \1 with the corresponding group + StringBuffer result = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + result.append( ( String )v.elementAt( value ) ); + } + else + { + result.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + result.append( '\\' ); + } + } + else + { + result.append( c ); + } + } + argument = result.toString(); + + RE reg = getCompiledPattern( options ); + int sOptions = getSubsOptions( options ); + return reg.subst( input, argument, sOptions ); + } + + protected int getSubsOptions( int options ) + { + int subsOptions = RE.REPLACE_FIRSTONLY; + if( RegexpUtil.hasFlag( options, REPLACE_ALL ) ) + subsOptions = RE.REPLACE_ALL; + return subsOptions; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java new file mode 100644 index 000000000..7c486420e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for the built-in regexp matcher of JDK 1.4. + * + * @author Stefan Bodewig + * @author Matthew Inger + * mattinger@mindless.com + */ +public class Jdk14RegexpMatcher implements RegexpMatcher +{ + + private String pattern; + + public Jdk14RegexpMatcher() { } + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String input, int options ) + throws BuildException + { + Pattern p = getCompiledPattern( options ); + Matcher matcher = p.matcher( input ); + if( !matcher.find() ) + { + return null; + } + Vector v = new Vector(); + int cnt = matcher.groupCount(); + for( int i = 0; i <= cnt; i++ ) + { + v.addElement( matcher.group( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + try + { + Pattern p = getCompiledPattern( options ); + return p.matcher( input ).find(); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + protected Pattern getCompiledPattern( int options ) + throws BuildException + { + int cOptions = getCompilerOptions( options ); + try + { + Pattern p = Pattern.compile( this.pattern, cOptions ); + return p; + } + catch( PatternSyntaxException e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = 0; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + cOptions |= Pattern.CASE_INSENSITIVE; + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + cOptions |= Pattern.MULTILINE; + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + cOptions |= Pattern.DOTALL; + + return cOptions; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java new file mode 100644 index 000000000..23998f0e6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.tools.ant.BuildException; + + +/** + * Regular expression implementation using the JDK 1.4 regular expression + * package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class Jdk14RegexpRegexp extends Jdk14RegexpMatcher implements Regexp +{ + + public Jdk14RegexpRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + // translate \1 to $(1) so that the Matcher will work + StringBuffer subst = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + subst.append( "$" ).append( value ); + } + else + { + subst.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + subst.append( '\\' ); + } + } + else + { + subst.append( c ); + } + } + argument = subst.toString(); + + int sOptions = getSubsOptions( options ); + Pattern p = getCompiledPattern( options ); + StringBuffer sb = new StringBuffer(); + + Matcher m = p.matcher( input ); + if( RegexpUtil.hasFlag( sOptions, REPLACE_ALL ) ) + { + sb.append( m.replaceAll( argument ) ); + } + else + { + boolean res = m.find(); + if( res ) + { + m.appendReplacement( sb, argument ); + m.appendTail( sb ); + } + else + { + sb.append( input ); + } + } + + return sb.toString(); + } + + protected int getSubsOptions( int options ) + { + int subsOptions = REPLACE_FIRST; + if( RegexpUtil.hasFlag( options, REPLACE_ALL ) ) + subsOptions = REPLACE_ALL; + return subsOptions; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Regexp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Regexp.java new file mode 100644 index 000000000..2acaeda47 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Regexp.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; + +/** + * Interface which represents a regular expression, and the operations that can + * be performed on it. + * + * @author Matthew Inger + */ +public interface Regexp extends RegexpMatcher +{ + + /** + * Replace only the first occurance of the regular expression + */ + int REPLACE_FIRST = 0x00000001; + + /** + * Replace all occurances of the regular expression + */ + int REPLACE_ALL = 0x00000010; + + /** + * Perform a substitution on the regular expression. + * + * @param input The string to substitute on + * @param argument The string which defines the substitution + * @param options The list of options for the match and replace. See the + * MATCH_ and REPLACE_ constants above. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + String substitute( String input, String argument, int options ) + throws BuildException; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpFactory.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpFactory.java new file mode 100644 index 000000000..559bdf958 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpFactory.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Regular expression factory, which will create Regexp objects. The actual + * implementation class depends on the System or Ant Property: ant.regexp.regexpimpl + * . + * + * @author Matthew Inger + * mattinger@mindless.com + * @version $Revision$ + */ +public class RegexpFactory extends RegexpMatcherFactory +{ + public RegexpFactory() { } + + /** + * Create a new regular expression matcher instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Regexp newRegexp() + throws BuildException + { + return ( Regexp )newRegexp( null ); + } + + /** + * Create a new regular expression matcher instance. + * + * @param p Project whose ant.regexp.regexpimpl property will be used. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Regexp newRegexp( Project p ) + throws BuildException + { + String systemDefault = null; + if( p == null ) + { + systemDefault = System.getProperty( "ant.regexp.regexpimpl" ); + } + else + { + systemDefault = ( String )p.getProperties().get( "ant.regexp.regexpimpl" ); + } + + if( systemDefault != null ) + { + return createRegexpInstance( systemDefault ); + // XXX should we silently catch possible exceptions and try to + // load a different implementation? + } + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp" ); + } + catch( BuildException be ) + {} + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.JakartaOroRegexp" ); + } + catch( BuildException be ) + {} + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.JakartaRegexpRegexp" ); + } + catch( BuildException be ) + {} + + throw new BuildException( "No supported regular expression matcher found" ); + } + + /** + * Wrapper over {@seee RegexpMatcherFactory#createInstance createInstance} + * that ensures that we are dealing with a Regexp implementation. + * + * @param classname Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @since 1.3 + */ + protected Regexp createRegexpInstance( String classname ) + throws BuildException + { + + RegexpMatcher m = createInstance( classname ); + if( m instanceof Regexp ) + { + return ( Regexp )m; + } + else + { + throw new BuildException( classname + " doesn't implement the Regexp interface" ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcher.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcher.java new file mode 100644 index 000000000..26ef487c0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcher.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.tools.ant.BuildException; + +/** + * Interface describing a regular expression matcher. + * + * @author Stefan Bodewig + * @author Matthew Inger + */ +public interface RegexpMatcher +{ + + /** + * Default Mask (case insensitive, neither multiline nor singleline + * specified). + */ + int MATCH_DEFAULT = 0x00000000; + + /** + * Perform a case insenstive match + */ + int MATCH_CASE_INSENSITIVE = 0x00000100; + + /** + * Treat the input as a multiline input + */ + int MATCH_MULTILINE = 0x00001000; + + /** + * Treat the input as singleline input ('.' matches newline) + */ + int MATCH_SINGLELINE = 0x00010000; + + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + * @exception BuildException Description of Exception + */ + void setPattern( String pattern ) + throws BuildException; + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + * @exception BuildException Description of Exception + */ + String getPattern() + throws BuildException; + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean matches( String argument ) + throws BuildException; + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + Vector getGroups( String argument ) + throws BuildException; + + /** + * Does this regular expression match the input, given certain options + * + * @param input The string to check for a match + * @param options The list of options for the match. See the MATCH_ + * constants above. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean matches( String input, int options ) + throws BuildException; + + /** + * Get the match groups from this regular expression. The return type of the + * elements is always String. + * + * @param input The string to check for a match + * @param options The list of options for the match. See the MATCH_ + * constants above. + * @return The Groups value + * @exception BuildException Description of Exception + */ + Vector getGroups( String input, int options ) + throws BuildException; + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java new file mode 100644 index 000000000..ae7b5331a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Simple Factory Class that produces an implementation of RegexpMatcher based + * on the system property ant.regexp.matcherimpl and the classes + * available.

                  + * + * In a more general framework this class would be abstract and have a static + * newInstance method.

                  + * + * @author Stefan Bodewig + */ +public class RegexpMatcherFactory +{ + + public RegexpMatcherFactory() { } + + /** + * Create a new regular expression instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public RegexpMatcher newRegexpMatcher() + throws BuildException + { + return newRegexpMatcher( null ); + } + + /** + * Create a new regular expression instance. + * + * @param p Project whose ant.regexp.regexpimpl property will be used. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public RegexpMatcher newRegexpMatcher( Project p ) + throws BuildException + { + String systemDefault = null; + if( p == null ) + { + systemDefault = System.getProperty( "ant.regexp.regexpimpl" ); + } + else + { + systemDefault = ( String )p.getProperties().get( "ant.regexp.regexpimpl" ); + } + + if( systemDefault != null ) + { + return createInstance( systemDefault ); + // XXX should we silently catch possible exceptions and try to + // load a different implementation? + } + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.Jdk14RegexpMatcher" ); + } + catch( BuildException be ) + {} + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.JakartaOroMatcher" ); + } + catch( BuildException be ) + {} + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.JakartaRegexpMatcher" ); + } + catch( BuildException be ) + {} + + throw new BuildException( "No supported regular expression matcher found" ); + } + + protected RegexpMatcher createInstance( String className ) + throws BuildException + { + try + { + Class implClass = Class.forName( className ); + return ( RegexpMatcher )implClass.newInstance(); + } + catch( Throwable t ) + { + throw new BuildException( t ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpUtil.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpUtil.java new file mode 100644 index 000000000..fb1e3416a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpUtil.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; + +/** + * Regular expression utilities class which handles flag operations + * + * @author Matthew Inger + */ +public class RegexpUtil extends Object +{ + public final static boolean hasFlag( int options, int flag ) + { + return ( ( options & flag ) > 0 ); + } + + public final static int removeFlag( int options, int flag ) + { + return ( options & ( 0xFFFFFFFF - flag ) ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/bzip2/BZip2Constants.java b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/BZip2Constants.java new file mode 100644 index 000000000..fa26ce9fc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/BZip2Constants.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; + +/** + * Base class for both the compress and decompress classes. Holds common arrays, + * and static data. + * + * @author Keiron Liddle + */ +public interface BZip2Constants +{ + + int baseBlockSize = 100000; + int MAX_ALPHA_SIZE = 258; + int MAX_CODE_LEN = 23; + int RUNA = 0; + int RUNB = 1; + int N_GROUPS = 6; + int G_SIZE = 50; + int N_ITERS = 4; + int MAX_SELECTORS = ( 2 + ( 900000 / G_SIZE ) ); + int NUM_OVERSHOOT_BYTES = 20; + + int rNums[] = { + 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, + 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, + 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, + 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, + 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, + 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, + 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, + 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, + 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, + 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, + 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, + 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, + 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, + 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, + 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, + 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, + 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, + 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, + 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, + 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, + 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, + 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, + 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, + 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, + 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, + 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, + 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, + 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, + 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, + 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, + 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, + 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, + 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, + 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, + 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, + 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, + 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, + 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, + 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, + 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, + 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, + 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, + 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, + 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, + 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, + 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, + 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, + 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, + 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, + 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, + 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, + 936, 638 + }; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2InputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2InputStream.java new file mode 100644 index 000000000..d614359db --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2InputStream.java @@ -0,0 +1,939 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; +import java.io.*; + +/** + * An input stream that decompresses from the BZip2 format (without the file + * header chars) to be read as any other stream. + * + * @author Keiron Liddle + */ +public class CBZip2InputStream extends InputStream implements BZip2Constants +{ + + private final static int START_BLOCK_STATE = 1; + private final static int RAND_PART_A_STATE = 2; + private final static int RAND_PART_B_STATE = 3; + private final static int RAND_PART_C_STATE = 4; + private final static int NO_RAND_PART_A_STATE = 5; + private final static int NO_RAND_PART_B_STATE = 6; + private final static int NO_RAND_PART_C_STATE = 7; + private CRC mCrc = new CRC(); + + private boolean inUse[] = new boolean[256]; + + private char seqToUnseq[] = new char[256]; + private char unseqToSeq[] = new char[256]; + + private char selector[] = new char[MAX_SELECTORS]; + private char selectorMtf[] = new char[MAX_SELECTORS]; + + /* + * freq table collected to save a pass over the data + * during decompression. + */ + private int unzftab[] = new int[256]; + + private int limit[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int base[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int perm[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int minLens[] = new int[N_GROUPS]; + + private boolean streamEnd = false; + + private int currentChar = -1; + + private int currentState = START_BLOCK_STATE; + int rNToGo = 0; + int rTPos = 0; + int i, tPos; + + int i2, count, chPrev, ch2; + int j2; + char z; + + private boolean blockRandomised; + + /* + * always: in the range 0 .. 9. + * The current block size is 100000 * this number. + */ + private int blockSize100k; + private int bsBuff; + private int bsLive; + + private InputStream bsStream; + + private int bytesIn; + private int bytesOut; + private int computedBlockCRC, computedCombinedCRC; + + /* + * index of the last char in the block, so + * the block size == last + 1. + */ + private int last; + private char[] ll8; + private int nInUse; + + /* + * index in zptr[] of original string after sorting. + */ + private int origPtr; + + private int storedBlockCRC, storedCombinedCRC; + + private int[] tt; + + public CBZip2InputStream( InputStream zStream ) + { + ll8 = null; + tt = null; + bsSetStream( zStream ); + initialize(); + initBlock(); + setupBlock(); + } + + private static void badBGLengths() + { + cadvise(); + } + + private static void badBlockHeader() + { + cadvise(); + } + + private static void bitStreamEOF() + { + cadvise(); + } + + private static void blockOverrun() + { + cadvise(); + } + + private static void cadvise() + { + System.out.println( "CRC Error" ); + //throw new CCoruptionError(); + } + + private static void compressedStreamEOF() + { + cadvise(); + } + + private static void crcError() + { + cadvise(); + } + + public int read() + { + if( streamEnd ) + { + return -1; + } + else + { + int retChar = currentChar; + switch ( currentState ) + { + case START_BLOCK_STATE: + break; + case RAND_PART_A_STATE: + break; + case RAND_PART_B_STATE: + setupRandPartB(); + break; + case RAND_PART_C_STATE: + setupRandPartC(); + break; + case NO_RAND_PART_A_STATE: + break; + case NO_RAND_PART_B_STATE: + setupNoRandPartB(); + break; + case NO_RAND_PART_C_STATE: + setupNoRandPartC(); + break; + default: + break; + } + return retChar; + } + } + + private void setDecompressStructureSizes( int newSize100k ) + { + if( !( 0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k + && blockSize100k <= 9 ) ) + { + // throw new IOException("Invalid block size"); + } + + blockSize100k = newSize100k; + + if( newSize100k == 0 ) + return; + + int n = baseBlockSize * newSize100k; + ll8 = new char[n]; + tt = new int[n]; + } + + private void setupBlock() + { + int cftab[] = new int[257]; + char ch; + + cftab[0] = 0; + for( i = 1; i <= 256; i++ ) + cftab[i] = unzftab[i - 1]; + for( i = 1; i <= 256; i++ ) + cftab[i] += cftab[i - 1]; + + for( i = 0; i <= last; i++ ) + { + ch = ( char )ll8[i]; + tt[cftab[ch]] = i; + cftab[ch]++; + } + cftab = null; + + tPos = tt[origPtr]; + + count = 0; + i2 = 0; + ch2 = 256; + /* + * not a char and not EOF + */ + if( blockRandomised ) + { + rNToGo = 0; + rTPos = 0; + setupRandPartA(); + } + else + { + setupNoRandPartA(); + } + } + + private void setupNoRandPartA() + { + if( i2 <= last ) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + i2++; + + currentChar = ch2; + currentState = NO_RAND_PART_B_STATE; + mCrc.updateCRC( ch2 ); + } + else + { + endBlock(); + initBlock(); + setupBlock(); + } + } + + private void setupNoRandPartB() + { + if( ch2 != chPrev ) + { + currentState = NO_RAND_PART_A_STATE; + count = 1; + setupNoRandPartA(); + } + else + { + count++; + if( count >= 4 ) + { + z = ll8[tPos]; + tPos = tt[tPos]; + currentState = NO_RAND_PART_C_STATE; + j2 = 0; + setupNoRandPartC(); + } + else + { + currentState = NO_RAND_PART_A_STATE; + setupNoRandPartA(); + } + } + } + + private void setupNoRandPartC() + { + if( j2 < ( int )z ) + { + currentChar = ch2; + mCrc.updateCRC( ch2 ); + j2++; + } + else + { + currentState = NO_RAND_PART_A_STATE; + i2++; + count = 0; + setupNoRandPartA(); + } + } + + private void setupRandPartA() + { + if( i2 <= last ) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + if( rNToGo == 0 ) + { + rNToGo = rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + ch2 ^= ( int )( ( rNToGo == 1 ) ? 1 : 0 ); + i2++; + + currentChar = ch2; + currentState = RAND_PART_B_STATE; + mCrc.updateCRC( ch2 ); + } + else + { + endBlock(); + initBlock(); + setupBlock(); + } + } + + private void setupRandPartB() + { + if( ch2 != chPrev ) + { + currentState = RAND_PART_A_STATE; + count = 1; + setupRandPartA(); + } + else + { + count++; + if( count >= 4 ) + { + z = ll8[tPos]; + tPos = tt[tPos]; + if( rNToGo == 0 ) + { + rNToGo = rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + z ^= ( ( rNToGo == 1 ) ? 1 : 0 ); + j2 = 0; + currentState = RAND_PART_C_STATE; + setupRandPartC(); + } + else + { + currentState = RAND_PART_A_STATE; + setupRandPartA(); + } + } + } + + private void setupRandPartC() + { + if( j2 < ( int )z ) + { + currentChar = ch2; + mCrc.updateCRC( ch2 ); + j2++; + } + else + { + currentState = RAND_PART_A_STATE; + i2++; + count = 0; + setupRandPartA(); + } + } + + private void getAndMoveToFrontDecode() + { + char yy[] = new char[256]; + int i; + int j; + int nextSym; + int limitLast; + int EOB; + int groupNo; + int groupPos; + + limitLast = baseBlockSize * blockSize100k; + origPtr = bsGetIntVS( 24 ); + + recvDecodingTables(); + EOB = nInUse + 1; + groupNo = -1; + groupPos = 0; + + /* + * Setting up the unzftab entries here is not strictly + * necessary, but it does save having to do it later + * in a separate pass, and so saves a block's worth of + * cache misses. + */ + for( i = 0; i <= 255; i++ ) + unzftab[i] = 0; + + for( i = 0; i <= 255; i++ ) + yy[i] = ( char )i; + + last = -1; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + nextSym = perm[zt][zvec - base[zt][zn]]; + } + + while( true ) + { + + if( nextSym == EOB ) + break; + + if( nextSym == RUNA || nextSym == RUNB ) + { + char ch; + int s = -1; + int N = 1; + do + { + if( nextSym == RUNA ) + s = s + ( 0 + 1 ) * N; + else if( nextSym == RUNB ) + s = s + ( 1 + 1 ) * N; + N = N * 2; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + ; + nextSym = perm[zt][zvec - base[zt][zn]]; + } + }while ( nextSym == RUNA || nextSym == RUNB ); + + s++; + ch = seqToUnseq[yy[0]]; + unzftab[ch] += s; + + while( s > 0 ) + { + last++; + ll8[last] = ch; + s--; + } + ; + + if( last >= limitLast ) + blockOverrun(); + continue; + } + else + { + char tmp; + last++; + if( last >= limitLast ) + blockOverrun(); + + tmp = yy[nextSym - 1]; + unzftab[seqToUnseq[tmp]]++; + ll8[last] = seqToUnseq[tmp]; + + /* + * This loop is hammered during decompression, + * hence the unrolling. + * for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1]; + */ + j = nextSym - 1; + for( ; j > 3; j -= 4 ) + { + yy[j] = yy[j - 1]; + yy[j - 1] = yy[j - 2]; + yy[j - 2] = yy[j - 3]; + yy[j - 3] = yy[j - 4]; + } + for( ; j > 0; j-- ) + yy[j] = yy[j - 1]; + + yy[0] = tmp; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + ; + nextSym = perm[zt][zvec - base[zt][zn]]; + } + continue; + } + } + } + + private void bsFinishedWithStream() + { + bsStream = null; + } + + private int bsGetInt32() + { + return ( int )bsGetint(); + } + + private int bsGetIntVS( int numBits ) + { + return ( int )bsR( numBits ); + } + + private char bsGetUChar() + { + return ( char )bsR( 8 ); + } + + private int bsGetint() + { + int u = 0; + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + return u; + } + + private int bsR( int n ) + { + int v; + { + while( bsLive < n ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + + v = ( bsBuff >> ( bsLive - n ) ) & ( ( 1 << n ) - 1 ); + bsLive -= n; + return v; + } + + private void bsSetStream( InputStream f ) + { + bsStream = f; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + bytesIn = 0; + } + + private void complete() + { + storedCombinedCRC = bsGetInt32(); + if( storedCombinedCRC != computedCombinedCRC ) + crcError(); + + bsFinishedWithStream(); + streamEnd = true; + } + + private void endBlock() + { + computedBlockCRC = mCrc.getFinalCRC(); + /* + * A bad CRC is considered a fatal error. + */ + if( storedBlockCRC != computedBlockCRC ) + crcError(); + + computedCombinedCRC = ( computedCombinedCRC << 1 ) + | ( computedCombinedCRC >>> 31 ); + computedCombinedCRC ^= computedBlockCRC; + } + + private void hbCreateDecodeTables( int[] limit, int[] base, + int[] perm, char[] length, + int minLen, int maxLen, int alphaSize ) + { + int pp; + int i; + int j; + int vec; + + pp = 0; + for( i = minLen; i <= maxLen; i++ ) + for( j = 0; j < alphaSize; j++ ) + if( length[j] == i ) + { + perm[pp] = j; + pp++; + } + ; + + for( i = 0; i < MAX_CODE_LEN; i++ ) + base[i] = 0; + for( i = 0; i < alphaSize; i++ ) + base[length[i] + 1]++; + + for( i = 1; i < MAX_CODE_LEN; i++ ) + base[i] += base[i - 1]; + + for( i = 0; i < MAX_CODE_LEN; i++ ) + limit[i] = 0; + vec = 0; + + for( i = minLen; i <= maxLen; i++ ) + { + vec += ( base[i + 1] - base[i] ); + limit[i] = vec - 1; + vec <<= 1; + } + for( i = minLen + 1; i <= maxLen; i++ ) + base[i] = ( ( limit[i - 1] + 1 ) << 1 ) - base[i]; + } + + private void initBlock() + { + char magic1; + char magic2; + char magic3; + char magic4; + char magic5; + char magic6; + magic1 = bsGetUChar(); + magic2 = bsGetUChar(); + magic3 = bsGetUChar(); + magic4 = bsGetUChar(); + magic5 = bsGetUChar(); + magic6 = bsGetUChar(); + if( magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 + && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90 ) + { + complete(); + return; + } + + if( magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 + || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59 ) + { + badBlockHeader(); + streamEnd = true; + return; + } + + storedBlockCRC = bsGetInt32(); + + if( bsR( 1 ) == 1 ) + blockRandomised = true; + else + blockRandomised = false; + + // currBlockNo++; + getAndMoveToFrontDecode(); + + mCrc.initialiseCRC(); + currentState = START_BLOCK_STATE; + } + + private void initialize() + { + char magic3; + char magic4; + magic3 = bsGetUChar(); + magic4 = bsGetUChar(); + if( magic3 != 'h' || magic4 < '1' || magic4 > '9' ) + { + bsFinishedWithStream(); + streamEnd = true; + return; + } + + setDecompressStructureSizes( magic4 - '0' ); + computedCombinedCRC = 0; + } + + private void makeMaps() + { + int i; + nInUse = 0; + for( i = 0; i < 256; i++ ) + if( inUse[i] ) + { + seqToUnseq[nInUse] = ( char )i; + unseqToSeq[i] = ( char )nInUse; + nInUse++; + } + } + + private void recvDecodingTables() + { + char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE]; + int i; + int j; + int t; + int nGroups; + int nSelectors; + int alphaSize; + int minLen; + int maxLen; + boolean inUse16[] = new boolean[16]; + + /* + * Receive the mapping table + */ + for( i = 0; i < 16; i++ ) + if( bsR( 1 ) == 1 ) + inUse16[i] = true; + else + inUse16[i] = false; + + for( i = 0; i < 256; i++ ) + inUse[i] = false; + + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + for( j = 0; j < 16; j++ ) + if( bsR( 1 ) == 1 ) + inUse[i * 16 + j] = true; + + makeMaps(); + alphaSize = nInUse + 2; + + /* + * Now the selectors + */ + nGroups = bsR( 3 ); + nSelectors = bsR( 15 ); + for( i = 0; i < nSelectors; i++ ) + { + j = 0; + while( bsR( 1 ) == 1 ) + j++; + selectorMtf[i] = ( char )j; + } + { + /* + * Undo the MTF values for the selectors. + */ + char pos[] = new char[N_GROUPS]; + char tmp; + char v; + for( v = 0; v < nGroups; v++ ) + pos[v] = v; + + for( i = 0; i < nSelectors; i++ ) + { + v = selectorMtf[i]; + tmp = pos[v]; + while( v > 0 ) + { + pos[v] = pos[v - 1]; + v--; + } + pos[0] = tmp; + selector[i] = tmp; + } + } + + /* + * Now the coding tables + */ + for( t = 0; t < nGroups; t++ ) + { + int curr = bsR( 5 ); + for( i = 0; i < alphaSize; i++ ) + { + while( bsR( 1 ) == 1 ) + { + if( bsR( 1 ) == 0 ) + curr++; + else + curr--; + } + len[t][i] = ( char )curr; + } + } + + /* + * Create the Huffman decoding tables + */ + for( t = 0; t < nGroups; t++ ) + { + minLen = 32; + maxLen = 0; + for( i = 0; i < alphaSize; i++ ) + { + if( len[t][i] > maxLen ) + maxLen = len[t][i]; + if( len[t][i] < minLen ) + minLen = len[t][i]; + } + hbCreateDecodeTables( limit[t], base[t], perm[t], len[t], minLen, + maxLen, alphaSize ); + minLens[t] = minLen; + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2OutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2OutputStream.java new file mode 100644 index 000000000..7ee53781c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2OutputStream.java @@ -0,0 +1,1807 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; +import java.io.*; + +/** + * An output stream that compresses into the BZip2 format (without the file + * header chars) into another stream. + * + * @author Keiron Liddle TODO: Update to + * BZip2 1.0.1 + */ +public class CBZip2OutputStream extends OutputStream implements BZip2Constants +{ + protected final static int SETMASK = ( 1 << 21 ); + protected final static int CLEARMASK = ( ~SETMASK ); + protected final static int GREATER_ICOST = 15; + protected final static int LESSER_ICOST = 0; + protected final static int SMALL_THRESH = 20; + protected final static int DEPTH_THRESH = 10; + + /* + * If you are ever unlucky/improbable enough + * to get a stack overflow whilst sorting, + * increase the following constant and try + * again. In practice I have never seen the + * stack go above 27 elems, so the following + * limit seems very generous. + */ + protected final static int QSORT_STACK_SIZE = 1000; + CRC mCrc = new CRC(); + + private boolean inUse[] = new boolean[256]; + + private char seqToUnseq[] = new char[256]; + private char unseqToSeq[] = new char[256]; + + private char selector[] = new char[MAX_SELECTORS]; + private char selectorMtf[] = new char[MAX_SELECTORS]; + + private int mtfFreq[] = new int[MAX_ALPHA_SIZE]; + + private int currentChar = -1; + private int runLength = 0; + + boolean closed = false; + + /* + * Knuth's increments seem to work better + * than Incerpi-Sedgewick here. Possibly + * because the number of elems to sort is + * usually small, typically <= 20. + */ + private int incs[] = {1, 4, 13, 40, 121, 364, 1093, 3280, + 9841, 29524, 88573, 265720, + 797161, 2391484}; + + boolean blockRandomised; + + /* + * always: in the range 0 .. 9. + * The current block size is 100000 * this number. + */ + int blockSize100k; + int bsBuff; + int bsLive; + + int bytesIn; + int bytesOut; + + /* + * index of the last char in the block, so + * the block size == last + 1. + */ + int last; + + /* + * index in zptr[] of original string after sorting. + */ + int origPtr; + + private int allowableBlockSize; + + private char block[]; + + private int blockCRC, combinedCRC; + + private OutputStream bsStream; + private boolean firstAttempt; + private int ftab[]; + private int nBlocksRandomised; + private int nInUse; + + private int nMTF; + private int quadrant[]; + private short szptr[]; + private int workDone; + + /* + * Used when sorting. If too many long comparisons + * happen, we stop sorting, randomise the block + * slightly, and try again. + */ + private int workFactor; + private int workLimit; + private int zptr[]; + + public CBZip2OutputStream( OutputStream inStream ) + throws IOException + { + this( inStream, 9 ); + } + + public CBZip2OutputStream( OutputStream inStream, int inBlockSize ) + throws IOException + { + block = null; + quadrant = null; + zptr = null; + ftab = null; + + bsSetStream( inStream ); + + workFactor = 50; + if( inBlockSize > 9 ) + { + inBlockSize = 9; + } + if( inBlockSize < 1 ) + { + inBlockSize = 1; + } + blockSize100k = inBlockSize; + allocateCompressStructures(); + initialize(); + initBlock(); + } + + protected static void hbMakeCodeLengths( char[] len, int[] freq, + int alphaSize, int maxLen ) + { + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int nNodes; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int nHeap; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int n1; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int n2; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int i; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int j; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int k; + boolean tooLong; + + int heap[] = new int[MAX_ALPHA_SIZE + 2]; + int weight[] = new int[MAX_ALPHA_SIZE * 2]; + int parent[] = new int[MAX_ALPHA_SIZE * 2]; + + for( i = 0; i < alphaSize; i++ ) + weight[i + 1] = ( freq[i] == 0 ? 1 : freq[i] ) << 8; + + while( true ) + { + nNodes = alphaSize; + nHeap = 0; + + heap[0] = 0; + weight[0] = 0; + parent[0] = -2; + + for( i = 1; i <= alphaSize; i++ ) + { + parent[i] = -1; + nHeap++; + heap[nHeap] = i; + { + int zz; + int tmp; + zz = nHeap; + tmp = heap[zz]; + while( weight[tmp] < weight[heap[zz >> 1]] ) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if( !( nHeap < ( MAX_ALPHA_SIZE + 2 ) ) ) + panic(); + + while( nHeap > 1 ) + { + n1 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0; + int yy = 0; + int tmp = 0; + zz = 1; + tmp = heap[zz]; + while( true ) + { + yy = zz << 1; + if( yy > nHeap ) + break; + if( yy < nHeap && + weight[heap[yy + 1]] < weight[heap[yy]] ) + yy++; + if( weight[tmp] < weight[heap[yy]] ) + break; + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + n2 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0; + int yy = 0; + int tmp = 0; + zz = 1; + tmp = heap[zz]; + while( true ) + { + yy = zz << 1; + if( yy > nHeap ) + break; + if( yy < nHeap && + weight[heap[yy + 1]] < weight[heap[yy]] ) + yy++; + if( weight[tmp] < weight[heap[yy]] ) + break; + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + nNodes++; + parent[n1] = parent[n2] = nNodes; + + weight[nNodes] = ( ( weight[n1] & 0xffffff00 ) + + ( weight[n2] & 0xffffff00 ) ) + | ( 1 + ( ( ( weight[n1] & 0x000000ff ) > + ( weight[n2] & 0x000000ff ) ) ? + ( weight[n1] & 0x000000ff ) : + ( weight[n2] & 0x000000ff ) ) ); + + parent[nNodes] = -1; + nHeap++; + heap[nHeap] = nNodes; + { + int zz = 0; + int tmp = 0; + zz = nHeap; + tmp = heap[zz]; + while( weight[tmp] < weight[heap[zz >> 1]] ) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if( !( nNodes < ( MAX_ALPHA_SIZE * 2 ) ) ) + panic(); + + tooLong = false; + for( i = 1; i <= alphaSize; i++ ) + { + j = 0; + k = i; + while( parent[k] >= 0 ) + { + k = parent[k]; + j++; + } + len[i - 1] = ( char )j; + if( j > maxLen ) + tooLong = true; + } + + if( !tooLong ) + break; + + for( i = 1; i < alphaSize; i++ ) + { + j = weight[i] >> 8; + j = 1 + ( j / 2 ); + weight[i] = j << 8; + } + } + } + + private static void panic() + { + System.out.println( "panic" ); + //throw new CError(); + } + + public void close() + throws IOException + { + if( closed ) + return; + + if( runLength > 0 ) + writeRun(); + currentChar = -1; + endBlock(); + endCompression(); + closed = true; + super.close(); + bsStream.close(); + } + + public void finalize() + throws Throwable + { + close(); + } + + public void flush() + throws IOException + { + super.flush(); + bsStream.flush(); + } + + /** + * modified by Oliver Merkel, 010128 + * + * @param bv Description of Parameter + * @exception IOException Description of Exception + */ + public void write( int bv ) + throws IOException + { + int b = ( 256 + bv ) % 256; + if( currentChar != -1 ) + { + if( currentChar == b ) + { + runLength++; + if( runLength > 254 ) + { + writeRun(); + currentChar = -1; + runLength = 0; + } + } + else + { + writeRun(); + runLength = 1; + currentChar = b; + } + } + else + { + currentChar = b; + runLength++; + } + } + + private void allocateCompressStructures() + { + int n = baseBlockSize * blockSize100k; + block = new char[( n + 1 + NUM_OVERSHOOT_BYTES )]; + quadrant = new int[( n + NUM_OVERSHOOT_BYTES )]; + zptr = new int[n]; + ftab = new int[65537]; + + if( block == null || quadrant == null || zptr == null + || ftab == null ) + { + //int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; + //compressOutOfMemory ( totalDraw, n ); + } + + /* + * The back end needs a place to store the MTF values + * whilst it calculates the coding tables. We could + * put them in the zptr array. However, these values + * will fit in a short, so we overlay szptr at the + * start of zptr, in the hope of reducing the number + * of cache misses induced by the multiple traversals + * of the MTF values when calculating coding tables. + * Seems to improve compression speed by about 1%. + */ + // szptr = zptr; + + szptr = new short[2 * n]; + } + + private void bsFinishedWithStream() + throws IOException + { + while( bsLive > 0 ) + { + int ch = ( bsBuff >> 24 ); + try + { + bsStream.write( ch );// write 8-bit + } + catch( IOException e ) + { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + } + + private void bsPutIntVS( int numBits, int c ) + throws IOException + { + bsW( numBits, c ); + } + + private void bsPutUChar( int c ) + throws IOException + { + bsW( 8, c ); + } + + private void bsPutint( int u ) + throws IOException + { + bsW( 8, ( u >> 24 ) & 0xff ); + bsW( 8, ( u >> 16 ) & 0xff ); + bsW( 8, ( u >> 8 ) & 0xff ); + bsW( 8, u & 0xff ); + } + + private void bsSetStream( OutputStream f ) + { + bsStream = f; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + bytesIn = 0; + } + + private void bsW( int n, int v ) + throws IOException + { + while( bsLive >= 8 ) + { + int ch = ( bsBuff >> 24 ); + try + { + bsStream.write( ch );// write 8-bit + } + catch( IOException e ) + { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + bsBuff |= ( v << ( 32 - bsLive - n ) ); + bsLive += n; + } + + private void doReversibleTransformation() + { + int i; + + workLimit = workFactor * last; + workDone = 0; + blockRandomised = false; + firstAttempt = true; + + mainSort(); + + if( workDone > workLimit && firstAttempt ) + { + randomiseBlock(); + workLimit = workDone = 0; + blockRandomised = true; + firstAttempt = false; + mainSort(); + } + + origPtr = -1; + for( i = 0; i <= last; i++ ) + if( zptr[i] == 0 ) + { + origPtr = i; + break; + } + ; + + if( origPtr == -1 ) + panic(); + } + + private void endBlock() + throws IOException + { + blockCRC = mCrc.getFinalCRC(); + combinedCRC = ( combinedCRC << 1 ) | ( combinedCRC >>> 31 ); + combinedCRC ^= blockCRC; + + /* + * sort the block and establish posn of original string + */ + doReversibleTransformation(); + + /* + * A 6-byte block header, the value chosen arbitrarily + * as 0x314159265359 :-). A 32 bit value does not really + * give a strong enough guarantee that the value will not + * appear by chance in the compressed datastream. Worst-case + * probability of this event, for a 900k block, is about + * 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. + * For a compressed file of size 100Gb -- about 100000 blocks -- + * only a 48-bit marker will do. NB: normal compression/ + * decompression do *not* rely on these statistical properties. + * They are only important when trying to recover blocks from + * damaged files. + */ + bsPutUChar( 0x31 ); + bsPutUChar( 0x41 ); + bsPutUChar( 0x59 ); + bsPutUChar( 0x26 ); + bsPutUChar( 0x53 ); + bsPutUChar( 0x59 ); + + /* + * Now the block's CRC, so it is in a known place. + */ + bsPutint( blockCRC ); + + /* + * Now a single bit indicating randomisation. + */ + if( blockRandomised ) + { + bsW( 1, 1 ); + nBlocksRandomised++; + } + else + { + bsW( 1, 0 ); + } + + /* + * Finally, block's contents proper. + */ + moveToFrontCodeAndSend(); + } + + private void endCompression() + throws IOException + { + /* + * Now another magic 48-bit number, 0x177245385090, to + * indicate the end of the last block. (sqrt(pi), if + * you want to know. I did want to use e, but it contains + * too much repetition -- 27 18 28 18 28 46 -- for me + * to feel statistically comfortable. Call me paranoid.) + */ + bsPutUChar( 0x17 ); + bsPutUChar( 0x72 ); + bsPutUChar( 0x45 ); + bsPutUChar( 0x38 ); + bsPutUChar( 0x50 ); + bsPutUChar( 0x90 ); + + bsPutint( combinedCRC ); + + bsFinishedWithStream(); + } + + private boolean fullGtU( int i1, int i2 ) + { + int k; + char c1; + char c2; + int s1; + int s2; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + k = last + 1; + + do + { + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + if( i1 > last ) + { + i1 -= last; + i1--; + } + ; + if( i2 > last ) + { + i2 -= last; + i2--; + } + ; + + k -= 4; + workDone++; + }while ( k >= 0 ); + + return false; + } + + private void generateMTFValues() + { + char yy[] = new char[256]; + int i; + int j; + char tmp; + char tmp2; + int zPend; + int wr; + int EOB; + + makeMaps(); + EOB = nInUse + 1; + + for( i = 0; i <= EOB; i++ ) + mtfFreq[i] = 0; + + wr = 0; + zPend = 0; + for( i = 0; i < nInUse; i++ ) + yy[i] = ( char )i; + + for( i = 0; i <= last; i++ ) + { + char ll_i; + + ll_i = unseqToSeq[block[zptr[i]]]; + + j = 0; + tmp = yy[j]; + while( ll_i != tmp ) + { + j++; + tmp2 = tmp; + tmp = yy[j]; + yy[j] = tmp2; + } + ; + yy[0] = tmp; + + if( j == 0 ) + { + zPend++; + } + else + { + if( zPend > 0 ) + { + zPend--; + while( true ) + { + switch ( zPend % 2 ) + { + case 0: + szptr[wr] = ( short )RUNA; + wr++; + mtfFreq[RUNA]++; + break; + case 1: + szptr[wr] = ( short )RUNB; + wr++; + mtfFreq[RUNB]++; + break; + } + ; + if( zPend < 2 ) + break; + zPend = ( zPend - 2 ) / 2; + } + ; + zPend = 0; + } + szptr[wr] = ( short )( j + 1 ); + wr++; + mtfFreq[j + 1]++; + } + } + + if( zPend > 0 ) + { + zPend--; + while( true ) + { + switch ( zPend % 2 ) + { + case 0: + szptr[wr] = ( short )RUNA; + wr++; + mtfFreq[RUNA]++; + break; + case 1: + szptr[wr] = ( short )RUNB; + wr++; + mtfFreq[RUNB]++; + break; + } + if( zPend < 2 ) + break; + zPend = ( zPend - 2 ) / 2; + } + } + + szptr[wr] = ( short )EOB; + wr++; + mtfFreq[EOB]++; + + nMTF = wr; + } + + private void hbAssignCodes( int[] code, char[] length, int minLen, + int maxLen, int alphaSize ) + { + int n; + int vec; + int i; + + vec = 0; + for( n = minLen; n <= maxLen; n++ ) + { + for( i = 0; i < alphaSize; i++ ) + if( length[i] == n ) + { + code[i] = vec; + vec++; + } + ; + vec <<= 1; + } + } + + private void initBlock() + { + // blockNo++; + mCrc.initialiseCRC(); + last = -1; + // ch = 0; + + for( int i = 0; i < 256; i++ ) + inUse[i] = false; + + /* + * 20 is just a paranoia constant + */ + allowableBlockSize = baseBlockSize * blockSize100k - 20; + } + + private void initialize() + throws IOException + { + bytesIn = 0; + bytesOut = 0; + nBlocksRandomised = 0; + + /* + * Write `magic' bytes h indicating file-format == huffmanised, + * followed by a digit indicating blockSize100k. + */ + bsPutUChar( 'h' ); + bsPutUChar( '0' + blockSize100k ); + + combinedCRC = 0; + } + + private void mainSort() + { + int i; + int j; + int ss; + int sb; + int runningOrder[] = new int[256]; + int copy[] = new int[256]; + boolean bigDone[] = new boolean[256]; + int c1; + int c2; + int numQSorted; + + /* + * In the various block-sized structures, live data runs + * from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, + * set up the overshoot area for block. + */ + // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); + for( i = 0; i < NUM_OVERSHOOT_BYTES; i++ ) + block[last + i + 2] = block[( i % ( last + 1 ) ) + 1]; + for( i = 0; i <= last + NUM_OVERSHOOT_BYTES; i++ ) + quadrant[i] = 0; + + block[0] = ( char )( block[last + 1] ); + + if( last < 4000 ) + { + /* + * Use simpleSort(), since the full sorting mechanism + * has quite a large constant overhead. + */ + for( i = 0; i <= last; i++ ) + zptr[i] = i; + firstAttempt = false; + workDone = workLimit = 0; + simpleSort( 0, last, 0 ); + } + else + { + numQSorted = 0; + for( i = 0; i <= 255; i++ ) + bigDone[i] = false; + + for( i = 0; i <= 65536; i++ ) + ftab[i] = 0; + + c1 = block[0]; + for( i = 0; i <= last; i++ ) + { + c2 = block[i + 1]; + ftab[( c1 << 8 ) + c2]++; + c1 = c2; + } + + for( i = 1; i <= 65536; i++ ) + ftab[i] += ftab[i - 1]; + + c1 = block[1]; + for( i = 0; i < last; i++ ) + { + c2 = block[i + 2]; + j = ( c1 << 8 ) + c2; + c1 = c2; + ftab[j]--; + zptr[ftab[j]] = i; + } + + j = ( ( block[last + 1] ) << 8 ) + ( block[1] ); + ftab[j]--; + zptr[ftab[j]] = last; + + /* + * Now ftab contains the first loc of every small bucket. + * Calculate the running order, from smallest to largest + * big bucket. + */ + for( i = 0; i <= 255; i++ ) + runningOrder[i] = i; + { + int vv; + int h = 1; + do + h = 3 * h + 1; +while ( h <= 256 ); + do + { + h = h / 3; + for( i = h; i <= 255; i++ ) + { + vv = runningOrder[i]; + j = i; + while( ( ftab[( ( runningOrder[j - h] ) + 1 ) << 8] + - ftab[( runningOrder[j - h] ) << 8] ) > + ( ftab[( ( vv ) + 1 ) << 8] - ftab[( vv ) << 8] ) ) + { + runningOrder[j] = runningOrder[j - h]; + j = j - h; + if( j <= ( h - 1 ) ) + break; + } + runningOrder[j] = vv; + } + }while ( h != 1 ); + } + + /* + * The main sorting loop. + */ + for( i = 0; i <= 255; i++ ) + { + + /* + * Process big buckets, starting with the least full. + */ + ss = runningOrder[i]; + + /* + * Complete the big bucket [ss] by quicksorting + * any unsorted small buckets [ss, j]. Hopefully + * previous pointer-scanning phases have already + * completed many of the small buckets [ss, j], so + * we don't have to sort them at all. + */ + for( j = 0; j <= 255; j++ ) + { + sb = ( ss << 8 ) + j; + if( !( ( ftab[sb] & SETMASK ) == SETMASK ) ) + { + int lo = ftab[sb] & CLEARMASK; + int hi = ( ftab[sb + 1] & CLEARMASK ) - 1; + if( hi > lo ) + { + qSort3( lo, hi, 2 ); + numQSorted += ( hi - lo + 1 ); + if( workDone > workLimit && firstAttempt ) + return; + } + ftab[sb] |= SETMASK; + } + } + + /* + * The ss big bucket is now done. Record this fact, + * and update the quadrant descriptors. Remember to + * update quadrants in the overshoot area too, if + * necessary. The "if (i < 255)" test merely skips + * this updating for the last bucket processed, since + * updating for the last bucket is pointless. + */ + bigDone[ss] = true; + + if( i < 255 ) + { + int bbStart = ftab[ss << 8] & CLEARMASK; + int bbSize = ( ftab[( ss + 1 ) << 8] & CLEARMASK ) - bbStart; + int shifts = 0; + + while( ( bbSize >> shifts ) > 65534 ) + shifts++; + + for( j = 0; j < bbSize; j++ ) + { + int a2update = zptr[bbStart + j]; + int qVal = ( j >> shifts ); + quadrant[a2update] = qVal; + if( a2update < NUM_OVERSHOOT_BYTES ) + quadrant[a2update + last + 1] = qVal; + } + + if( !( ( ( bbSize - 1 ) >> shifts ) <= 65535 ) ) + panic(); + } + + /* + * Now scan this big bucket so as to synthesise the + * sorted order for small buckets [t, ss] for all t != ss. + */ + for( j = 0; j <= 255; j++ ) + copy[j] = ftab[( j << 8 ) + ss] & CLEARMASK; + + for( j = ftab[ss << 8] & CLEARMASK; + j < ( ftab[( ss + 1 ) << 8] & CLEARMASK ); j++ ) + { + c1 = block[zptr[j]]; + if( !bigDone[c1] ) + { + zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; + copy[c1]++; + } + } + + for( j = 0; j <= 255; j++ ) + ftab[( j << 8 ) + ss] |= SETMASK; + } + } + } + + private void makeMaps() + { + int i; + nInUse = 0; + for( i = 0; i < 256; i++ ) + if( inUse[i] ) + { + seqToUnseq[nInUse] = ( char )i; + unseqToSeq[i] = ( char )nInUse; + nInUse++; + } + } + + private char med3( char a, char b, char c ) + { + char t; + if( a > b ) + { + t = a; + a = b; + b = t; + } + if( b > c ) + { + t = b; + b = c; + c = t; + } + if( a > b ) + b = a; + return b; + } + + private void moveToFrontCodeAndSend() + throws IOException + { + bsPutIntVS( 24, origPtr ); + generateMTFValues(); + sendMTFValues(); + } + + private void qSort3( int loSt, int hiSt, int dSt ) + { + int unLo; + int unHi; + int ltLo; + int gtHi; + int med; + int n; + int m; + int sp; + int lo; + int hi; + int d; + StackElem[] stack = new StackElem[QSORT_STACK_SIZE]; + for( int count = 0; count < QSORT_STACK_SIZE; count++ ) + stack[count] = new StackElem(); + + sp = 0; + + stack[sp].ll = loSt; + stack[sp].hh = hiSt; + stack[sp].dd = dSt; + sp++; + + while( sp > 0 ) + { + if( sp >= QSORT_STACK_SIZE ) + panic(); + + sp--; + lo = stack[sp].ll; + hi = stack[sp].hh; + d = stack[sp].dd; + + if( hi - lo < SMALL_THRESH || d > DEPTH_THRESH ) + { + simpleSort( lo, hi, d ); + if( workDone > workLimit && firstAttempt ) + return; + continue; + } + + med = med3( block[zptr[lo] + d + 1], + block[zptr[hi] + d + 1], + block[zptr[( lo + hi ) >> 1] + d + 1] ); + + unLo = ltLo = lo; + unHi = gtHi = hi; + + while( true ) + { + while( true ) + { + if( unLo > unHi ) + break; + n = ( ( int )block[zptr[unLo] + d + 1] ) - med; + if( n == 0 ) + { + int temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[ltLo]; + zptr[ltLo] = temp; + ltLo++; + unLo++; + continue; + } + ; + if( n > 0 ) + break; + unLo++; + } + while( true ) + { + if( unLo > unHi ) + break; + n = ( ( int )block[zptr[unHi] + d + 1] ) - med; + if( n == 0 ) + { + int temp = 0; + temp = zptr[unHi]; + zptr[unHi] = zptr[gtHi]; + zptr[gtHi] = temp; + gtHi--; + unHi--; + continue; + } + ; + if( n < 0 ) + break; + unHi--; + } + if( unLo > unHi ) + break; + int temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[unHi]; + zptr[unHi] = temp; + unLo++; + unHi--; + } + + if( gtHi < ltLo ) + { + stack[sp].ll = lo; + stack[sp].hh = hi; + stack[sp].dd = d + 1; + sp++; + continue; + } + + n = ( ( ltLo - lo ) < ( unLo - ltLo ) ) ? ( ltLo - lo ) : ( unLo - ltLo ); + vswap( lo, unLo - n, n ); + m = ( ( hi - gtHi ) < ( gtHi - unHi ) ) ? ( hi - gtHi ) : ( gtHi - unHi ); + vswap( unLo, hi - m + 1, m ); + + n = lo + unLo - ltLo - 1; + m = hi - ( gtHi - unHi ) + 1; + + stack[sp].ll = lo; + stack[sp].hh = n; + stack[sp].dd = d; + sp++; + + stack[sp].ll = n + 1; + stack[sp].hh = m - 1; + stack[sp].dd = d + 1; + sp++; + + stack[sp].ll = m; + stack[sp].hh = hi; + stack[sp].dd = d; + sp++; + } + } + + private void randomiseBlock() + { + int i; + int rNToGo = 0; + int rTPos = 0; + for( i = 0; i < 256; i++ ) + inUse[i] = false; + + for( i = 0; i <= last; i++ ) + { + if( rNToGo == 0 ) + { + rNToGo = ( char )rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + block[i + 1] ^= ( ( rNToGo == 1 ) ? 1 : 0 ); + // handle 16 bit signed numbers + block[i + 1] &= 0xFF; + + inUse[block[i + 1]] = true; + } + } + + private void sendMTFValues() + throws IOException + { + char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE]; + + int v; + + int t; + + int i; + + int j; + + int gs; + + int ge; + + int totc; + + int bt; + + int bc; + + int iter; + int nSelectors = 0; + int alphaSize; + int minLen; + int maxLen; + int selCtr; + int nGroups; + int nBytes; + + alphaSize = nInUse + 2; + for( t = 0; t < N_GROUPS; t++ ) + for( v = 0; v < alphaSize; v++ ) + len[t][v] = ( char )GREATER_ICOST; + + /* + * Decide how many coding tables to use + */ + if( nMTF <= 0 ) + panic(); + + if( nMTF < 200 ) + nGroups = 2; + else if( nMTF < 600 ) + nGroups = 3; + else if( nMTF < 1200 ) + nGroups = 4; + else if( nMTF < 2400 ) + nGroups = 5; + else + nGroups = 6; + { + /* + * Generate an initial set of coding tables + */ + int nPart; + int remF; + int tFreq; + int aFreq; + + nPart = nGroups; + remF = nMTF; + gs = 0; + while( nPart > 0 ) + { + tFreq = remF / nPart; + ge = gs - 1; + aFreq = 0; + while( aFreq < tFreq && ge < alphaSize - 1 ) + { + ge++; + aFreq += mtfFreq[ge]; + } + + if( ge > gs && nPart != nGroups && nPart != 1 + && ( ( nGroups - nPart ) % 2 == 1 ) ) + { + aFreq -= mtfFreq[ge]; + ge--; + } + + for( v = 0; v < alphaSize; v++ ) + if( v >= gs && v <= ge ) + len[nPart - 1][v] = ( char )LESSER_ICOST; + else + len[nPart - 1][v] = ( char )GREATER_ICOST; + + nPart--; + gs = ge + 1; + remF -= aFreq; + } + } + + int rfreq[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + int fave[] = new int[N_GROUPS]; + short cost[] = new short[N_GROUPS]; + /* + * Iterate up to N_ITERS times to improve the tables. + */ + for( iter = 0; iter < N_ITERS; iter++ ) + { + for( t = 0; t < nGroups; t++ ) + fave[t] = 0; + + for( t = 0; t < nGroups; t++ ) + for( v = 0; v < alphaSize; v++ ) + rfreq[t][v] = 0; + + nSelectors = 0; + totc = 0; + gs = 0; + while( true ) + { + + /* + * Set group start & end marks. + */ + if( gs >= nMTF ) + break; + ge = gs + G_SIZE - 1; + if( ge >= nMTF ) + ge = nMTF - 1; + + /* + * Calculate the cost of this group as coded + * by each of the coding tables. + */ + for( t = 0; t < nGroups; t++ ) + cost[t] = 0; + + if( nGroups == 6 ) + { + short cost0; + short cost1; + short cost2; + short cost3; + short cost4; + short cost5; + cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; + for( i = gs; i <= ge; i++ ) + { + short icv = szptr[i]; + cost0 += len[0][icv]; + cost1 += len[1][icv]; + cost2 += len[2][icv]; + cost3 += len[3][icv]; + cost4 += len[4][icv]; + cost5 += len[5][icv]; + } + cost[0] = cost0; + cost[1] = cost1; + cost[2] = cost2; + cost[3] = cost3; + cost[4] = cost4; + cost[5] = cost5; + } + else + { + for( i = gs; i <= ge; i++ ) + { + short icv = szptr[i]; + for( t = 0; t < nGroups; t++ ) + cost[t] += len[t][icv]; + } + } + + /* + * Find the coding table which is best for this group, + * and record its identity in the selector table. + */ + bc = 999999999; + bt = -1; + for( t = 0; t < nGroups; t++ ) + if( cost[t] < bc ) + { + bc = cost[t]; + bt = t; + } + ; + totc += bc; + fave[bt]++; + selector[nSelectors] = ( char )bt; + nSelectors++; + + /* + * Increment the symbol frequencies for the selected table. + */ + for( i = gs; i <= ge; i++ ) + rfreq[bt][szptr[i]]++; + + gs = ge + 1; + } + + /* + * Recompute the tables based on the accumulated frequencies. + */ + for( t = 0; t < nGroups; t++ ) + hbMakeCodeLengths( len[t], rfreq[t], alphaSize, 20 ); + } + + rfreq = null; + fave = null; + cost = null; + + if( !( nGroups < 8 ) ) + panic(); + if( !( nSelectors < 32768 && nSelectors <= ( 2 + ( 900000 / G_SIZE ) ) ) ) + panic(); + { + /* + * Compute MTF values for the selectors. + */ + char pos[] = new char[N_GROUPS]; + char ll_i; + char tmp2; + char tmp; + for( i = 0; i < nGroups; i++ ) + pos[i] = ( char )i; + for( i = 0; i < nSelectors; i++ ) + { + ll_i = selector[i]; + j = 0; + tmp = pos[j]; + while( ll_i != tmp ) + { + j++; + tmp2 = tmp; + tmp = pos[j]; + pos[j] = tmp2; + } + pos[0] = tmp; + selectorMtf[i] = ( char )j; + } + } + + int code[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + + /* + * Assign actual codes for the tables. + */ + for( t = 0; t < nGroups; t++ ) + { + minLen = 32; + maxLen = 0; + for( i = 0; i < alphaSize; i++ ) + { + if( len[t][i] > maxLen ) + maxLen = len[t][i]; + if( len[t][i] < minLen ) + minLen = len[t][i]; + } + if( maxLen > 20 ) + panic(); + if( minLen < 1 ) + panic(); + hbAssignCodes( code[t], len[t], minLen, maxLen, alphaSize ); + } + { + /* + * Transmit the mapping table. + */ + boolean inUse16[] = new boolean[16]; + for( i = 0; i < 16; i++ ) + { + inUse16[i] = false; + for( j = 0; j < 16; j++ ) + if( inUse[i * 16 + j] ) + inUse16[i] = true; + } + + nBytes = bytesOut; + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + bsW( 1, 1 ); + else + bsW( 1, 0 ); + + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + for( j = 0; j < 16; j++ ) + if( inUse[i * 16 + j] ) + bsW( 1, 1 ); + else + bsW( 1, 0 ); + + } + + /* + * Now the selectors. + */ + nBytes = bytesOut; + bsW( 3, nGroups ); + bsW( 15, nSelectors ); + for( i = 0; i < nSelectors; i++ ) + { + for( j = 0; j < selectorMtf[i]; j++ ) + bsW( 1, 1 ); + bsW( 1, 0 ); + } + + /* + * Now the coding tables. + */ + nBytes = bytesOut; + + for( t = 0; t < nGroups; t++ ) + { + int curr = len[t][0]; + bsW( 5, curr ); + for( i = 0; i < alphaSize; i++ ) + { + while( curr < len[t][i] ) + { + bsW( 2, 2 ); + curr++; + /* + * 10 + */ + } + while( curr > len[t][i] ) + { + bsW( 2, 3 ); + curr--; + /* + * 11 + */ + } + bsW( 1, 0 ); + } + } + + /* + * And finally, the block data proper + */ + nBytes = bytesOut; + selCtr = 0; + gs = 0; + while( true ) + { + if( gs >= nMTF ) + break; + ge = gs + G_SIZE - 1; + if( ge >= nMTF ) + ge = nMTF - 1; + for( i = gs; i <= ge; i++ ) + { + bsW( len[selector[selCtr]][szptr[i]], + code[selector[selCtr]][szptr[i]] ); + } + + gs = ge + 1; + selCtr++; + } + if( !( selCtr == nSelectors ) ) + panic(); + } + + private void simpleSort( int lo, int hi, int d ) + { + int i; + int j; + int h; + int bigN; + int hp; + int v; + + bigN = hi - lo + 1; + if( bigN < 2 ) + return; + + hp = 0; + while( incs[hp] < bigN ) + hp++; + hp--; + + for( ; hp >= 0; hp-- ) + { + h = incs[hp]; + + i = lo + h; + while( true ) + { + /* + * copy 1 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + /* + * copy 2 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + /* + * copy 3 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + if( workDone > workLimit && firstAttempt ) + return; + } + } + } + + private void vswap( int p1, int p2, int n ) + { + int temp = 0; + while( n > 0 ) + { + temp = zptr[p1]; + zptr[p1] = zptr[p2]; + zptr[p2] = temp; + p1++; + p2++; + n--; + } + } + + private void writeRun() + throws IOException + { + if( last < allowableBlockSize ) + { + inUse[currentChar] = true; + for( int i = 0; i < runLength; i++ ) + { + mCrc.updateCRC( ( char )currentChar ); + } + switch ( runLength ) + { + case 1: + last++; + block[last + 1] = ( char )currentChar; + break; + case 2: + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + break; + case 3: + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + break; + default: + inUse[runLength - 4] = true; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )( runLength - 4 ); + break; + } + } + else + { + endBlock(); + initBlock(); + writeRun(); + } + } + + private class StackElem + { + int dd; + int hh; + int ll; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CRC.java b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CRC.java new file mode 100644 index 000000000..f12b482a6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CRC.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; + +/** + * A simple class the hold and calculate the CRC for sanity checking of the + * data. + * + * @author Keiron Liddle + */ +class CRC +{ + public static int crc32Table[] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + }; + + int globalCrc; + + public CRC() + { + initialiseCRC(); + } + + void setGlobalCRC( int newCrc ) + { + globalCrc = newCrc; + } + + int getFinalCRC() + { + return ~globalCrc; + } + + int getGlobalCRC() + { + return globalCrc; + } + + void initialiseCRC() + { + globalCrc = 0xffffffff; + } + + void updateCRC( int inCh ) + { + int temp = ( globalCrc >> 24 ) ^ inCh; + if( temp < 0 ) + temp = 256 + temp; + globalCrc = ( globalCrc << 8 ) ^ CRC.crc32Table[temp]; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/mail/MailMessage.java b/proposal/myrmidon/src/todo/org/apache/tools/mail/MailMessage.java new file mode 100644 index 000000000..d8520d6c6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/mail/MailMessage.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.mail; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.InetAddress; +import java.net.Socket; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +/** + * A class to help send SMTP email. This class is an improvement on the + * sun.net.smtp.SmtpClient class found in the JDK. This version has extra + * functionality, and can be used with JVMs that did not extend from the JDK. + * It's not as robust as the JavaMail Standard Extension classes, but it's + * easier to use and easier to install, and has an Open Source license.

                  + * + * It can be used like this:

                  + * String mailhost = "localhost";  // or another mail host
                  + * String from = "Mail Message Servlet <MailMessage@server.com>";
                  + * String to = "to@you.com";
                  + * String cc1 = "cc1@you.com";
                  + * String cc2 = "cc2@you.com";
                  + * String bcc = "bcc@you.com";
                  + *  
                  + * MailMessage msg = new MailMessage(mailhost);
                  + * msg.setPort(25);
                  + * msg.from(from);
                  + * msg.to(to);
                  + * msg.cc(cc1);
                  + * msg.cc(cc2);
                  + * msg.bcc(bcc);
                  + * msg.setSubject("Test subject");
                  + * PrintStream out = msg.getPrintStream();
                  + *  
                  + * Enumeration enum = req.getParameterNames();
                  + * while (enum.hasMoreElements()) {
                  + *   String name = (String)enum.nextElement();
                  + *   String value = req.getParameter(name);
                  + *   out.println(name + " = " + value);
                  + * }
                  + *  
                  + * msg.sendAndClose();
                  + * 

                  + * + * Be sure to set the from address, then set the recepient addresses, then set + * the subject and other headers, then get the PrintStream, then write the + * message, and finally send and close. The class does minimal error checking + * internally; it counts on the mail host to complain if there's any + * malformatted input or out of order execution.

                  + * + * An attachment mechanism based on RFC 1521 could be implemented on top of this + * class. In the meanwhile, JavaMail is the best solution for sending email with + * attachments.

                  + * + * Still to do: + *

                    + *
                  • Figure out how to close the connection in case of error + *
                  + * + * + * @author Jason Hunter + * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers + * version 1.0, 1999/12/29 + */ +public class MailMessage +{ + + /** + * default port for SMTP: 25 + */ + public final static int DEFAULT_PORT = 25; + + /** + * host port for the mail server + */ + private int port = DEFAULT_PORT; + + /** + * list of email addresses to cc to + */ + private Vector cc; + + /** + * sender email address + */ + private String from; + + /** + * headers to send in the mail + */ + private Hashtable headers; + + /** + * host name for the mail server + */ + private String host; + + private SmtpResponseReader in; + + private MailPrintStream out; + + private Socket socket; + + /** + * list of email addresses to send to + */ + private Vector to; + + /** + * Constructs a new MailMessage to send an email. Use localhost as the mail + * server. + * + * @exception IOException if there's any problem contacting the mail server + */ + public MailMessage() + throws IOException + { + this( "localhost" ); + } + + /** + * Constructs a new MailMessage to send an email. Use the given host as the + * mail server. + * + * @param host the mail server to use + * @exception IOException if there's any problem contacting the mail server + */ + public MailMessage( String host ) + throws IOException + { + this.host = host; + to = new Vector(); + cc = new Vector(); + headers = new Hashtable(); + setHeader( "X-Mailer", "org.apache.tools.mail.MailMessage (jakarta.apache.org)" ); + connect(); + sendHelo(); + } + + // Make a limited attempt to extract a sanitized email address + // Prefer text in , ignore anything in (parentheses) + static String sanitizeAddress( String s ) + { + int paramDepth = 0; + int start = 0; + int end = 0; + int len = s.length(); + + for( int i = 0; i < len; i++ ) + { + char c = s.charAt( i ); + if( c == '(' ) + { + paramDepth++; + if( start == 0 ) + { + end = i;// support "address (name)" + } + } + else if( c == ')' ) + { + paramDepth--; + if( end == 0 ) + { + start = i + 1;// support "(name) address" + } + } + else if( paramDepth == 0 && c == '<' ) + { + start = i + 1; + } + else if( paramDepth == 0 && c == '>' ) + { + end = i; + } + } + + if( end == 0 ) + { + end = len; + } + + return s.substring( start, end ); + } + + /** + * Sets the named header to the given value. RFC 822 provides the rules for + * what text may constitute a header name and value. + * + * @param name The new Header value + * @param value The new Header value + */ + public void setHeader( String name, String value ) + { + // Blindly trust the user doesn't set any invalid headers + headers.put( name, value ); + } + + /** + * Set the port to connect to the SMTP host. + * + * @param port the port to use for connection. + * @see #DEFAULT_PORT + */ + public void setPort( int port ) + { + this.port = port; + } + + /** + * Sets the subject of the mail message. Actually sets the "Subject" header. + * + * @param subj The new Subject value + */ + public void setSubject( String subj ) + { + headers.put( "Subject", subj ); + } + + /** + * Returns a PrintStream that can be used to write the body of the message. + * A stream is used since email bodies are byte-oriented. A writer could be + * wrapped on top if necessary for internationalization. + * + * @return The PrintStream value + * @exception IOException if there's any problem reported by the mail server + */ + public PrintStream getPrintStream() + throws IOException + { + setFromHeader(); + setToHeader(); + setCcHeader(); + sendData(); + flushHeaders(); + return out; + } + + /** + * Sets the bcc address. Does NOT set any header since it's a *blind* copy. + * This method may be called multiple times. + * + * @param bcc Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void bcc( String bcc ) + throws IOException + { + sendRcpt( bcc ); + // No need to keep track of Bcc'd addresses + } + + /** + * Sets the cc address. Also sets the "Cc" header. This method may be called + * multiple times. + * + * @param cc Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void cc( String cc ) + throws IOException + { + sendRcpt( cc ); + this.cc.addElement( cc ); + } + + /** + * Sets the from address. Also sets the "From" header. This method should be + * called only once. + * + * @param from Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void from( String from ) + throws IOException + { + sendFrom( from ); + this.from = from; + } + + /** + * Sends the message and closes the connection to the server. The + * MailMessage object cannot be reused. + * + * @exception IOException if there's any problem reported by the mail server + */ + public void sendAndClose() + throws IOException + { + sendDot(); + sendQuit(); + disconnect(); + } + + /** + * Sets the to address. Also sets the "To" header. This method may be called + * multiple times. + * + * @param to Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void to( String to ) + throws IOException + { + sendRcpt( to ); + this.to.addElement( to ); + } + + void setCcHeader() + { + setHeader( "Cc", vectorToList( cc ) ); + } + + void setFromHeader() + { + setHeader( "From", from ); + } + + void setToHeader() + { + setHeader( "To", vectorToList( to ) ); + } + + void getReady() + throws IOException + { + String response = in.getResponse(); + int[] ok = {220}; + if( !isResponseOK( response, ok ) ) + { + throw new IOException( + "Didn't get introduction from server: " + response ); + } + } + + boolean isResponseOK( String response, int[] ok ) + { + // Check that the response is one of the valid codes + for( int i = 0; i < ok.length; i++ ) + { + if( response.startsWith( "" + ok[i] ) ) + { + return true; + } + } + return false; + } + + // * * * * * Raw protocol methods below here * * * * * + + void connect() + throws IOException + { + socket = new Socket( host, port ); + out = new MailPrintStream( + new BufferedOutputStream( + socket.getOutputStream() ) ); + in = new SmtpResponseReader( socket.getInputStream() ); + getReady(); + } + + void disconnect() + throws IOException + { + if( out != null ) + out.close(); + if( in != null ) + in.close(); + if( socket != null ) + socket.close(); + } + + void flushHeaders() + throws IOException + { + // XXX Should I care about order here? + Enumeration e = headers.keys(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + String value = ( String )headers.get( name ); + out.println( name + ": " + value ); + } + out.println(); + out.flush(); + } + + void send( String msg, int[] ok ) + throws IOException + { + out.rawPrint( msg + "\r\n" );// raw supports . + //System.out.println("S: " + msg); + String response = in.getResponse(); + //System.out.println("R: " + response); + if( !isResponseOK( response, ok ) ) + { + throw new IOException( + "Unexpected reply to command: " + msg + ": " + response ); + } + } + + void sendData() + throws IOException + { + int[] ok = {354}; + send( "DATA", ok ); + } + + void sendDot() + throws IOException + { + int[] ok = {250}; + send( "\r\n.", ok );// make sure dot is on new line + } + + void sendFrom( String from ) + throws IOException + { + int[] ok = {250}; + send( "MAIL FROM: " + "<" + sanitizeAddress( from ) + ">", ok ); + } + + void sendHelo() + throws IOException + { + String local = InetAddress.getLocalHost().getHostName(); + int[] ok = {250}; + send( "HELO " + local, ok ); + } + + void sendQuit() + throws IOException + { + int[] ok = {221}; + send( "QUIT", ok ); + } + + void sendRcpt( String rcpt ) + throws IOException + { + int[] ok = {250, 251}; + send( "RCPT TO: " + "<" + sanitizeAddress( rcpt ) + ">", ok ); + } + + String vectorToList( Vector v ) + { + StringBuffer buf = new StringBuffer(); + Enumeration e = v.elements(); + while( e.hasMoreElements() ) + { + buf.append( e.nextElement() ); + if( e.hasMoreElements() ) + { + buf.append( ", " ); + } + } + return buf.toString(); + } +} + +// This PrintStream subclass makes sure that . becomes .. +// per RFC 821. It also ensures that new lines are always \r\n. +// +class MailPrintStream extends PrintStream +{ + + int lastChar; + + public MailPrintStream( OutputStream out ) + { + super( out, true );// deprecated, but email is byte-oriented + } + + // Mac does \n\r, but that's tough to distinguish from Windows \r\n\r\n. + // Don't tackle that problem right now. + public void write( int b ) + { + if( b == '\n' && lastChar != '\r' ) + { + rawWrite( '\r' );// ensure always \r\n + rawWrite( b ); + } + else if( b == '.' && lastChar == '\n' ) + { + rawWrite( '.' );// add extra dot + rawWrite( b ); + } + else + { + rawWrite( b ); + } + lastChar = b; + } + + public void write( byte buf[], int off, int len ) + { + for( int i = 0; i < len; i++ ) + { + write( buf[off + i] ); + } + } + + void rawPrint( String s ) + { + int len = s.length(); + for( int i = 0; i < len; i++ ) + { + rawWrite( s.charAt( i ) ); + } + } + + void rawWrite( int b ) + { + super.write( b ); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/mail/SmtpResponseReader.java b/proposal/myrmidon/src/todo/org/apache/tools/mail/SmtpResponseReader.java new file mode 100644 index 000000000..73f935bfc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/mail/SmtpResponseReader.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.mail; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * A wrapper around the raw input from the SMTP server that assembles multi line + * responses into a single String.

                  + * + * The same rules used here would apply to FTP and other Telnet based protocols + * as well.

                  + * + * @author Stefan Bodewig + */ +public class SmtpResponseReader +{ + + protected BufferedReader reader = null; + private StringBuffer result = new StringBuffer(); + + /** + * Wrap this input stream. + * + * @param in Description of Parameter + */ + public SmtpResponseReader( InputStream in ) + { + reader = new BufferedReader( new InputStreamReader( in ) ); + } + + /** + * Read until the server indicates that the response is complete. + * + * @return Responsecode (3 digits) + Blank + Text from all response line + * concatenated (with blanks replacing the \r\n sequences). + * @exception IOException Description of Exception + */ + public String getResponse() + throws IOException + { + result.setLength( 0 ); + String line = reader.readLine(); + if( line != null && line.length() >= 3 ) + { + result.append( line.substring( 0, 3 ) ); + result.append( " " ); + } + + while( line != null ) + { + append( line ); + if( !hasMoreLines( line ) ) + { + break; + } + line = reader.readLine(); + } + return result.toString().trim(); + } + + /** + * Closes the underlying stream. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + reader.close(); + } + + /** + * Should we expect more input? + * + * @param line Description of Parameter + * @return Description of the Returned Value + */ + protected boolean hasMoreLines( String line ) + { + return line.length() > 3 && line.charAt( 3 ) == '-'; + } + + /** + * Append the text from this line of the resonse. + * + * @param line Description of Parameter + */ + private void append( String line ) + { + if( line.length() > 4 ) + { + result.append( line.substring( 4 ) ); + result.append( " " ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarBuffer.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarBuffer.java new file mode 100644 index 000000000..a580a3377 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarBuffer.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The TarBuffer class implements the tar archive concept of a buffered input + * stream. This concept goes back to the days of blocked tape drives and special + * io devices. In the Java universe, the only real function that this class + * performs is to ensure that files have the correct "block" size, or other tars + * will complain.

                  + * + * You should never have a need to access this class directly. TarBuffers are + * created by Tar IO Streams. + * + * @author Timothy Gerard Endres time@ice.com + */ + +public class TarBuffer +{ + + public final static int DEFAULT_RCDSIZE = ( 512 ); + public final static int DEFAULT_BLKSIZE = ( DEFAULT_RCDSIZE * 20 ); + private byte[] blockBuffer; + private int blockSize; + private int currBlkIdx; + private int currRecIdx; + private boolean debug; + + private InputStream inStream; + private OutputStream outStream; + private int recordSize; + private int recsPerBlock; + + public TarBuffer( InputStream inStream ) + { + this( inStream, TarBuffer.DEFAULT_BLKSIZE ); + } + + public TarBuffer( InputStream inStream, int blockSize ) + { + this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarBuffer( InputStream inStream, int blockSize, int recordSize ) + { + this.inStream = inStream; + this.outStream = null; + + this.initialize( blockSize, recordSize ); + } + + public TarBuffer( OutputStream outStream ) + { + this( outStream, TarBuffer.DEFAULT_BLKSIZE ); + } + + public TarBuffer( OutputStream outStream, int blockSize ) + { + this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarBuffer( OutputStream outStream, int blockSize, int recordSize ) + { + this.inStream = null; + this.outStream = outStream; + + this.initialize( blockSize, recordSize ); + } + + /** + * Set the debugging flag for the buffer. + * + * @param debug If true, print debugging output. + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Get the TAR Buffer's block size. Blocks consist of multiple records. + * + * @return The BlockSize value + */ + public int getBlockSize() + { + return this.blockSize; + } + + /** + * Get the current block number, zero based. + * + * @return The current zero based block number. + */ + public int getCurrentBlockNum() + { + return this.currBlkIdx; + } + + /** + * Get the current record number, within the current block, zero based. + * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum. + * + * @return The current zero based record number. + */ + public int getCurrentRecordNum() + { + return this.currRecIdx - 1; + } + + /** + * Get the TAR Buffer's record size. + * + * @return The RecordSize value + */ + public int getRecordSize() + { + return this.recordSize; + } + + /** + * Determine if an archive record indicate End of Archive. End of archive is + * indicated by a record that consists entirely of null bytes. + * + * @param record The record data to check. + * @return The EOFRecord value + */ + public boolean isEOFRecord( byte[] record ) + { + for( int i = 0, sz = this.getRecordSize(); i < sz; ++i ) + { + if( record[i] != 0 ) + { + return false; + } + } + + return true; + } + + /** + * Close the TarBuffer. If this is an output buffer, also flush the current + * block before closing. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + if( this.debug ) + { + System.err.println( "TarBuffer.closeBuffer()." ); + } + + if( this.outStream != null ) + { + this.flushBlock(); + + if( this.outStream != System.out + && this.outStream != System.err ) + { + this.outStream.close(); + + this.outStream = null; + } + } + else if( this.inStream != null ) + { + if( this.inStream != System.in ) + { + this.inStream.close(); + + this.inStream = null; + } + } + } + + /** + * Read a record from the input stream and return the data. + * + * @return The record data. + * @exception IOException Description of Exception + */ + public byte[] readRecord() + throws IOException + { + if( this.debug ) + { + System.err.println( "ReadRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading from an output buffer" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + if( !this.readBlock() ) + { + return null; + } + } + + byte[] result = new byte[this.recordSize]; + + System.arraycopy( this.blockBuffer, + ( this.currRecIdx * this.recordSize ), result, 0, + this.recordSize ); + + this.currRecIdx++; + + return result; + } + + /** + * Skip over a record on the input stream. + * + * @exception IOException Description of Exception + */ + public void skipRecord() + throws IOException + { + if( this.debug ) + { + System.err.println( "SkipRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading (via skip) from an output buffer" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + if( !this.readBlock() ) + { + return;// UNDONE + } + } + + this.currRecIdx++; + } + + /** + * Write an archive record to the archive. + * + * @param record The record data to write to the archive. + * @exception IOException Description of Exception + */ + public void writeRecord( byte[] record ) + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( record.length != this.recordSize ) + { + throw new IOException( "record to write has length '" + + record.length + + "' which is not the record size of '" + + this.recordSize + "'" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + this.writeBlock(); + } + + System.arraycopy( record, 0, this.blockBuffer, + ( this.currRecIdx * this.recordSize ), + this.recordSize ); + + this.currRecIdx++; + } + + /** + * Write an archive record to the archive, where the record may be inside of + * a larger array buffer. The buffer must be "offset plus record size" long. + * + * @param buf The buffer containing the record data to write. + * @param offset The offset of the record data within buf. + * @exception IOException Description of Exception + */ + public void writeRecord( byte[] buf, int offset ) + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( ( offset + this.recordSize ) > buf.length ) + { + throw new IOException( "record has length '" + buf.length + + "' with offset '" + offset + + "' which is less than the record size of '" + + this.recordSize + "'" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + this.writeBlock(); + } + + System.arraycopy( buf, offset, this.blockBuffer, + ( this.currRecIdx * this.recordSize ), + this.recordSize ); + + this.currRecIdx++; + } + + /** + * Flush the current data block if it has any data in it. + * + * @exception IOException Description of Exception + */ + private void flushBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "TarBuffer.flushBlock() called." ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( this.currRecIdx > 0 ) + { + this.writeBlock(); + } + } + + /** + * Initialization common to all constructors. + * + * @param blockSize Description of Parameter + * @param recordSize Description of Parameter + */ + private void initialize( int blockSize, int recordSize ) + { + this.debug = false; + this.blockSize = blockSize; + this.recordSize = recordSize; + this.recsPerBlock = ( this.blockSize / this.recordSize ); + this.blockBuffer = new byte[this.blockSize]; + + if( this.inStream != null ) + { + this.currBlkIdx = -1; + this.currRecIdx = this.recsPerBlock; + } + else + { + this.currBlkIdx = 0; + this.currRecIdx = 0; + } + } + + /** + * @return false if End-Of-File, else true + * @exception IOException Description of Exception + */ + private boolean readBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "ReadBlock: blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading from an output buffer" ); + } + + this.currRecIdx = 0; + + int offset = 0; + int bytesNeeded = this.blockSize; + + while( bytesNeeded > 0 ) + { + long numBytes = this.inStream.read( this.blockBuffer, offset, + bytesNeeded ); + + // + // NOTE + // We have fit EOF, and the block is not full! + // + // This is a broken archive. It does not follow the standard + // blocking algorithm. However, because we are generous, and + // it requires little effort, we will simply ignore the error + // and continue as if the entire block were read. This does + // not appear to break anything upstream. We used to return + // false in this case. + // + // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix. + // + if( numBytes == -1 ) + { + break; + } + + offset += numBytes; + bytesNeeded -= numBytes; + + if( numBytes != this.blockSize ) + { + if( this.debug ) + { + System.err.println( "ReadBlock: INCOMPLETE READ " + + numBytes + " of " + this.blockSize + + " bytes read." ); + } + } + } + + this.currBlkIdx++; + + return true; + } + + /** + * Write a TarBuffer block to the archive. + * + * @exception IOException Description of Exception + */ + private void writeBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteBlock: blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + this.outStream.write( this.blockBuffer, 0, this.blockSize ); + this.outStream.flush(); + + this.currRecIdx = 0; + this.currBlkIdx++; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarConstants.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarConstants.java new file mode 100644 index 000000000..6203d31d1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarConstants.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; + +/** + * This interface contains all the definitions used in the package. + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ + +public interface TarConstants +{ + + /** + * The length of the name field in a header buffer. + */ + int NAMELEN = 100; + + /** + * The length of the mode field in a header buffer. + */ + int MODELEN = 8; + + /** + * The length of the user id field in a header buffer. + */ + int UIDLEN = 8; + + /** + * The length of the group id field in a header buffer. + */ + int GIDLEN = 8; + + /** + * The length of the checksum field in a header buffer. + */ + int CHKSUMLEN = 8; + + /** + * The length of the size field in a header buffer. + */ + int SIZELEN = 12; + + /** + * The length of the magic field in a header buffer. + */ + int MAGICLEN = 8; + + /** + * The length of the modification time field in a header buffer. + */ + int MODTIMELEN = 12; + + /** + * The length of the user name field in a header buffer. + */ + int UNAMELEN = 32; + + /** + * The length of the group name field in a header buffer. + */ + int GNAMELEN = 32; + + /** + * The length of the devices field in a header buffer. + */ + int DEVLEN = 8; + + /** + * LF_ constants represent the "link flag" of an entry, or more commonly, + * the "entry type". This is the "old way" of indicating a normal file. + */ + byte LF_OLDNORM = 0; + + /** + * Normal file type. + */ + byte LF_NORMAL = ( byte )'0'; + + /** + * Link file type. + */ + byte LF_LINK = ( byte )'1'; + + /** + * Symbolic link file type. + */ + byte LF_SYMLINK = ( byte )'2'; + + /** + * Character device file type. + */ + byte LF_CHR = ( byte )'3'; + + /** + * Block device file type. + */ + byte LF_BLK = ( byte )'4'; + + /** + * Directory file type. + */ + byte LF_DIR = ( byte )'5'; + + /** + * FIFO (pipe) file type. + */ + byte LF_FIFO = ( byte )'6'; + + /** + * Contiguous file type. + */ + byte LF_CONTIG = ( byte )'7'; + + /** + * The magic tag representing a POSIX tar archive. + */ + String TMAGIC = "ustar"; + + /** + * The magic tag representing a GNU tar archive. + */ + String GNU_TMAGIC = "ustar "; + + /** + * The namr of the GNU tar entry which contains a long name. + */ + String GNU_LONGLINK = "././@LongLink"; + + /** + * Identifies the *next* file on the tape as having a long name. + */ + byte LF_GNUTYPE_LONGNAME = ( byte )'L'; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarEntry.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarEntry.java new file mode 100644 index 000000000..7914dd132 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarEntry.java @@ -0,0 +1,654 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.File; +import java.util.Date; + +/** + * This class represents an entry in a Tar archive. It consists of the entry's + * header, as well as the entry's File. Entries can be instantiated in one of + * three ways, depending on how they are to be used.

                  + * + * TarEntries that are created from the header bytes read from an archive are + * instantiated with the TarEntry( byte[] ) constructor. These entries will be + * used when extracting from or listing the contents of an archive. These + * entries have their header filled in using the header bytes. They also set the + * File to null, since they reference an archive entry not a file.

                  + * + * TarEntries that are created from Files that are to be written into an archive + * are instantiated with the TarEntry( File ) constructor. These entries have + * their header filled in using the File's information. They also keep a + * reference to the File for convenience when writing entries.

                  + * + * Finally, TarEntries can be constructed from nothing but a name. This allows + * the programmer to construct the entry by hand, for instance when only an + * InputStream is available for writing to the archive, and the header + * information is constructed from other information. In this case the header + * fields are set to defaults and the File is set to null.

                  + * + * The C structure for a Tar Entry's header is:

                  + * struct header {
                  + * char name[NAMSIZ];
                  + * char mode[8];
                  + * char uid[8];
                  + * char gid[8];
                  + * char size[12];
                  + * char mtime[12];
                  + * char chksum[8];
                  + * char linkflag;
                  + * char linkname[NAMSIZ];
                  + * char magic[8];
                  + * char uname[TUNMLEN];
                  + * char gname[TGNMLEN];
                  + * char devmajor[8];
                  + * char devminor[8];
                  + * } header;
                  + * 
                  + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ + +public class TarEntry implements TarConstants +{ + /** + * The entry's modification time. + */ + private int checkSum; + /** + * The entry's group name. + */ + private int devMajor; + /** + * The entry's major device number. + */ + private int devMinor; + /** + * The entry's minor device number. + */ + private File file; + /** + * The entry's user id. + */ + private int groupId; + /** + * The entry's user name. + */ + private StringBuffer groupName; + /** + * The entry's checksum. + */ + private byte linkFlag; + /** + * The entry's link flag. + */ + private StringBuffer linkName; + /** + * The entry's link name. + */ + private StringBuffer magic; + /** + * The entry's size. + */ + private long modTime; + /** + * The entry's name. + */ + private int mode; + + private StringBuffer name; + /** + * The entry's group id. + */ + private long size; + /** + * The entry's permission mode. + */ + private int userId; + /** + * The entry's magic tag. + */ + private StringBuffer userName; + + /** + * Construct an entry with only a name. This allows the programmer to + * construct the entry's header "by hand". File is set to null. + * + * @param name Description of Parameter + */ + public TarEntry( String name ) + { + this(); + + boolean isDir = name.endsWith( "/" ); + + this.checkSum = 0; + this.devMajor = 0; + this.devMinor = 0; + this.name = new StringBuffer( name ); + this.mode = isDir ? 040755 : 0100644; + this.linkFlag = isDir ? LF_DIR : LF_NORMAL; + this.userId = 0; + this.groupId = 0; + this.size = 0; + this.checkSum = 0; + this.modTime = ( new Date() ).getTime() / 1000; + this.linkName = new StringBuffer( "" ); + this.userName = new StringBuffer( "" ); + this.groupName = new StringBuffer( "" ); + this.devMajor = 0; + this.devMinor = 0; + + } + + /** + * Construct an entry with a name an a link flag. + * + * @param name Description of Parameter + * @param linkFlag Description of Parameter + */ + public TarEntry( String name, byte linkFlag ) + { + this( name ); + this.linkFlag = linkFlag; + } + + /** + * Construct an entry for a file. File is set to file, and the header is + * constructed from information from the file. + * + * @param file The file that the entry represents. + */ + public TarEntry( File file ) + { + this(); + + this.file = file; + + String name = file.getPath(); + String osname = System.getProperty( "os.name" ); + + if( osname != null ) + { + + // Strip off drive letters! + // REVIEW Would a better check be "(File.separator == '\')"? + String win32Prefix = "Windows"; + String prefix = osname.substring( 0, win32Prefix.length() ); + + if( prefix.equalsIgnoreCase( win32Prefix ) ) + { + if( name.length() > 2 ) + { + char ch1 = name.charAt( 0 ); + char ch2 = name.charAt( 1 ); + + if( ch2 == ':' + && ( ( ch1 >= 'a' && ch1 <= 'z' ) + || ( ch1 >= 'A' && ch1 <= 'Z' ) ) ) + { + name = name.substring( 2 ); + } + } + } + else if( osname.toLowerCase().indexOf( "netware" ) > -1 ) + { + int colon = name.indexOf( ':' ); + if( colon != -1 ) + { + name = name.substring( colon + 1 ); + } + } + } + + name = name.replace( File.separatorChar, '/' ); + + // No absolute pathnames + // Windows (and Posix?) paths can start with "\\NetworkDrive\", + // so we loop on starting /'s. + while( name.startsWith( "/" ) ) + { + name = name.substring( 1 ); + } + + this.linkName = new StringBuffer( "" ); + this.name = new StringBuffer( name ); + + if( file.isDirectory() ) + { + this.mode = 040755; + this.linkFlag = LF_DIR; + + if( this.name.charAt( this.name.length() - 1 ) != '/' ) + { + this.name.append( "/" ); + } + } + else + { + this.mode = 0100644; + this.linkFlag = LF_NORMAL; + } + + this.size = file.length(); + this.modTime = file.lastModified() / 1000; + this.checkSum = 0; + this.devMajor = 0; + this.devMinor = 0; + } + + /** + * Construct an entry from an archive's header bytes. File is set to null. + * + * @param headerBuf The header bytes from a tar archive entry. + */ + public TarEntry( byte[] headerBuf ) + { + this(); + this.parseTarHeader( headerBuf ); + } + + /** + * The entry's file reference + */ + + /** + * Construct an empty entry and prepares the header values. + */ + private TarEntry() + { + this.magic = new StringBuffer( TMAGIC ); + this.name = new StringBuffer(); + this.linkName = new StringBuffer(); + + String user = System.getProperty( "user.name", "" ); + + if( user.length() > 31 ) + { + user = user.substring( 0, 31 ); + } + + this.userId = 0; + this.groupId = 0; + this.userName = new StringBuffer( user ); + this.groupName = new StringBuffer( "" ); + this.file = null; + } + + /** + * Set this entry's group id. + * + * @param groupId This entry's new group id. + */ + public void setGroupId( int groupId ) + { + this.groupId = groupId; + } + + /** + * Set this entry's group name. + * + * @param groupName This entry's new group name. + */ + public void setGroupName( String groupName ) + { + this.groupName = new StringBuffer( groupName ); + } + + /** + * Convenience method to set this entry's group and user ids. + * + * @param userId This entry's new user id. + * @param groupId This entry's new group id. + */ + public void setIds( int userId, int groupId ) + { + this.setUserId( userId ); + this.setGroupId( groupId ); + } + + /** + * Set this entry's modification time. The parameter passed to this method + * is in "Java time". + * + * @param time This entry's new modification time. + */ + public void setModTime( long time ) + { + this.modTime = time / 1000; + } + + /** + * Set this entry's modification time. + * + * @param time This entry's new modification time. + */ + public void setModTime( Date time ) + { + this.modTime = time.getTime() / 1000; + } + + /** + * Set the mode for this entry + * + * @param mode The new Mode value + */ + public void setMode( int mode ) + { + this.mode = mode; + } + + /** + * Set this entry's name. + * + * @param name This entry's new name. + */ + public void setName( String name ) + { + this.name = new StringBuffer( name ); + } + + /** + * Convenience method to set this entry's group and user names. + * + * @param userName This entry's new user name. + * @param groupName This entry's new group name. + */ + public void setNames( String userName, String groupName ) + { + this.setUserName( userName ); + this.setGroupName( groupName ); + } + + /** + * Set this entry's file size. + * + * @param size This entry's new file size. + */ + public void setSize( long size ) + { + this.size = size; + } + + /** + * Set this entry's user id. + * + * @param userId This entry's new user id. + */ + public void setUserId( int userId ) + { + this.userId = userId; + } + + /** + * Set this entry's user name. + * + * @param userName This entry's new user name. + */ + public void setUserName( String userName ) + { + this.userName = new StringBuffer( userName ); + } + + /** + * If this entry represents a file, and the file is a directory, return an + * array of TarEntries for this entry's children. + * + * @return An array of TarEntry's for this entry's children. + */ + public TarEntry[] getDirectoryEntries() + { + if( this.file == null || !this.file.isDirectory() ) + { + return new TarEntry[0]; + } + + String[] list = this.file.list(); + TarEntry[] result = new TarEntry[list.length]; + + for( int i = 0; i < list.length; ++i ) + { + result[i] = new TarEntry( new File( this.file, list[i] ) ); + } + + return result; + } + + /** + * Get this entry's file. + * + * @return This entry's file. + */ + public File getFile() + { + return this.file; + } + + /** + * Get this entry's group id. + * + * @return This entry's group id. + */ + public int getGroupId() + { + return this.groupId; + } + + /** + * Get this entry's group name. + * + * @return This entry's group name. + */ + public String getGroupName() + { + return this.groupName.toString(); + } + + /** + * Set this entry's modification time. + * + * @return The ModTime value + */ + public Date getModTime() + { + return new Date( this.modTime * 1000 ); + } + + /** + * Get this entry's mode. + * + * @return This entry's mode. + */ + public int getMode() + { + return this.mode; + } + + /** + * Get this entry's name. + * + * @return This entry's name. + */ + public String getName() + { + return this.name.toString(); + } + + /** + * Get this entry's file size. + * + * @return This entry's file size. + */ + public long getSize() + { + return this.size; + } + + + /** + * Get this entry's user id. + * + * @return This entry's user id. + */ + public int getUserId() + { + return this.userId; + } + + /** + * Get this entry's user name. + * + * @return This entry's user name. + */ + public String getUserName() + { + return this.userName.toString(); + } + + /** + * Determine if the given entry is a descendant of this entry. Descendancy + * is determined by the name of the descendant starting with this entry's + * name. + * + * @param desc Entry to be checked as a descendent of this. + * @return True if entry is a descendant of this. + */ + public boolean isDescendent( TarEntry desc ) + { + return desc.getName().startsWith( this.getName() ); + } + + /** + * Return whether or not this entry represents a directory. + * + * @return True if this entry is a directory. + */ + public boolean isDirectory() + { + if( this.file != null ) + { + return this.file.isDirectory(); + } + + if( this.linkFlag == LF_DIR ) + { + return true; + } + + if( this.getName().endsWith( "/" ) ) + { + return true; + } + + return false; + } + + + /** + * Indicate if this entry is a GNU long name block + * + * @return true if this is a long name extension provided by GNU tar + */ + public boolean isGNULongNameEntry() + { + return linkFlag == LF_GNUTYPE_LONGNAME && + name.toString().equals( GNU_LONGLINK ); + } + + /** + * Determine if the two entries are equal. Equality is determined by the + * header names being equal. + * + * @param it Description of Parameter + * @return it Entry to be checked for equality. + * @return True if the entries are equal. + */ + public boolean equals( TarEntry it ) + { + return this.getName().equals( it.getName() ); + } + + /** + * Parse an entry's header information from a header buffer. + * + * @param header The tar entry header buffer to get information from. + */ + public void parseTarHeader( byte[] header ) + { + int offset = 0; + + this.name = TarUtils.parseName( header, offset, NAMELEN ); + offset += NAMELEN; + this.mode = ( int )TarUtils.parseOctal( header, offset, MODELEN ); + offset += MODELEN; + this.userId = ( int )TarUtils.parseOctal( header, offset, UIDLEN ); + offset += UIDLEN; + this.groupId = ( int )TarUtils.parseOctal( header, offset, GIDLEN ); + offset += GIDLEN; + this.size = TarUtils.parseOctal( header, offset, SIZELEN ); + offset += SIZELEN; + this.modTime = TarUtils.parseOctal( header, offset, MODTIMELEN ); + offset += MODTIMELEN; + this.checkSum = ( int )TarUtils.parseOctal( header, offset, CHKSUMLEN ); + offset += CHKSUMLEN; + this.linkFlag = header[offset++]; + this.linkName = TarUtils.parseName( header, offset, NAMELEN ); + offset += NAMELEN; + this.magic = TarUtils.parseName( header, offset, MAGICLEN ); + offset += MAGICLEN; + this.userName = TarUtils.parseName( header, offset, UNAMELEN ); + offset += UNAMELEN; + this.groupName = TarUtils.parseName( header, offset, GNAMELEN ); + offset += GNAMELEN; + this.devMajor = ( int )TarUtils.parseOctal( header, offset, DEVLEN ); + offset += DEVLEN; + this.devMinor = ( int )TarUtils.parseOctal( header, offset, DEVLEN ); + } + + /** + * Write an entry's header information to a header buffer. + * + * @param outbuf The tar entry header buffer to fill in. + */ + public void writeEntryHeader( byte[] outbuf ) + { + int offset = 0; + + offset = TarUtils.getNameBytes( this.name, outbuf, offset, NAMELEN ); + offset = TarUtils.getOctalBytes( this.mode, outbuf, offset, MODELEN ); + offset = TarUtils.getOctalBytes( this.userId, outbuf, offset, UIDLEN ); + offset = TarUtils.getOctalBytes( this.groupId, outbuf, offset, GIDLEN ); + offset = TarUtils.getLongOctalBytes( this.size, outbuf, offset, SIZELEN ); + offset = TarUtils.getLongOctalBytes( this.modTime, outbuf, offset, MODTIMELEN ); + + int csOffset = offset; + + for( int c = 0; c < CHKSUMLEN; ++c ) + { + outbuf[offset++] = ( byte )' '; + } + + outbuf[offset++] = this.linkFlag; + offset = TarUtils.getNameBytes( this.linkName, outbuf, offset, NAMELEN ); + offset = TarUtils.getNameBytes( this.magic, outbuf, offset, MAGICLEN ); + offset = TarUtils.getNameBytes( this.userName, outbuf, offset, UNAMELEN ); + offset = TarUtils.getNameBytes( this.groupName, outbuf, offset, GNAMELEN ); + offset = TarUtils.getOctalBytes( this.devMajor, outbuf, offset, DEVLEN ); + offset = TarUtils.getOctalBytes( this.devMinor, outbuf, offset, DEVLEN ); + + while( offset < outbuf.length ) + { + outbuf[offset++] = 0; + } + + long checkSum = TarUtils.computeCheckSum( outbuf ); + + TarUtils.getCheckSumOctalBytes( checkSum, outbuf, csOffset, CHKSUMLEN ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarInputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarInputStream.java new file mode 100644 index 000000000..5e340e6ae --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarInputStream.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The TarInputStream reads a UNIX tar archive as an InputStream. methods are + * provided to position at each successive entry in the archive, and the read + * each entry as a normal input stream using read(). + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ +public class TarInputStream extends FilterInputStream +{ + protected TarBuffer buffer; + protected TarEntry currEntry; + + protected boolean debug; + protected int entryOffset; + protected int entrySize; + protected boolean hasHitEOF; + protected byte[] oneBuf; + protected byte[] readBuf; + + public TarInputStream( InputStream is ) + { + this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarInputStream( InputStream is, int blockSize ) + { + this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarInputStream( InputStream is, int blockSize, int recordSize ) + { + super( is ); + + this.buffer = new TarBuffer( is, blockSize, recordSize ); + this.readBuf = null; + this.oneBuf = new byte[1]; + this.debug = false; + this.hasHitEOF = false; + } + + /** + * Sets the debugging flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + this.buffer.setDebug( debug ); + } + + /** + * Get the next entry in this tar archive. This will skip over any remaining + * data in the current entry, if there is one, and place the input stream at + * the header of the next entry, and read the header and instantiate a new + * TarEntry from the header bytes and return that entry. If there are no + * more entries in the archive, null will be returned to indicate that the + * end of the archive has been reached. + * + * @return The next TarEntry in the archive, or null. + * @exception IOException Description of Exception + */ + public TarEntry getNextEntry() + throws IOException + { + if( this.hasHitEOF ) + { + return null; + } + + if( this.currEntry != null ) + { + int numToSkip = this.entrySize - this.entryOffset; + + if( this.debug ) + { + System.err.println( "TarInputStream: SKIP currENTRY '" + + this.currEntry.getName() + "' SZ " + + this.entrySize + " OFF " + + this.entryOffset + " skipping " + + numToSkip + " bytes" ); + } + + if( numToSkip > 0 ) + { + this.skip( numToSkip ); + } + + this.readBuf = null; + } + + byte[] headerBuf = this.buffer.readRecord(); + + if( headerBuf == null ) + { + if( this.debug ) + { + System.err.println( "READ NULL RECORD" ); + } + this.hasHitEOF = true; + } + else if( this.buffer.isEOFRecord( headerBuf ) ) + { + if( this.debug ) + { + System.err.println( "READ EOF RECORD" ); + } + this.hasHitEOF = true; + } + + if( this.hasHitEOF ) + { + this.currEntry = null; + } + else + { + this.currEntry = new TarEntry( headerBuf ); + + if( !( headerBuf[257] == 'u' && headerBuf[258] == 's' + && headerBuf[259] == 't' && headerBuf[260] == 'a' + && headerBuf[261] == 'r' ) ) + { + this.entrySize = 0; + this.entryOffset = 0; + this.currEntry = null; + + throw new IOException( "bad header in block " + + this.buffer.getCurrentBlockNum() + + " record " + + this.buffer.getCurrentRecordNum() + + ", " + + "header magic is not 'ustar', but '" + + headerBuf[257] + + headerBuf[258] + + headerBuf[259] + + headerBuf[260] + + headerBuf[261] + + "', or (dec) " + + ( ( int )headerBuf[257] ) + + ", " + + ( ( int )headerBuf[258] ) + + ", " + + ( ( int )headerBuf[259] ) + + ", " + + ( ( int )headerBuf[260] ) + + ", " + + ( ( int )headerBuf[261] ) ); + } + + if( this.debug ) + { + System.err.println( "TarInputStream: SET CURRENTRY '" + + this.currEntry.getName() + + "' size = " + + this.currEntry.getSize() ); + } + + this.entryOffset = 0; + + // REVIEW How do we resolve this discrepancy?! + this.entrySize = ( int )this.currEntry.getSize(); + } + + if( this.currEntry != null && this.currEntry.isGNULongNameEntry() ) + { + // read in the name + StringBuffer longName = new StringBuffer(); + byte[] buffer = new byte[256]; + int length = 0; + while( ( length = read( buffer ) ) >= 0 ) + { + longName.append( new String( buffer, 0, length ) ); + } + getNextEntry(); + this.currEntry.setName( longName.toString() ); + } + + return this.currEntry; + } + + /** + * Get the record size being used by this stream's TarBuffer. + * + * @return The TarBuffer record size. + */ + public int getRecordSize() + { + return this.buffer.getRecordSize(); + } + + /** + * Get the available data that can be read from the current entry in the + * archive. This does not indicate how much data is left in the entire + * archive, only in the current entry. This value is determined from the + * entry's size header field and the amount of data already read from the + * current entry. + * + * @return The number of available bytes for the current entry. + * @exception IOException Description of Exception + */ + public int available() + throws IOException + { + return this.entrySize - this.entryOffset; + } + + /** + * Closes this stream. Calls the TarBuffer's close() method. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + this.buffer.close(); + } + + /** + * Copies the contents of the current tar archive entry directly into an + * output stream. + * + * @param out The OutputStream into which to write the entry's data. + * @exception IOException Description of Exception + */ + public void copyEntryContents( OutputStream out ) + throws IOException + { + byte[] buf = new byte[32 * 1024]; + + while( true ) + { + int numRead = this.read( buf, 0, buf.length ); + + if( numRead == -1 ) + { + break; + } + + out.write( buf, 0, numRead ); + } + } + + /** + * Since we do not support marking just yet, we do nothing. + * + * @param markLimit The limit to mark. + */ + public void mark( int markLimit ) { } + + /** + * Since we do not support marking just yet, we return false. + * + * @return False. + */ + public boolean markSupported() + { + return false; + } + + /** + * Reads a byte from the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @return The byte read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read() + throws IOException + { + int num = this.read( this.oneBuf, 0, 1 ); + + if( num == -1 ) + { + return num; + } + else + { + return ( int )this.oneBuf[0]; + } + } + + /** + * Reads bytes from the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @param buf The buffer into which to place bytes read. + * @return The number of bytes read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read( byte[] buf ) + throws IOException + { + return this.read( buf, 0, buf.length ); + } + + /** + * Reads bytes from the current tar archive entry. This method is aware of + * the boundaries of the current entry in the archive and will deal with + * them as if they were this stream's start and EOF. + * + * @param buf The buffer into which to place bytes read. + * @param offset The offset at which to place bytes read. + * @param numToRead The number of bytes to read. + * @return The number of bytes read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read( byte[] buf, int offset, int numToRead ) + throws IOException + { + int totalRead = 0; + + if( this.entryOffset >= this.entrySize ) + { + return -1; + } + + if( ( numToRead + this.entryOffset ) > this.entrySize ) + { + numToRead = ( this.entrySize - this.entryOffset ); + } + + if( this.readBuf != null ) + { + int sz = ( numToRead > this.readBuf.length ) ? this.readBuf.length + : numToRead; + + System.arraycopy( this.readBuf, 0, buf, offset, sz ); + + if( sz >= this.readBuf.length ) + { + this.readBuf = null; + } + else + { + int newLen = this.readBuf.length - sz; + byte[] newBuf = new byte[newLen]; + + System.arraycopy( this.readBuf, sz, newBuf, 0, newLen ); + + this.readBuf = newBuf; + } + + totalRead += sz; + numToRead -= sz; + offset += sz; + } + + while( numToRead > 0 ) + { + byte[] rec = this.buffer.readRecord(); + + if( rec == null ) + { + // Unexpected EOF! + throw new IOException( "unexpected EOF with " + numToRead + + " bytes unread" ); + } + + int sz = numToRead; + int recLen = rec.length; + + if( recLen > sz ) + { + System.arraycopy( rec, 0, buf, offset, sz ); + + this.readBuf = new byte[recLen - sz]; + + System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz ); + } + else + { + sz = recLen; + + System.arraycopy( rec, 0, buf, offset, recLen ); + } + + totalRead += sz; + numToRead -= sz; + offset += sz; + } + + this.entryOffset += totalRead; + + return totalRead; + } + + /** + * Since we do not support marking just yet, we do nothing. + */ + public void reset() { } + + /** + * Skip bytes in the input buffer. This skips bytes in the current entry's + * data, not the entire archive, and will stop at the end of the current + * entry's data if the number to skip extends beyond that point. + * + * @param numToSkip The number of bytes to skip. + * @exception IOException Description of Exception + */ + public void skip( int numToSkip ) + throws IOException + { + + // REVIEW + // This is horribly inefficient, but it ensures that we + // properly skip over bytes via the TarBuffer... + // + byte[] skipBuf = new byte[8 * 1024]; + + for( int num = numToSkip; num > 0; ) + { + int numRead = this.read( skipBuf, 0, + ( num > skipBuf.length ? skipBuf.length + : num ) ); + + if( numRead == -1 ) + { + break; + } + + num -= numRead; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarOutputStream.java new file mode 100644 index 000000000..92914c329 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarOutputStream.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * The TarOutputStream writes a UNIX tar archive as an OutputStream. Methods are + * provided to put entries, and then write their contents by writing to this + * stream using write(). + * + * @author Timothy Gerard Endres time@ice.com + */ +public class TarOutputStream extends FilterOutputStream +{ + public final static int LONGFILE_ERROR = 0; + public final static int LONGFILE_TRUNCATE = 1; + public final static int LONGFILE_GNU = 2; + protected int longFileMode = LONGFILE_ERROR; + protected byte[] assemBuf; + protected int assemLen; + protected TarBuffer buffer; + protected int currBytes; + protected int currSize; + + protected boolean debug; + protected byte[] oneBuf; + protected byte[] recordBuf; + + public TarOutputStream( OutputStream os ) + { + this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarOutputStream( OutputStream os, int blockSize ) + { + this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarOutputStream( OutputStream os, int blockSize, int recordSize ) + { + super( os ); + + this.buffer = new TarBuffer( os, blockSize, recordSize ); + this.debug = false; + this.assemLen = 0; + this.assemBuf = new byte[recordSize]; + this.recordBuf = new byte[recordSize]; + this.oneBuf = new byte[1]; + } + + /** + * Sets the debugging flag in this stream's TarBuffer. + * + * @param debug The new BufferDebug value + */ + public void setBufferDebug( boolean debug ) + { + this.buffer.setDebug( debug ); + } + + + /** + * Sets the debugging flag. + * + * @param debugF True to turn on debugging. + */ + public void setDebug( boolean debugF ) + { + this.debug = debugF; + } + + public void setLongFileMode( int longFileMode ) + { + this.longFileMode = longFileMode; + } + + /** + * Get the record size being used by this stream's TarBuffer. + * + * @return The TarBuffer record size. + */ + public int getRecordSize() + { + return this.buffer.getRecordSize(); + } + + /** + * Ends the TAR archive and closes the underlying OutputStream. This means + * that finish() is called followed by calling the TarBuffer's close(). + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + this.finish(); + this.buffer.close(); + } + + /** + * Close an entry. This method MUST be called for all file entries that + * contain data. The reason is that we must buffer data written to the + * stream in order to satisfy the buffer's record based writes. Thus, there + * may be data fragments still being assembled that must be written to the + * output stream before this entry is closed and the next entry written. + * + * @exception IOException Description of Exception + */ + public void closeEntry() + throws IOException + { + if( this.assemLen > 0 ) + { + for( int i = this.assemLen; i < this.assemBuf.length; ++i ) + { + this.assemBuf[i] = 0; + } + + this.buffer.writeRecord( this.assemBuf ); + + this.currBytes += this.assemLen; + this.assemLen = 0; + } + + if( this.currBytes < this.currSize ) + { + throw new IOException( "entry closed at '" + this.currBytes + + "' before the '" + this.currSize + + "' bytes specified in the header were written" ); + } + } + + /** + * Ends the TAR archive without closing the underlying OutputStream. The + * result is that the EOF record of nulls is written. + * + * @exception IOException Description of Exception + */ + public void finish() + throws IOException + { + this.writeEOFRecord(); + } + + /** + * Put an entry on the output stream. This writes the entry's header record + * and positions the output stream for writing the contents of the entry. + * Once this method is called, the stream is ready for calls to write() to + * write the entry's contents. Once the contents are written, closeEntry() + * MUST be called to ensure that all buffered data is completely + * written to the output stream. + * + * @param entry The TarEntry to be written to the archive. + * @exception IOException Description of Exception + */ + public void putNextEntry( TarEntry entry ) + throws IOException + { + if( entry.getName().length() >= TarConstants.NAMELEN ) + { + + if( longFileMode == LONGFILE_GNU ) + { + // create a TarEntry for the LongLink, the contents + // of which are the entry's name + TarEntry longLinkEntry = new TarEntry( TarConstants.GNU_LONGLINK, + TarConstants.LF_GNUTYPE_LONGNAME ); + + longLinkEntry.setSize( entry.getName().length() + 1 ); + putNextEntry( longLinkEntry ); + write( entry.getName().getBytes() ); + write( 0 ); + closeEntry(); + } + else if( longFileMode != LONGFILE_TRUNCATE ) + { + throw new RuntimeException( "file name '" + entry.getName() + + "' is too long ( > " + + TarConstants.NAMELEN + " bytes)" ); + } + } + + entry.writeEntryHeader( this.recordBuf ); + this.buffer.writeRecord( this.recordBuf ); + + this.currBytes = 0; + + if( entry.isDirectory() ) + { + this.currSize = 0; + } + else + { + this.currSize = ( int )entry.getSize(); + } + } + + /** + * Writes a byte to the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @param b The byte written. + * @exception IOException Description of Exception + */ + public void write( int b ) + throws IOException + { + this.oneBuf[0] = ( byte )b; + + this.write( this.oneBuf, 0, 1 ); + } + + /** + * Writes bytes to the current tar archive entry. This method simply calls + * write( byte[], int, int ). + * + * @param wBuf The buffer to write to the archive. + * @exception IOException Description of Exception + */ + public void write( byte[] wBuf ) + throws IOException + { + this.write( wBuf, 0, wBuf.length ); + } + + /** + * Writes bytes to the current tar archive entry. This method is aware of + * the current entry and will throw an exception if you attempt to write + * bytes past the length specified for the current entry. The method is also + * (painfully) aware of the record buffering required by TarBuffer, and + * manages buffers that are not a multiple of recordsize in length, + * including assembling records from small buffers. + * + * @param wBuf The buffer to write to the archive. + * @param wOffset The offset in the buffer from which to get bytes. + * @param numToWrite The number of bytes to write. + * @exception IOException Description of Exception + */ + public void write( byte[] wBuf, int wOffset, int numToWrite ) + throws IOException + { + if( ( this.currBytes + numToWrite ) > this.currSize ) + { + throw new IOException( "request to write '" + numToWrite + + "' bytes exceeds size in header of '" + + this.currSize + "' bytes" ); + // + // We have to deal with assembly!!! + // The programmer can be writing little 32 byte chunks for all + // we know, and we must assemble complete records for writing. + // REVIEW Maybe this should be in TarBuffer? Could that help to + // eliminate some of the buffer copying. + // + } + + if( this.assemLen > 0 ) + { + if( ( this.assemLen + numToWrite ) >= this.recordBuf.length ) + { + int aLen = this.recordBuf.length - this.assemLen; + + System.arraycopy( this.assemBuf, 0, this.recordBuf, 0, + this.assemLen ); + System.arraycopy( wBuf, wOffset, this.recordBuf, + this.assemLen, aLen ); + this.buffer.writeRecord( this.recordBuf ); + + this.currBytes += this.recordBuf.length; + wOffset += aLen; + numToWrite -= aLen; + this.assemLen = 0; + } + else + { + System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen, + numToWrite ); + + wOffset += numToWrite; + this.assemLen += numToWrite; + numToWrite -= numToWrite; + } + } + + // + // When we get here we have EITHER: + // o An empty "assemble" buffer. + // o No bytes to write (numToWrite == 0) + // + while( numToWrite > 0 ) + { + if( numToWrite < this.recordBuf.length ) + { + System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen, + numToWrite ); + + this.assemLen += numToWrite; + + break; + } + + this.buffer.writeRecord( wBuf, wOffset ); + + int num = this.recordBuf.length; + + this.currBytes += num; + numToWrite -= num; + wOffset += num; + } + } + + /** + * Write an EOF (end of archive) record to the tar archive. An EOF record + * consists of a record of all zeros. + * + * @exception IOException Description of Exception + */ + private void writeEOFRecord() + throws IOException + { + for( int i = 0; i < this.recordBuf.length; ++i ) + { + this.recordBuf[i] = 0; + } + + this.buffer.writeRecord( this.recordBuf ); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarUtils.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarUtils.java new file mode 100644 index 000000000..40bf84d59 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarUtils.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; + +/** + * This class provides static utility methods to work with byte streams. + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ +public class TarUtils +{ + + /** + * Parse the checksum octal integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The integer value of the entry's checksum. + */ + public static int getCheckSumOctalBytes( long value, byte[] buf, int offset, int length ) + { + getOctalBytes( value, buf, offset, length ); + + buf[offset + length - 1] = ( byte )' '; + buf[offset + length - 2] = 0; + + return offset + length; + } + + /** + * Parse an octal long integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The long value of the octal bytes. + */ + public static int getLongOctalBytes( long value, byte[] buf, int offset, int length ) + { + byte[] temp = new byte[length + 1]; + + getOctalBytes( value, temp, 0, length + 1 ); + System.arraycopy( temp, 0, buf, offset, length ); + + return offset + length; + } + + /** + * Determine the number of bytes in an entry name. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param name Description of Parameter + * @param buf Description of Parameter + * @return The number of bytes in a header's entry name. + */ + public static int getNameBytes( StringBuffer name, byte[] buf, int offset, int length ) + { + int i; + + for( i = 0; i < length && i < name.length(); ++i ) + { + buf[offset + i] = ( byte )name.charAt( i ); + } + + for( ; i < length; ++i ) + { + buf[offset + i] = 0; + } + + return offset + length; + } + + /** + * Parse an octal integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The integer value of the octal bytes. + */ + public static int getOctalBytes( long value, byte[] buf, int offset, int length ) + { + byte[] result = new byte[length]; + int idx = length - 1; + + buf[offset + idx] = 0; + --idx; + buf[offset + idx] = ( byte )' '; + --idx; + + if( value == 0 ) + { + buf[offset + idx] = ( byte )'0'; + --idx; + } + else + { + for( long val = value; idx >= 0 && val > 0; --idx ) + { + buf[offset + idx] = ( byte )( ( byte )'0' + ( byte )( val & 7 ) ); + val = val >> 3; + } + } + + for( ; idx >= 0; --idx ) + { + buf[offset + idx] = ( byte )' '; + } + + return offset + length; + } + + /** + * Compute the checksum of a tar entry header. + * + * @param buf The tar entry's header buffer. + * @return The computed checksum. + */ + public static long computeCheckSum( byte[] buf ) + { + long sum = 0; + + for( int i = 0; i < buf.length; ++i ) + { + sum += 255 & buf[i]; + } + + return sum; + } + + /** + * Parse an entry name from a header buffer. + * + * @param header The header buffer from which to parse. + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @return The header's entry name. + */ + public static StringBuffer parseName( byte[] header, int offset, int length ) + { + StringBuffer result = new StringBuffer( length ); + int end = offset + length; + + for( int i = offset; i < end; ++i ) + { + if( header[i] == 0 ) + { + break; + } + + result.append( ( char )header[i] ); + } + + return result; + } + + /** + * Parse an octal string from a header buffer. This is used for the file + * permission mode value. + * + * @param header The header buffer from which to parse. + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @return The long value of the octal string. + */ + public static long parseOctal( byte[] header, int offset, int length ) + { + long result = 0; + boolean stillPadding = true; + int end = offset + length; + + for( int i = offset; i < end; ++i ) + { + if( header[i] == 0 ) + { + break; + } + + if( header[i] == ( byte )' ' || header[i] == '0' ) + { + if( stillPadding ) + { + continue; + } + + if( header[i] == ( byte )' ' ) + { + break; + } + } + + stillPadding = false; + result = ( result << 3 ) + ( header[i] - '0' ); + } + + return result; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/AsiExtraField.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/AsiExtraField.java new file mode 100644 index 000000000..e2e3c9122 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/AsiExtraField.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.zip.CRC32; +import java.util.zip.ZipException; + +/** + * Adds Unix file permission and UID/GID fields as well as symbolic link + * handling.

                  + * + * This class uses the ASi extra field in the format:

                  + *         Value         Size            Description
                  + *         -----         ----            -----------
                  + * (Unix3) 0x756e        Short           tag for this extra block type
                  + *         TSize         Short           total data size for this block
                  + *         CRC           Long            CRC-32 of the remaining data
                  + *         Mode          Short           file permissions
                  + *         SizDev        Long            symlink'd size OR major/minor dev num
                  + *         UID           Short           user ID
                  + *         GID           Short           group ID
                  + *         (var.)        variable        symbolic link filename
                  + * 
                  taken from appnote.iz (Info-ZIP note, 981119) found at + * ftp://ftp.uu.net/pub/archiving/zip/doc/

                  + * + * Short is two bytes and Long is four bytes in big endian byte and word order, + * device numbers are currently not supported.

                  + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable +{ + + private final static ZipShort HEADER_ID = new ZipShort( 0x756E ); + + /** + * Standard Unix stat(2) file mode. + * + * @since 1.1 + */ + private int mode = 0; + /** + * User ID. + * + * @since 1.1 + */ + private int uid = 0; + /** + * Group ID. + * + * @since 1.1 + */ + private int gid = 0; + /** + * File this entry points to, if it is a symbolic link.

                  + * + * empty string - if entry is not a symbolic link.

                  + * + * @since 1.1 + */ + private String link = ""; + /** + * Is this an entry for a directory? + * + * @since 1.1 + */ + private boolean dirFlag = false; + + /** + * Instance used to calculate checksums. + * + * @since 1.1 + */ + private CRC32 crc = new CRC32(); + + public AsiExtraField() { } + + /** + * Indicate whether this entry is a directory. + * + * @param dirFlag The new Directory value + * @since 1.1 + */ + public void setDirectory( boolean dirFlag ) + { + this.dirFlag = dirFlag; + mode = getMode( mode ); + } + + /** + * Set the group id. + * + * @param gid The new GroupId value + * @since 1.1 + */ + public void setGroupId( int gid ) + { + this.gid = gid; + } + + /** + * Indicate that this entry is a symbolic link to the given filename. + * + * @param name Name of the file this entry links to, empty String if it is + * not a symbolic link. + * @since 1.1 + */ + public void setLinkedFile( String name ) + { + link = name; + mode = getMode( mode ); + } + + /** + * File mode of this file. + * + * @param mode The new Mode value + * @since 1.1 + */ + public void setMode( int mode ) + { + this.mode = getMode( mode ); + } + + /** + * Set the user id. + * + * @param uid The new UserId value + * @since 1.1 + */ + public void setUserId( int uid ) + { + this.uid = uid; + } + + /** + * Delegate to local file data. + * + * @return The CentralDirectoryData value + * @since 1.1 + */ + public byte[] getCentralDirectoryData() + { + return getLocalFileDataData(); + } + + /** + * Delegate to local file data. + * + * @return The CentralDirectoryLength value + * @since 1.1 + */ + public ZipShort getCentralDirectoryLength() + { + return getLocalFileDataLength(); + } + + /** + * Get the group id. + * + * @return The GroupId value + * @since 1.1 + */ + public int getGroupId() + { + return gid; + } + + /** + * The Header-ID. + * + * @return The HeaderId value + * @since 1.1 + */ + public ZipShort getHeaderId() + { + return HEADER_ID; + } + + /** + * Name of linked file + * + * @return name of the file this entry links to if it is a symbolic link, + * the empty string otherwise. + * @since 1.1 + */ + public String getLinkedFile() + { + return link; + } + + /** + * The actual data to put into local file data - without Header-ID or length + * specifier. + * + * @return The LocalFileDataData value + * @since 1.1 + */ + public byte[] getLocalFileDataData() + { + // CRC will be added later + byte[] data = new byte[getLocalFileDataLength().getValue() - 4]; + System.arraycopy( ( new ZipShort( getMode() ) ).getBytes(), 0, data, 0, 2 ); + + byte[] linkArray = getLinkedFile().getBytes(); + System.arraycopy( ( new ZipLong( linkArray.length ) ).getBytes(), + 0, data, 2, 4 ); + + System.arraycopy( ( new ZipShort( getUserId() ) ).getBytes(), + 0, data, 6, 2 ); + System.arraycopy( ( new ZipShort( getGroupId() ) ).getBytes(), + 0, data, 8, 2 ); + + System.arraycopy( linkArray, 0, data, 10, linkArray.length ); + + crc.reset(); + crc.update( data ); + long checksum = crc.getValue(); + + byte[] result = new byte[data.length + 4]; + System.arraycopy( ( new ZipLong( checksum ) ).getBytes(), 0, result, 0, 4 ); + System.arraycopy( data, 0, result, 4, data.length ); + return result; + } + + /** + * Length of the extra field in the local file data - without Header-ID or + * length specifier. + * + * @return The LocalFileDataLength value + * @since 1.1 + */ + public ZipShort getLocalFileDataLength() + { + return new ZipShort( 4// CRC + + 2// Mode + + 4// SizDev + + 2// UID + + 2// GID + + getLinkedFile().getBytes().length ); + } + + /** + * File mode of this file. + * + * @return The Mode value + * @since 1.1 + */ + public int getMode() + { + return mode; + } + + /** + * Get the user id. + * + * @return The UserId value + * @since 1.1 + */ + public int getUserId() + { + return uid; + } + + /** + * Is this entry a directory? + * + * @return The Directory value + * @since 1.1 + */ + public boolean isDirectory() + { + return dirFlag && !isLink(); + } + + /** + * Is this entry a symbolic link? + * + * @return The Link value + * @since 1.1 + */ + public boolean isLink() + { + return getLinkedFile().length() != 0; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param data Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public void parseFromLocalFileData( byte[] data, int offset, int length ) + throws ZipException + { + + long givenChecksum = ( new ZipLong( data, offset ) ).getValue(); + byte[] tmp = new byte[length - 4]; + System.arraycopy( data, offset + 4, tmp, 0, length - 4 ); + crc.reset(); + crc.update( tmp ); + long realChecksum = crc.getValue(); + if( givenChecksum != realChecksum ) + { + throw new ZipException( "bad CRC checksum " + + Long.toHexString( givenChecksum ) + + " instead of " + + Long.toHexString( realChecksum ) ); + } + + int newMode = ( new ZipShort( tmp, 0 ) ).getValue(); + byte[] linkArray = new byte[( int )( new ZipLong( tmp, 2 ) ).getValue()]; + uid = ( new ZipShort( tmp, 6 ) ).getValue(); + gid = ( new ZipShort( tmp, 8 ) ).getValue(); + + if( linkArray.length == 0 ) + { + link = ""; + } + else + { + System.arraycopy( tmp, 10, linkArray, 0, linkArray.length ); + link = new String( linkArray ); + } + setDirectory( ( newMode & DIR_FLAG ) != 0 ); + setMode( newMode ); + } + + /** + * Get the file mode for given permissions with the correct file type. + * + * @param mode Description of Parameter + * @return The Mode value + * @since 1.1 + */ + protected int getMode( int mode ) + { + int type = FILE_FLAG; + if( isLink() ) + { + type = LINK_FLAG; + } + else if( isDirectory() ) + { + type = DIR_FLAG; + } + return type | ( mode & PERM_MASK ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ExtraFieldUtils.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ExtraFieldUtils.java new file mode 100644 index 000000000..0b97ce6a7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ExtraFieldUtils.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.ZipException; + +/** + * ZipExtraField related methods + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ExtraFieldUtils +{ + + /** + * Static registry of known extra fields. + * + * @since 1.1 + */ + private static Hashtable implementations; + + static + { + implementations = new Hashtable(); + register( AsiExtraField.class ); + } + + /** + * Create an instance of the approriate ExtraField, falls back to {@link + * UnrecognizedExtraField UnrecognizedExtraField}. + * + * @param headerId Description of Parameter + * @return Description of the Returned Value + * @exception InstantiationException Description of Exception + * @exception IllegalAccessException Description of Exception + * @since 1.1 + */ + public static ZipExtraField createExtraField( ZipShort headerId ) + throws InstantiationException, IllegalAccessException + { + Class c = ( Class )implementations.get( headerId ); + if( c != null ) + { + return ( ZipExtraField )c.newInstance(); + } + UnrecognizedExtraField u = new UnrecognizedExtraField(); + u.setHeaderId( headerId ); + return u; + } + + /** + * Merges the central directory fields of the given ZipExtraFields. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public static byte[] mergeCentralDirectoryData( ZipExtraField[] data ) + { + int sum = 4 * data.length; + for( int i = 0; i < data.length; i++ ) + { + sum += data[i].getCentralDirectoryLength().getValue(); + } + byte[] result = new byte[sum]; + int start = 0; + for( int i = 0; i < data.length; i++ ) + { + System.arraycopy( data[i].getHeaderId().getBytes(), + 0, result, start, 2 ); + System.arraycopy( data[i].getCentralDirectoryLength().getBytes(), + 0, result, start + 2, 2 ); + byte[] local = data[i].getCentralDirectoryData(); + System.arraycopy( local, 0, result, start + 4, local.length ); + start += ( local.length + 4 ); + } + return result; + } + + /** + * Merges the local file data fields of the given ZipExtraFields. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public static byte[] mergeLocalFileDataData( ZipExtraField[] data ) + { + int sum = 4 * data.length; + for( int i = 0; i < data.length; i++ ) + { + sum += data[i].getLocalFileDataLength().getValue(); + } + byte[] result = new byte[sum]; + int start = 0; + for( int i = 0; i < data.length; i++ ) + { + System.arraycopy( data[i].getHeaderId().getBytes(), + 0, result, start, 2 ); + System.arraycopy( data[i].getLocalFileDataLength().getBytes(), + 0, result, start + 2, 2 ); + byte[] local = data[i].getLocalFileDataData(); + System.arraycopy( local, 0, result, start + 4, local.length ); + start += ( local.length + 4 ); + } + return result; + } + + /** + * Split the array into ExtraFields and populate them with the give data. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @exception ZipException Description of Exception + * @since 1.1 + */ + public static ZipExtraField[] parse( byte[] data ) + throws ZipException + { + Vector v = new Vector(); + int start = 0; + while( start <= data.length - 4 ) + { + ZipShort headerId = new ZipShort( data, start ); + int length = ( new ZipShort( data, start + 2 ) ).getValue(); + if( start + 4 + length > data.length ) + { + throw new ZipException( "data starting at " + start + " is in unknown format" ); + } + try + { + ZipExtraField ze = createExtraField( headerId ); + ze.parseFromLocalFileData( data, start + 4, length ); + v.addElement( ze ); + } + catch( InstantiationException ie ) + { + throw new ZipException( ie.getMessage() ); + } + catch( IllegalAccessException iae ) + { + throw new ZipException( iae.getMessage() ); + } + start += ( length + 4 ); + } + if( start != data.length ) + {// array not exhausted + throw new ZipException( "data starting at " + start + " is in unknown format" ); + } + + ZipExtraField[] result = new ZipExtraField[v.size()]; + v.copyInto( result ); + return result; + } + + /** + * Register a ZipExtraField implementation.

                  + * + * The given class must have a no-arg constructor and implement the {@link + * ZipExtraField ZipExtraField interface}.

                  + * + * @param c Description of Parameter + * @since 1.1 + */ + public static void register( Class c ) + { + try + { + ZipExtraField ze = ( ZipExtraField )c.newInstance(); + implementations.put( ze.getHeaderId(), c ); + } + catch( ClassCastException cc ) + { + throw new RuntimeException( c + + " doesn\'t implement ZipExtraField" ); + } + catch( InstantiationException ie ) + { + throw new RuntimeException( c + " is not a concrete class" ); + } + catch( IllegalAccessException ie ) + { + throw new RuntimeException( c + + "\'s no-arg constructor is not public" ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/UnixStat.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/UnixStat.java new file mode 100644 index 000000000..e238ed32e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/UnixStat.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Constants from stat.h on Unix systems. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface UnixStat +{ + + /** + * Bits used for permissions (and sticky bit) + * + * @since 1.1 + */ + int PERM_MASK = 07777; + /** + * Indicates symbolic links. + * + * @since 1.1 + */ + int LINK_FLAG = 0120000; + /** + * Indicates plain files. + * + * @since 1.1 + */ + int FILE_FLAG = 0100000; + /** + * Indicates directories. + * + * @since 1.1 + */ + int DIR_FLAG = 040000; + + // ---------------------------------------------------------- + // somewhat arbitrary choices that are quite common for shared + // installations + // ----------------------------------------------------------- + + /** + * Default permissions for symbolic links. + * + * @since 1.1 + */ + int DEFAULT_LINK_PERM = 0777; + /** + * Default permissions for directories. + * + * @since 1.1 + */ + int DEFAULT_DIR_PERM = 0755; + /** + * Default permissions for plain files. + * + * @since 1.1 + */ + int DEFAULT_FILE_PERM = 0644; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/UnrecognizedExtraField.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/UnrecognizedExtraField.java new file mode 100644 index 000000000..092e1c60b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/UnrecognizedExtraField.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Simple placeholder for all those extra fields we don't want to deal with.

                  + * + * Assumes local file data and central directory entries are identical - unless + * told the opposite.

                  + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class UnrecognizedExtraField implements ZipExtraField +{ + + /** + * Extra field data in central directory - without Header-ID or length + * specifier. + * + * @since 1.1 + */ + private byte[] centralData; + + /** + * The Header-ID. + * + * @since 1.1 + */ + private ZipShort headerId; + + /** + * Extra field data in local file data - without Header-ID or length + * specifier. + * + * @since 1.1 + */ + private byte[] localData; + + public void setCentralDirectoryData( byte[] data ) + { + centralData = data; + } + + public void setHeaderId( ZipShort headerId ) + { + this.headerId = headerId; + } + + public void setLocalFileDataData( byte[] data ) + { + localData = data; + } + + public byte[] getCentralDirectoryData() + { + if( centralData != null ) + { + return centralData; + } + return getLocalFileDataData(); + } + + public ZipShort getCentralDirectoryLength() + { + if( centralData != null ) + { + return new ZipShort( centralData.length ); + } + return getLocalFileDataLength(); + } + + public ZipShort getHeaderId() + { + return headerId; + } + + public byte[] getLocalFileDataData() + { + return localData; + } + + public ZipShort getLocalFileDataLength() + { + return new ZipShort( localData.length ); + } + + public void parseFromLocalFileData( byte[] data, int offset, int length ) + { + byte[] tmp = new byte[length]; + System.arraycopy( data, offset, tmp, 0, length ); + setLocalFileDataData( tmp ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipEntry.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipEntry.java new file mode 100644 index 000000000..078203ae2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipEntry.java @@ -0,0 +1,435 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Vector; +import java.util.zip.ZipException; + +/** + * Extension that adds better handling of extra fields and provides access to + * the internal and external file attributes. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipEntry extends java.util.zip.ZipEntry +{ + + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static Method setCompressedSizeMethod = null; + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static Object lockReflection = new Object(); + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static boolean triedToGetMethod = false; + + private int internalAttributes = 0; + private long externalAttributes = 0; + private Vector extraFields = new Vector(); + + /** + * Helper for JDK 1.1 <-> 1.2 incompatibility. + * + * @since 1.2 + */ + private Long compressedSize = null; + + /** + * Creates a new zip entry with the specified name. + * + * @param name Description of Parameter + * @since 1.1 + */ + public ZipEntry( String name ) + { + super( name ); + } + + /** + * Creates a new zip entry with fields taken from the specified zip entry. + * + * @param entry Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public ZipEntry( java.util.zip.ZipEntry entry ) + throws ZipException + { + /* + * REVISIT: call super(entry) instead of this stuff in Ant2, + * "copy constructor" has not been available in JDK 1.1 + */ + super( entry.getName() ); + + setComment( entry.getComment() ); + setMethod( entry.getMethod() ); + setTime( entry.getTime() ); + + long size = entry.getSize(); + if( size > 0 ) + { + setSize( size ); + } + long cSize = entry.getCompressedSize(); + if( cSize > 0 ) + { + setComprSize( cSize ); + } + long crc = entry.getCrc(); + if( crc > 0 ) + { + setCrc( crc ); + } + + byte[] extra = entry.getExtra(); + if( extra != null ) + { + setExtraFields( ExtraFieldUtils.parse( extra ) ); + } + else + { + // initializes extra data to an empty byte array + setExtra(); + } + } + + /** + * Creates a new zip entry with fields taken from the specified zip entry. + * + * @param entry Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public ZipEntry( ZipEntry entry ) + throws ZipException + { + this( ( java.util.zip.ZipEntry )entry ); + setInternalAttributes( entry.getInternalAttributes() ); + setExternalAttributes( entry.getExternalAttributes() ); + setExtraFields( entry.getExtraFields() ); + } + + /** + * Try to get a handle to the setCompressedSize method. + * + * @since 1.2 + */ + private static void checkSCS() + { + if( !triedToGetMethod ) + { + synchronized( lockReflection ) + { + triedToGetMethod = true; + try + { + setCompressedSizeMethod = + java.util.zip.ZipEntry.class.getMethod( "setCompressedSize", + new Class[]{Long.TYPE} ); + } + catch( NoSuchMethodException nse ) + { + } + } + } + } + + /** + * Are we running JDK 1.2 or higher? + * + * @return Description of the Returned Value + * @since 1.2 + */ + private static boolean haveSetCompressedSize() + { + checkSCS(); + return setCompressedSizeMethod != null; + } + + /** + * Invoke setCompressedSize via reflection. + * + * @param ze Description of Parameter + * @param size Description of Parameter + * @since 1.2 + */ + private static void performSetCompressedSize( ZipEntry ze, long size ) + { + Long[] s = {new Long( size )}; + try + { + setCompressedSizeMethod.invoke( ze, s ); + } + catch( InvocationTargetException ite ) + { + Throwable nested = ite.getTargetException(); + throw new RuntimeException( "Exception setting the compressed size " + + "of " + ze + ": " + + nested.getMessage() ); + } + catch( Throwable other ) + { + throw new RuntimeException( "Exception setting the compressed size " + + "of " + ze + ": " + + other.getMessage() ); + } + } + + /** + * Make this class work in JDK 1.1 like a 1.2 class.

                  + * + * This either stores the size for later usage or invokes setCompressedSize + * via reflection.

                  + * + * @param size The new ComprSize value + * @since 1.2 + */ + public void setComprSize( long size ) + { + if( haveSetCompressedSize() ) + { + performSetCompressedSize( this, size ); + } + else + { + compressedSize = new Long( size ); + } + } + + /** + * Sets the external file attributes. + * + * @param value The new ExternalAttributes value + * @since 1.1 + */ + public void setExternalAttributes( long value ) + { + externalAttributes = value; + } + + /** + * Throws an Exception if extra data cannot be parsed into extra fields. + * + * @param extra The new Extra value + * @exception RuntimeException Description of Exception + * @since 1.1 + */ + public void setExtra( byte[] extra ) + throws RuntimeException + { + try + { + setExtraFields( ExtraFieldUtils.parse( extra ) ); + } + catch( Exception e ) + { + throw new RuntimeException( e.getMessage() ); + } + } + + /** + * Replaces all currently attached extra fields with the new array. + * + * @param fields The new ExtraFields value + * @since 1.1 + */ + public void setExtraFields( ZipExtraField[] fields ) + { + extraFields.removeAllElements(); + for( int i = 0; i < fields.length; i++ ) + { + extraFields.addElement( fields[i] ); + } + setExtra(); + } + + /** + * Sets the internal file attributes. + * + * @param value The new InternalAttributes value + * @since 1.1 + */ + public void setInternalAttributes( int value ) + { + internalAttributes = value; + } + + /** + * Retrieves the extra data for the central directory. + * + * @return The CentralDirectoryExtra value + * @since 1.1 + */ + public byte[] getCentralDirectoryExtra() + { + return ExtraFieldUtils.mergeCentralDirectoryData( getExtraFields() ); + } + + /** + * Override to make this class work in JDK 1.1 like a 1.2 class. + * + * @return The CompressedSize value + * @since 1.2 + */ + public long getCompressedSize() + { + if( compressedSize != null ) + { + // has been set explicitly and we are running in a 1.1 VM + return compressedSize.longValue(); + } + return super.getCompressedSize(); + } + + /** + * Retrieves the external file attributes. + * + * @return The ExternalAttributes value + * @since 1.1 + */ + public long getExternalAttributes() + { + return externalAttributes; + } + + /** + * Retrieves extra fields. + * + * @return The ExtraFields value + * @since 1.1 + */ + public ZipExtraField[] getExtraFields() + { + ZipExtraField[] result = new ZipExtraField[extraFields.size()]; + extraFields.copyInto( result ); + return result; + } + + /** + * Retrieves the internal file attributes. + * + * @return The InternalAttributes value + * @since 1.1 + */ + public int getInternalAttributes() + { + return internalAttributes; + } + + /** + * Retrieves the extra data for the local file data. + * + * @return The LocalFileDataExtra value + * @since 1.1 + */ + public byte[] getLocalFileDataExtra() + { + byte[] extra = getExtra(); + return extra != null ? extra : new byte[0]; + } + + /** + * Adds an extra fields - replacing an already present extra field of the + * same type. + * + * @param ze The feature to be added to the ExtraField attribute + * @since 1.1 + */ + public void addExtraField( ZipExtraField ze ) + { + ZipShort type = ze.getHeaderId(); + boolean done = false; + for( int i = 0; !done && i < extraFields.size(); i++ ) + { + if( ( ( ZipExtraField )extraFields.elementAt( i ) ).getHeaderId().equals( type ) ) + { + extraFields.setElementAt( ze, i ); + done = true; + } + } + if( !done ) + { + extraFields.addElement( ze ); + } + setExtra(); + } + + /** + * Overwrite clone + * + * @return Description of the Returned Value + * @since 1.1 + */ + public Object clone() + { + ZipEntry e = null; + try + { + e = new ZipEntry( ( java.util.zip.ZipEntry )super.clone() ); + } + catch( Exception ex ) + { + // impossible as extra data is in correct format + ex.printStackTrace(); + } + e.setInternalAttributes( getInternalAttributes() ); + e.setExternalAttributes( getExternalAttributes() ); + e.setExtraFields( getExtraFields() ); + return e; + } + + /** + * Remove an extra fields. + * + * @param type Description of Parameter + * @since 1.1 + */ + public void removeExtraField( ZipShort type ) + { + boolean done = false; + for( int i = 0; !done && i < extraFields.size(); i++ ) + { + if( ( ( ZipExtraField )extraFields.elementAt( i ) ).getHeaderId().equals( type ) ) + { + extraFields.removeElementAt( i ); + done = true; + } + } + if( !done ) + { + throw new java.util.NoSuchElementException(); + } + setExtra(); + } + + /** + * Unfortunately {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} seems to access the extra data directly, + * so overriding getExtra doesn't help - we need to modify super's data + * directly. + * + * @since 1.1 + */ + protected void setExtra() + { + super.setExtra( ExtraFieldUtils.mergeLocalFileDataData( getExtraFields() ) ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipExtraField.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipExtraField.java new file mode 100644 index 000000000..534530706 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipExtraField.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.zip.ZipException; + +/** + * General format of extra field data.

                  + * + * Extra fields usually appear twice per file, once in the local file data and + * once in the central directory. Usually they are the same, but they don't have + * to be. {@link java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} + * will only use the local file data in both places.

                  + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface ZipExtraField +{ + + /** + * The Header-ID. + * + * @return The HeaderId value + * @since 1.1 + */ + ZipShort getHeaderId(); + + /** + * Length of the extra field in the local file data - without Header-ID or + * length specifier. + * + * @return The LocalFileDataLength value + * @since 1.1 + */ + ZipShort getLocalFileDataLength(); + + /** + * Length of the extra field in the central directory - without Header-ID or + * length specifier. + * + * @return The CentralDirectoryLength value + * @since 1.1 + */ + ZipShort getCentralDirectoryLength(); + + /** + * The actual data to put into local file data - without Header-ID or length + * specifier. + * + * @return The LocalFileDataData value + * @since 1.1 + */ + byte[] getLocalFileDataData(); + + /** + * The actual data to put central directory - without Header-ID or length + * specifier. + * + * @return The CentralDirectoryData value + * @since 1.1 + */ + byte[] getCentralDirectoryData(); + + /** + * Populate data from this array as if it was in local file data. + * + * @param data Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + void parseFromLocalFileData( byte[] data, int offset, int length ) + throws ZipException; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipLong.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipLong.java new file mode 100644 index 000000000..4b3573770 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipLong.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Utility class that represents a four byte integer with conversion rules for + * the big endian byte order of ZIP files. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipLong implements Cloneable +{ + + private long value; + + /** + * Create instance from a number. + * + * @param value Description of Parameter + * @since 1.1 + */ + public ZipLong( long value ) + { + this.value = value; + } + + /** + * Create instance from bytes. + * + * @param bytes Description of Parameter + * @since 1.1 + */ + public ZipLong( byte[] bytes ) + { + this( bytes, 0 ); + } + + /** + * Create instance from the four bytes starting at offset. + * + * @param bytes Description of Parameter + * @param offset Description of Parameter + * @since 1.1 + */ + public ZipLong( byte[] bytes, int offset ) + { + value = ( bytes[offset + 3] << 24 ) & 0xFF000000l; + value += ( bytes[offset + 2] << 16 ) & 0xFF0000; + value += ( bytes[offset + 1] << 8 ) & 0xFF00; + value += ( bytes[offset] & 0xFF ); + } + + /** + * Get value as two bytes in big endian byte order. + * + * @return The Bytes value + * @since 1.1 + */ + public byte[] getBytes() + { + byte[] result = new byte[4]; + result[0] = ( byte )( ( value & 0xFF ) ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + result[2] = ( byte )( ( value & 0xFF0000 ) >> 16 ); + result[3] = ( byte )( ( value & 0xFF000000l ) >> 24 ); + return result; + } + + /** + * Get value as Java int. + * + * @return The Value value + * @since 1.1 + */ + public long getValue() + { + return value; + } + + /** + * Override to make two instances with same value equal. + * + * @param o Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public boolean equals( Object o ) + { + if( o == null || !( o instanceof ZipLong ) ) + { + return false; + } + return value == ( ( ZipLong )o ).getValue(); + } + + /** + * Override to make two instances with same value equal. + * + * @return Description of the Returned Value + * @since 1.1 + */ + public int hashCode() + { + return ( int )value; + } + +}// ZipLong diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipOutputStream.java new file mode 100644 index 000000000..5ab382814 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipOutputStream.java @@ -0,0 +1,712 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Date; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.ZipException; + +/** + * Reimplementation of {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} that does handle the extended functionality of + * this package, especially internal/external file attributes and extra fields + * with different layouts for local file data and central directory entries.

                  + * + * This implementation will use a Data Descriptor to store size and CRC + * information for DEFLATED entries, this means, you don't need to calculate + * them yourself. Unfortunately this is not possible for the STORED method, here + * setting the CRC and uncompressed size information is required before {@link + * #putNextEntry putNextEntry} will be called.

                  + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipOutputStream extends DeflaterOutputStream +{ + + /** + * Helper, a 0 as ZipShort. + * + * @since 1.1 + */ + private final static byte[] ZERO = {0, 0}; + + /** + * Helper, a 0 as ZipLong. + * + * @since 1.1 + */ + private final static byte[] LZERO = {0, 0, 0, 0}; + + /** + * Compression method for deflated entries. + * + * @since 1.1 + */ + public final static int DEFLATED = ZipEntry.DEFLATED; + + /** + * Compression method for deflated entries. + * + * @since 1.1 + */ + public final static int STORED = ZipEntry.STORED; + + /* + * Various ZIP constants + */ + /** + * local file header signature + * + * @since 1.1 + */ + protected final static ZipLong LFH_SIG = new ZipLong( 0X04034B50L ); + /** + * data descriptor signature + * + * @since 1.1 + */ + protected final static ZipLong DD_SIG = new ZipLong( 0X08074B50L ); + /** + * central file header signature + * + * @since 1.1 + */ + protected final static ZipLong CFH_SIG = new ZipLong( 0X02014B50L ); + /** + * end of central dir signature + * + * @since 1.1 + */ + protected final static ZipLong EOCD_SIG = new ZipLong( 0X06054B50L ); + + /** + * Smallest date/time ZIP can handle. + * + * @since 1.1 + */ + private final static ZipLong DOS_TIME_MIN = new ZipLong( 0x00002100L ); + + /** + * The file comment. + * + * @since 1.1 + */ + private String comment = ""; + + /** + * Compression level for next entry. + * + * @since 1.1 + */ + private int level = Deflater.DEFAULT_COMPRESSION; + + /** + * Default compression method for next entry. + * + * @since 1.1 + */ + private int method = DEFLATED; + + /** + * List of ZipEntries written so far. + * + * @since 1.1 + */ + private Vector entries = new Vector(); + + /** + * CRC instance to avoid parsing DEFLATED data twice. + * + * @since 1.1 + */ + private CRC32 crc = new CRC32(); + + /** + * Count the bytes written to out. + * + * @since 1.1 + */ + private long written = 0; + + /** + * Data for current entry started here. + * + * @since 1.1 + */ + private long dataStart = 0; + + /** + * Start of central directory. + * + * @since 1.1 + */ + private ZipLong cdOffset = new ZipLong( 0 ); + + /** + * Length of central directory. + * + * @since 1.1 + */ + private ZipLong cdLength = new ZipLong( 0 ); + + /** + * Holds the offsets of the LFH starts for each entry + * + * @since 1.1 + */ + private Hashtable offsets = new Hashtable(); + + /** + * The encoding to use for filenames and the file comment.

                  + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * . Defaults to the platform's default character encoding.

                  + * + * @since 1.3 + */ + private String encoding = null; + + /** + * Current entry. + * + * @since 1.1 + */ + private ZipEntry entry; + + /** + * Creates a new ZIP OutputStream filtering the underlying stream. + * + * @param out Description of Parameter + * @since 1.1 + */ + public ZipOutputStream( OutputStream out ) + { + super( out, new Deflater( Deflater.DEFAULT_COMPRESSION, true ) ); + } + + /** + * Convert a Date object to a DOS date/time field.

                  + * + * Stolen from InfoZip's fileio.c

                  + * + * @param time Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + protected static ZipLong toDosTime( Date time ) + { + int year = time.getYear() + 1900; + int month = time.getMonth() + 1; + if( year < 1980 ) + { + return DOS_TIME_MIN; + } + long value = ( ( year - 1980 ) << 25 ) + | ( month << 21 ) + | ( time.getDate() << 16 ) + | ( time.getHours() << 11 ) + | ( time.getMinutes() << 5 ) + | ( time.getSeconds() >> 1 ); + + byte[] result = new byte[4]; + result[0] = ( byte )( ( value & 0xFF ) ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + result[2] = ( byte )( ( value & 0xFF0000 ) >> 16 ); + result[3] = ( byte )( ( value & 0xFF000000l ) >> 24 ); + return new ZipLong( result ); + } + + /** + * Set the file comment. + * + * @param comment The new Comment value + * @since 1.1 + */ + public void setComment( String comment ) + { + this.comment = comment; + } + + /** + * The encoding to use for filenames and the file comment.

                  + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * . Defaults to the platform's default character encoding.

                  + * + * @param encoding The new Encoding value + * @since 1.3 + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Sets the compression level for subsequent entries.

                  + * + * Default is Deflater.DEFAULT_COMPRESSION.

                  + * + * @param level The new Level value + * @since 1.1 + */ + public void setLevel( int level ) + { + this.level = level; + } + + /** + * Sets the default compression method for subsequent entries.

                  + * + * Default is DEFLATED.

                  + * + * @param method The new Method value + * @since 1.1 + */ + public void setMethod( int method ) + { + this.method = method; + } + + /** + * The encoding to use for filenames and the file comment. + * + * @return null if using the platform's default character encoding. + * @since 1.3 + */ + public String getEncoding() + { + return encoding; + } + + /** + * Writes all necessary data for this entry. + * + * @exception IOException Description of Exception + * @since 1.1 + */ + public void closeEntry() + throws IOException + { + if( entry == null ) + { + return; + } + + long realCrc = crc.getValue(); + crc.reset(); + + if( entry.getMethod() == DEFLATED ) + { + def.finish(); + while( !def.finished() ) + { + deflate(); + } + + entry.setSize( def.getTotalIn() ); + entry.setComprSize( def.getTotalOut() ); + entry.setCrc( realCrc ); + + def.reset(); + + written += entry.getCompressedSize(); + } + else + { + if( entry.getCrc() != realCrc ) + { + throw new ZipException( "bad CRC checksum for entry " + + entry.getName() + ": " + + Long.toHexString( entry.getCrc() ) + + " instead of " + + Long.toHexString( realCrc ) ); + } + + if( entry.getSize() != written - dataStart ) + { + throw new ZipException( "bad size for entry " + + entry.getName() + ": " + + entry.getSize() + + " instead of " + + ( written - dataStart ) ); + } + + } + + writeDataDescriptor( entry ); + entry = null; + } + + /* + * Found out by experiment, that DeflaterOutputStream.close() + * will call finish() - so we don't need to override close + * ourselves. + */ + /** + * Finishs writing the contents and closes this as well as the underlying + * stream. + * + * @exception IOException Description of Exception + * @since 1.1 + */ + public void finish() + throws IOException + { + closeEntry(); + cdOffset = new ZipLong( written ); + for( int i = 0; i < entries.size(); i++ ) + { + writeCentralFileHeader( ( ZipEntry )entries.elementAt( i ) ); + } + cdLength = new ZipLong( written - cdOffset.getValue() ); + writeCentralDirectoryEnd(); + offsets.clear(); + entries.removeAllElements(); + } + + /** + * Begin writing next entry. + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + public void putNextEntry( ZipEntry ze ) + throws IOException + { + closeEntry(); + + entry = ze; + entries.addElement( entry ); + + if( entry.getMethod() == -1 ) + {// not specified + entry.setMethod( method ); + } + + if( entry.getTime() == -1 ) + {// not specified + entry.setTime( System.currentTimeMillis() ); + } + + if( entry.getMethod() == STORED ) + { + if( entry.getSize() == -1 ) + { + throw new ZipException( "uncompressed size is required for STORED method" ); + } + if( entry.getCrc() == -1 ) + { + throw new ZipException( "crc checksum is required for STORED method" ); + } + entry.setComprSize( entry.getSize() ); + } + else + { + def.setLevel( level ); + } + writeLocalFileHeader( entry ); + } + + /** + * Writes bytes to ZIP entry.

                  + * + * Override is necessary to support STORED entries, as well as calculationg + * CRC automatically for DEFLATED entries.

                  + * + * @param b Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception IOException Description of Exception + */ + public void write( byte[] b, int offset, int length ) + throws IOException + { + if( entry.getMethod() == DEFLATED ) + { + super.write( b, offset, length ); + } + else + { + out.write( b, offset, length ); + written += length; + } + crc.update( b, offset, length ); + } + + /** + * Retrieve the bytes for the given String in the encoding set for this + * Stream. + * + * @param name Description of Parameter + * @return The Bytes value + * @exception ZipException Description of Exception + * @since 1.3 + */ + protected byte[] getBytes( String name ) + throws ZipException + { + if( encoding == null ) + { + return name.getBytes(); + } + else + { + try + { + return name.getBytes( encoding ); + } + catch( UnsupportedEncodingException uee ) + { + throw new ZipException( uee.getMessage() ); + } + } + } + + /** + * Writes the "End of central dir record" + * + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeCentralDirectoryEnd() + throws IOException + { + out.write( EOCD_SIG.getBytes() ); + + // disk numbers + out.write( ZERO ); + out.write( ZERO ); + + // number of entries + byte[] num = ( new ZipShort( entries.size() ) ).getBytes(); + out.write( num ); + out.write( num ); + + // length and location of CD + out.write( cdLength.getBytes() ); + out.write( cdOffset.getBytes() ); + + // ZIP file comment + byte[] data = getBytes( comment ); + out.write( ( new ZipShort( data.length ) ).getBytes() ); + out.write( data ); + } + + /** + * Writes the central file header entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeCentralFileHeader( ZipEntry ze ) + throws IOException + { + out.write( CFH_SIG.getBytes() ); + written += 4; + + // version made by + out.write( ( new ZipShort( 20 ) ).getBytes() ); + written += 2; + + // version needed to extract + // general purpose bit flag + if( ze.getMethod() == DEFLATED ) + { + // requires version 2 as we are going to store length info + // in the data descriptor + out.write( ( new ZipShort( 20 ) ).getBytes() ); + + // bit3 set to signal, we use a data descriptor + out.write( ( new ZipShort( 8 ) ).getBytes() ); + } + else + { + out.write( ( new ZipShort( 10 ) ).getBytes() ); + out.write( ZERO ); + } + written += 4; + + // compression method + out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() ); + written += 2; + + // last mod. time and date + out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() ); + written += 4; + + // CRC + // compressed length + // uncompressed length + out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getCompressedSize() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + written += 12; + + // file name length + byte[] name = getBytes( ze.getName() ); + out.write( ( new ZipShort( name.length ) ).getBytes() ); + written += 2; + + // extra field length + byte[] extra = ze.getCentralDirectoryExtra(); + out.write( ( new ZipShort( extra.length ) ).getBytes() ); + written += 2; + + // file comment length + String comm = ze.getComment(); + if( comm == null ) + { + comm = ""; + } + byte[] comment = getBytes( comm ); + out.write( ( new ZipShort( comment.length ) ).getBytes() ); + written += 2; + + // disk number start + out.write( ZERO ); + written += 2; + + // internal file attributes + out.write( ( new ZipShort( ze.getInternalAttributes() ) ).getBytes() ); + written += 2; + + // external file attributes + out.write( ( new ZipLong( ze.getExternalAttributes() ) ).getBytes() ); + written += 4; + + // relative offset of LFH + out.write( ( ( ZipLong )offsets.get( ze ) ).getBytes() ); + written += 4; + + // file name + out.write( name ); + written += name.length; + + // extra field + out.write( extra ); + written += extra.length; + + // file comment + out.write( comment ); + written += comment.length; + } + + /** + * Writes the data descriptor entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeDataDescriptor( ZipEntry ze ) + throws IOException + { + if( ze.getMethod() != DEFLATED ) + { + return; + } + out.write( DD_SIG.getBytes() ); + out.write( ( new ZipLong( entry.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( entry.getCompressedSize() ) ).getBytes() ); + out.write( ( new ZipLong( entry.getSize() ) ).getBytes() ); + written += 16; + } + + /** + * Writes the local file header entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeLocalFileHeader( ZipEntry ze ) + throws IOException + { + offsets.put( ze, new ZipLong( written ) ); + + out.write( LFH_SIG.getBytes() ); + written += 4; + + // version needed to extract + // general purpose bit flag + if( ze.getMethod() == DEFLATED ) + { + // requires version 2 as we are going to store length info + // in the data descriptor + out.write( ( new ZipShort( 20 ) ).getBytes() ); + + // bit3 set to signal, we use a data descriptor + out.write( ( new ZipShort( 8 ) ).getBytes() ); + } + else + { + out.write( ( new ZipShort( 10 ) ).getBytes() ); + out.write( ZERO ); + } + written += 4; + + // compression method + out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() ); + written += 2; + + // last mod. time and date + out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() ); + written += 4; + + // CRC + // compressed length + // uncompressed length + if( ze.getMethod() == DEFLATED ) + { + out.write( LZERO ); + out.write( LZERO ); + out.write( LZERO ); + } + else + { + out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + } + written += 12; + + // file name length + byte[] name = getBytes( ze.getName() ); + out.write( ( new ZipShort( name.length ) ).getBytes() ); + written += 2; + + // extra field length + byte[] extra = ze.getLocalFileDataExtra(); + out.write( ( new ZipShort( extra.length ) ).getBytes() ); + written += 2; + + // file name + out.write( name ); + written += name.length; + + // extra field + out.write( extra ); + written += extra.length; + + dataStart = written; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipShort.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipShort.java new file mode 100644 index 000000000..b06f040e1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipShort.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Utility class that represents a two byte integer with conversion rules for + * the big endian byte order of ZIP files. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipShort implements Cloneable +{ + + private int value; + + /** + * Create instance from a number. + * + * @param value Description of Parameter + * @since 1.1 + */ + public ZipShort( int value ) + { + this.value = value; + } + + /** + * Create instance from bytes. + * + * @param bytes Description of Parameter + * @since 1.1 + */ + public ZipShort( byte[] bytes ) + { + this( bytes, 0 ); + } + + /** + * Create instance from the two bytes starting at offset. + * + * @param bytes Description of Parameter + * @param offset Description of Parameter + * @since 1.1 + */ + public ZipShort( byte[] bytes, int offset ) + { + value = ( bytes[offset + 1] << 8 ) & 0xFF00; + value += ( bytes[offset] & 0xFF ); + } + + /** + * Get value as two bytes in big endian byte order. + * + * @return The Bytes value + * @since 1.1 + */ + public byte[] getBytes() + { + byte[] result = new byte[2]; + result[0] = ( byte )( value & 0xFF ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + return result; + } + + /** + * Get value as Java int. + * + * @return The Value value + * @since 1.1 + */ + public int getValue() + { + return value; + } + + /** + * Override to make two instances with same value equal. + * + * @param o Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public boolean equals( Object o ) + { + if( o == null || !( o instanceof ZipShort ) ) + { + return false; + } + return value == ( ( ZipShort )o ).getValue(); + } + + /** + * Override to make two instances with same value equal. + * + * @return Description of the Returned Value + * @since 1.1 + */ + public int hashCode() + { + return value; + } + +}// ZipShort