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:
+ *
+ * The current directory, and,
+ * 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:
+ *
+ *
+ * Other parameters are:
+ *
+ *
+ * comment, key, operation, type and value (the final four being
+ * eliminated shortly)
+ *
+ * The <entry> task must have:
+ *
+ *
+ * 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
+ *
+ * is incremental build still broken in beta-1?
+ * is Win32Icon broken?
+ * 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 ... ... [-unused ...]
+ * Parameters
+ * path File or directory to audit.
+ * search-path File or directory to search for declaration uses.
+ * Options
+ * -arguments -A Includes command line arguments from file.
+ * -classpath -cp Sets class path (also source path unless one
+ * explicitly set). Overrides METAPATH/CLASSPATH.
+ * -exit -x Exits after the first error.
+ * -fix -f Automatically fixes certain errors.
+ * -fullpath Prints full path for locations.
+ * -help -h Prints help and exits.
+ * -list -l Creates listing file for each audited file.
+ * -offsets -off Offset and length for locations.
+ * -output -o Prints output to file.
+ * -quiet -q Suppresses copyright and summary messages.
+ * -sourcepath Sets source path. Overrides SOURCEPATH.
+ * -tab -t Prints a tab character after first argument.
+ * -unused -u Finds declarations unused in search paths.
+ * -verbose -v Prints all messages.
+ * -version -V Prints version and exits.
+ */
+ //---------------------- PUBLIC METHODS ------------------------------------
+
+ /**
+ * pattern used by maudit to report the error for a file
+ */
+ /**
+ * RE does not seems to support regexp pattern with comments so i'm
+ * stripping it
+ */
+ // (?:file:)?((?#filepath).+):((?#line)\\d+)\\s*:\\s+((?#message).*)
+ final static String AUDIT_PATTERN = "(?:file:)?(.+):(\\d+)\\s*:\\s+(.*)";
+
+ protected File outFile = null;
+
+ protected Path searchPath = null;
+
+ protected boolean fix = false;
+
+ protected boolean list = false;
+
+ protected boolean unused = false;
+
+ /**
+ * default constructor
+ */
+ public MAudit()
+ {
+ super( "com.metamata.gui.rc.MAudit" );
+ }
+
+ /**
+ * handy factory to create a violation
+ *
+ * @param line Description of Parameter
+ * @param msg Description of Parameter
+ * @return Description of the Returned Value
+ */
+ final static Violation createViolation( int line, String msg )
+ {
+ Violation violation = new Violation();
+ violation.line = line;
+ violation.error = msg;
+ return violation;
+ }
+
+ public void setFix( boolean flag )
+ {
+ this.fix = flag;
+ }
+
+ public void setList( boolean flag )
+ {
+ this.list = flag;
+ }
+
+ /**
+ * set the destination file which should be an xml file
+ *
+ * @param outFile The new Tofile value
+ */
+ public void setTofile( File outFile )
+ {
+ this.outFile = outFile;
+ }
+
+ public void setUnused( boolean flag )
+ {
+ this.unused = flag;
+ }
+
+ public Path createSearchpath()
+ {
+ if( searchPath == null )
+ {
+ searchPath = new Path( project );
+ }
+ return searchPath;
+ }
+
+ protected Vector getOptions()
+ {
+ Vector options = new Vector( 512 );
+ // there is a bug in Metamata 2.0 build 37. The sourcepath argument does
+ // not work. So we will use the sourcepath prepended to classpath. (order
+ // is important since Metamata looks at .class and .java)
+ if( sourcePath != null )
+ {
+ sourcePath.append( classPath );// srcpath is prepended
+ classPath = sourcePath;
+ sourcePath = null;// prevent from using -sourcepath
+ }
+
+ // don't forget to modify the pattern if you change the options reporting
+ if( classPath != null )
+ {
+ options.addElement( "-classpath" );
+ options.addElement( classPath.toString() );
+ }
+ // suppress copyright msg when running, we will let it so that this
+ // will be the only output to the console if in xml mode
+// options.addElement("-quiet");
+ if( fix )
+ {
+ options.addElement( "-fix" );
+ }
+ options.addElement( "-fullpath" );
+
+ // generate .maudit files much more detailed than the report
+ // I don't like it very much, I think it could be interesting
+ // to get all .maudit files and include them in the XML.
+ if( list )
+ {
+ options.addElement( "-list" );
+ }
+ if( sourcePath != null )
+ {
+ options.addElement( "-sourcepath" );
+ options.addElement( sourcePath.toString() );
+ }
+
+ if( unused )
+ {
+ options.addElement( "-unused" );
+ options.addElement( searchPath.toString() );
+ }
+ addAllVector( options, includedFiles.keys() );
+ return options;
+ }
+
+ protected void checkOptions()
+ throws BuildException
+ {
+ super.checkOptions();
+ if( unused && searchPath == null )
+ {
+ throw new BuildException( "'searchpath' element must be set when looking for 'unused' declarations." );
+ }
+ if( !unused && searchPath != null )
+ {
+ log( "'searchpath' element ignored. 'unused' attribute is disabled.", Project.MSG_WARN );
+ }
+ }
+
+ protected void cleanUp()
+ throws BuildException
+ {
+ super.cleanUp();
+ // at this point if -list is used, we should move
+ // the .maudit file since we cannot choose their location :(
+ // the .maudit files match the .java files
+ // we'll use includedFiles to get the .maudit files.
+
+ /*
+ * if (out != null){
+ * / close it if not closed by the handler...
+ * }
+ */
+ }
+
+ protected ExecuteStreamHandler createStreamHandler()
+ throws BuildException
+ {
+ ExecuteStreamHandler handler = null;
+ // if we didn't specify a file, then use a screen report
+ if( outFile == null )
+ {
+ handler = new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_INFO );
+ }
+ else
+ {
+ try
+ {
+ //XXX
+ OutputStream out = new FileOutputStream( outFile );
+ handler = new MAuditStreamHandler( this, out );
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( e );
+ }
+ }
+ return handler;
+ }
+
+ /**
+ * the inner class used to report violation information
+ *
+ * @author RT
+ */
+ final static class Violation
+ {
+ String error;
+ int line;
+ }
+
+}
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java
new file mode 100644
index 000000000..1096d6700
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
+import org.apache.tools.ant.util.DOMElementWriter;
+import org.apache.tools.ant.util.regexp.RegexpMatcher;
+import org.apache.tools.ant.util.regexp.RegexpMatcherFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+
+/**
+ * This is a very bad stream handler for the MAudit task. All report to stdout
+ * that does not match a specific report pattern is dumped to the Ant output as
+ * warn level. The report that match the pattern is stored in a map with the key
+ * being the filepath that caused the error report.
+ *
+ * The limitation with the choosen implementation is clear:
+ *
+ * it does not handle multiline report( message that has \n ). the part
+ * until the \n will be stored and the other part (which will not match the
+ * pattern) will go to Ant output in Warn level.
+ * it does not report error that goes to stderr.
+ *
+ *
+ *
+ * @author Stephane Bailliez
+ */
+class MAuditStreamHandler implements ExecuteStreamHandler
+{
+
+ /**
+ * this is where the XML output will go, should mostly be a file the caller
+ * is responsible for flushing and closing this stream
+ */
+ protected OutputStream xmlOut = null;
+
+ /**
+ * the multimap. The key in the map is the filepath that caused the audit
+ * error and the value is a vector of MAudit.Violation entries.
+ */
+ protected Hashtable auditedFiles = new Hashtable();
+
+ /**
+ * reader for stdout
+ */
+ protected BufferedReader br;
+
+ /**
+ * matcher that will be used to extract the info from the line
+ */
+ protected RegexpMatcher matcher;
+
+ protected MAudit task;
+
+ MAuditStreamHandler( MAudit task, OutputStream xmlOut )
+ {
+ this.task = task;
+ this.xmlOut = xmlOut;
+ /**
+ * the matcher should be the Oro one. I don't know about the other one
+ */
+ matcher = ( new RegexpMatcherFactory() ).newRegexpMatcher();
+ matcher.setPattern( MAudit.AUDIT_PATTERN );
+ }
+
+ protected static DocumentBuilder getDocumentBuilder()
+ {
+ try
+ {
+ return DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ }
+ catch( Exception exc )
+ {
+ throw new ExceptionInInitializerError( exc );
+ }
+ }
+
+ /**
+ * 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. This will block until the end :-(
+ *
+ * @exception IOException Description of Exception
+ */
+ public void start()
+ throws IOException
+ {
+ parseOutput( br );
+ }
+
+ /**
+ * Pretty dangerous business here. It serializes what was extracted from the
+ * MAudit output and write it to the output.
+ */
+ public void stop()
+ {
+ // serialize the content as XML, move this to another method
+ // this is the only code that could be needed to be overrided
+ Document doc = getDocumentBuilder().newDocument();
+ Element rootElement = doc.createElement( "classes" );
+ Enumeration keys = auditedFiles.keys();
+ Hashtable filemapping = task.getFileMapping();
+ rootElement.setAttribute( "audited", String.valueOf( filemapping.size() ) );
+ rootElement.setAttribute( "reported", String.valueOf( auditedFiles.size() ) );
+ int errors = 0;
+ while( keys.hasMoreElements() )
+ {
+ String filepath = ( String )keys.nextElement();
+ Vector v = ( Vector )auditedFiles.get( filepath );
+ String fullclassname = ( String )filemapping.get( filepath );
+ if( fullclassname == null )
+ {
+ task.getProject().log( "Could not find class mapping for " + filepath, Project.MSG_WARN );
+ continue;
+ }
+ int pos = fullclassname.lastIndexOf( '.' );
+ String pkg = ( pos == -1 ) ? "" : fullclassname.substring( 0, pos );
+ String clazzname = ( pos == -1 ) ? fullclassname : fullclassname.substring( pos + 1 );
+ Element clazz = doc.createElement( "class" );
+ clazz.setAttribute( "package", pkg );
+ clazz.setAttribute( "name", clazzname );
+ clazz.setAttribute( "violations", String.valueOf( v.size() ) );
+ errors += v.size();
+ for( int i = 0; i < v.size(); i++ )
+ {
+ MAudit.Violation violation = ( MAudit.Violation )v.elementAt( i );
+ Element error = doc.createElement( "violation" );
+ error.setAttribute( "line", String.valueOf( violation.line ) );
+ error.setAttribute( "message", violation.error );
+ clazz.appendChild( error );
+ }
+ rootElement.appendChild( clazz );
+ }
+ rootElement.setAttribute( "violations", String.valueOf( errors ) );
+
+ // now write it to the outputstream, not very nice code
+ if( xmlOut != null )
+ {
+ Writer wri = null;
+ try
+ {
+ wri = new OutputStreamWriter( xmlOut, "UTF-8" );
+ wri.write( "\n" );
+ ( new DOMElementWriter() ).write( rootElement, wri, 0, " " );
+ wri.flush();
+ }
+ catch( IOException exc )
+ {
+ task.log( "Unable to write log file", Project.MSG_ERR );
+ }
+ finally
+ {
+ if( xmlOut != System.out && xmlOut != System.err )
+ {
+ if( wri != null )
+ {
+ try
+ {
+ wri.close();
+ }
+ catch( IOException e )
+ {}
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * add a violation entry for the file
+ *
+ * @param file The feature to be added to the ViolationEntry attribute
+ * @param entry The feature to be added to the ViolationEntry attribute
+ */
+ protected void addViolationEntry( String file, MAudit.Violation entry )
+ {
+ Vector violations = ( Vector )auditedFiles.get( file );
+ // if there is no decl for this file yet, create it.
+ if( violations == null )
+ {
+ violations = new Vector();
+ auditedFiles.put( file, violations );
+ }
+ violations.add( entry );
+ }
+
+ /**
+ * read each line and process it
+ *
+ * @param br Description of Parameter
+ * @exception IOException Description of Exception
+ */
+ protected void parseOutput( BufferedReader br )
+ throws IOException
+ {
+ String line = null;
+ while( ( line = br.readLine() ) != null )
+ {
+ processLine( line );
+ }
+ }
+
+ // we suppose here that there is only one report / line.
+ // There will obviouslly be a problem if the message is on several lines...
+ protected void processLine( String line )
+ {
+ Vector matches = matcher.getGroups( line );
+ if( matches != null )
+ {
+ String file = ( String )matches.elementAt( 1 );
+ int lineNum = Integer.parseInt( ( String )matches.elementAt( 2 ) );
+ String msg = ( String )matches.elementAt( 3 );
+ addViolationEntry( file, MAudit.createViolation( lineNum, msg ) );
+ }
+ else
+ {
+ // this doesn't match..report it as info, it could be
+ // either the copyright, summary or a multiline message (damn !)
+ task.log( line, Project.MSG_INFO );
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java
new file mode 100644
index 000000000..6a5ae42ee
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+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;
+
+/**
+ * Calculates global complexity and quality metrics on Java source code. You
+ * will not be able to use this task with the evaluation version since as of
+ * Metamata 2.0, Metrics does not support command line :-( For more information,
+ * visit the website at www.metamata.com
+ *
+ * @author Stephane Bailliez
+ */
+public class MMetrics extends AbstractMetamataTask
+{
+ /*
+ * The command line options as of Metamata 2.0 are as follows:
+ * Usage
+ * mmetrics ... ...
+ * Parameters
+ * path File or directory to measure.
+ * Options
+ * -arguments -A Includes command line arguments from file.
+ * -classpath -cp Sets class path (also source path unless one
+ * explicitly set). Overrides METAPATH/CLASSPATH.
+ * -compilation-units Measure compilation units.
+ * -files Measure compilation units.
+ * -format -f Sets output format, default output file type.
+ * -help -h Prints help and exits.
+ * -indent -i Sets string used to indent labels one level.
+ * -methods Measure methods, types, and compilation units.
+ * -output -o Sets output file name.
+ * -quiet -q Suppresses copyright message.
+ * -sourcepath Sets source path. Overrides SOURCEPATH.
+ * -types Measure types and compilation units.
+ * -verbose -v Prints all messages.
+ * -version -V Prints version and exits.
+ * Format Options
+ * comma csv Format output as comma-separated text.
+ * html htm Format output as an HTML table.
+ * tab tab-separated tsv Format output as tab-separated text.
+ * text txt Format output as space-aligned text.
+ */
+ /**
+ * the granularity mode. Should be one of 'files', 'methods' and 'types'.
+ */
+ protected String granularity = null;
+
+ /**
+ * the XML output file
+ */
+ protected File outFile = null;
+
+ /**
+ * the location of the temporary txt report
+ */
+ protected File tmpFile = createTmpFile();
+
+ protected Path path = null;
+
+ //--------------------------- PUBLIC METHODS -------------------------------
+
+ /**
+ * default constructor
+ */
+ public MMetrics()
+ {
+ super( "com.metamata.sc.MMetrics" );
+ }
+
+ /**
+ * set the granularity of the audit. Should be one of 'files', 'methods' or
+ * 'types'.
+ *
+ * @param granularity the audit reporting mode.
+ */
+ public void setGranularity( String granularity )
+ {
+ this.granularity = granularity;
+ }
+
+ /**
+ * Set the output XML file
+ *
+ * @param file the xml file to write the XML report to.
+ */
+ public void setTofile( File file )
+ {
+ this.outFile = file;
+ }
+
+ /**
+ * Set a new path (directory) to measure metrics from.
+ *
+ * @return the path instance to use.
+ */
+ public Path createPath()
+ {
+ if( path == null )
+ {
+ path = new Path( project );
+ }
+ return path;
+ }
+
+
+ protected Vector getOptions()
+ {
+ Vector options = new Vector( 512 );
+ // there is a bug in Metamata 2.0 build 37. The sourcepath argument does
+ // not work. So we will use the sourcepath prepended to classpath. (order
+ // is important since Metamata looks at .class and .java)
+ if( sourcePath != null )
+ {
+ sourcePath.append( classPath );// srcpath is prepended
+ classPath = sourcePath;
+ sourcePath = null;// prevent from using -sourcepath
+ }
+
+ // don't forget to modify the pattern if you change the options reporting
+ if( classPath != null )
+ {
+ options.addElement( "-classpath" );
+ options.addElement( classPath );
+ }
+ options.addElement( "-output" );
+ options.addElement( tmpFile.toString() );
+
+ options.addElement( "-" + granularity );
+
+ // display the metamata copyright
+ // options.addElement( "-quiet");
+ options.addElement( "-format" );
+
+ // need this because that's what the handler is using, it's
+ // way easier to process than any other separator
+ options.addElement( "tab" );
+
+ // specify a / as the indent character, used by the handler.
+ options.addElement( "-i" );
+ options.addElement( "/" );
+
+ // directories
+ String[] dirs = path.list();
+ for( int i = 0; i < dirs.length; i++ )
+ {
+ options.addElement( dirs[i] );
+ }
+ // files next.
+ addAllVector( options, includedFiles.keys() );
+ return options;
+ }
+
+ //------------------- PROTECTED / PRIVATE METHODS --------------------------
+
+
+ // check for existing options and outfile, all other are optional
+ protected void checkOptions()
+ throws BuildException
+ {
+ super.checkOptions();
+
+ if( !"files".equals( granularity ) && !"methods".equals( granularity )
+ && !"types".equals( granularity ) )
+ {
+ throw new BuildException( "Metrics reporting granularity is invalid. Must be one of 'files', 'methods', 'types'" );
+ }
+ if( outFile == null )
+ {
+ throw new BuildException( "Output XML file must be set via 'tofile' attribute." );
+ }
+ if( path == null && fileSets.size() == 0 )
+ {
+ throw new BuildException( "Must set either paths (path element) or files (fileset element)" );
+ }
+ // I don't accept dirs and files at the same time, I cannot recognize the semantic in the result
+ if( path != null && fileSets.size() > 0 )
+ {
+ throw new BuildException( "Cannot set paths (path element) and files (fileset element) at the same time" );
+ }
+ }
+
+
+ /**
+ * cleanup the temporary txt report
+ *
+ * @exception BuildException Description of Exception
+ */
+ protected void cleanUp()
+ throws BuildException
+ {
+ try
+ {
+ super.cleanUp();
+ }
+ finally
+ {
+ if( tmpFile != null )
+ {
+ tmpFile.delete();
+ tmpFile = null;
+ }
+ }
+ }
+
+ /**
+ * if the report is transform via a temporary txt file we should use a a
+ * normal logger here, otherwise we could use the metrics handler directly
+ * to capture and transform the output on stdout to XML.
+ *
+ * @return Description of the Returned Value
+ */
+ protected ExecuteStreamHandler createStreamHandler()
+ {
+ // write the report directtly to an XML stream
+ // return new MMetricsStreamHandler(this, xmlStream);
+ return new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_INFO );
+ }
+
+ protected void execute0( ExecuteStreamHandler handler )
+ throws BuildException
+ {
+ super.execute0( handler );
+ transformFile();
+ }
+
+ /**
+ * transform the generated file via the handler This function can either be
+ * called if the result is written to the output file via -output or we
+ * could use the handler directly on stdout if not.
+ *
+ * @exception BuildException Description of Exception
+ * @see #createStreamHandler()
+ */
+ protected void transformFile()
+ throws BuildException
+ {
+ FileInputStream tmpStream = null;
+ try
+ {
+ tmpStream = new FileInputStream( tmpFile );
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Error reading temporary file: " + tmpFile, e );
+ }
+ FileOutputStream xmlStream = null;
+ try
+ {
+ xmlStream = new FileOutputStream( outFile );
+ ExecuteStreamHandler xmlHandler = new MMetricsStreamHandler( this, xmlStream );
+ xmlHandler.setProcessOutputStream( tmpStream );
+ xmlHandler.start();
+ xmlHandler.stop();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Error creating output file: " + outFile, e );
+ }
+ finally
+ {
+ if( xmlStream != null )
+ {
+ try
+ {
+ xmlStream.close();
+ }
+ catch( IOException ignored )
+ {}
+ }
+ if( tmpStream != null )
+ {
+ try
+ {
+ tmpStream.close();
+ }
+ catch( IOException ignored )
+ {}
+ }
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java
new file mode 100644
index 000000000..b09fd00fc
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.EmptyStackException;
+import java.util.Enumeration;
+import java.util.Stack;
+import java.util.Vector;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * A handy metrics handler. Most of this code was done only with the screenshots
+ * on the documentation since the evaluation version as of this writing does not
+ * allow to save metrics or to run it via command line.
+ *
+ * This class can be used to transform a text file or to process the output
+ * stream directly.
+ *
+ * @author Stephane Bailliez
+ */
+public class MMetricsStreamHandler implements ExecuteStreamHandler
+{
+
+ /**
+ * CLASS construct, it should be named something like 'MyClass'
+ */
+ protected final static String CLASS = "class";
+
+ /**
+ * package construct, it should be look like 'com.mycompany.something'
+ */
+ protected final static String PACKAGE = "package";
+
+ /**
+ * FILE construct, it should look like something 'MyClass.java' or
+ * 'MyClass.class'
+ */
+ protected final static String FILE = "file";
+
+ /**
+ * METHOD construct, it should looke like something 'doSomething(...)' or
+ * 'doSomething()'
+ */
+ protected final static String METHOD = "method";
+
+ protected final static String[] ATTRIBUTES = {"name", "vg", "loc",
+ "dit", "noa", "nrm", "nlm", "wmc", "rfc", "dac", "fanout", "cbo", "lcom", "nocl"
+ };
+
+ /**
+ * the stack where are stored the metrics element so that they we can know
+ * if we have to close an element or not.
+ */
+ protected Stack stack = new Stack();
+
+ /**
+ * metrics handler
+ */
+ protected TransformerHandler metricsHandler;
+
+ /**
+ * reader for stdout
+ */
+ protected InputStream metricsOutput;
+
+ /**
+ * the task
+ */
+ protected Task task;
+
+ /**
+ * this is where the XML output will go, should mostly be a file the caller
+ * is responsible for flushing and closing this stream
+ */
+ protected OutputStream xmlOutputStream;
+
+ /**
+ * initialize this handler
+ *
+ * @param task Description of Parameter
+ * @param xmlOut Description of Parameter
+ */
+ MMetricsStreamHandler( Task task, OutputStream xmlOut )
+ {
+ this.task = task;
+ this.xmlOutputStream = xmlOut;
+ }
+
+ /**
+ * Ignore.
+ *
+ * @param p1 The new ProcessErrorStream value
+ * @exception IOException Description of Exception
+ */
+ public void setProcessErrorStream( InputStream p1 )
+ throws IOException { }
+
+ /**
+ * Ignore.
+ *
+ * @param p1 The new ProcessInputStream value
+ * @exception IOException Description of Exception
+ */
+ public void setProcessInputStream( OutputStream p1 )
+ throws IOException { }
+
+ /**
+ * Set the inputstream
+ *
+ * @param is The new ProcessOutputStream value
+ * @exception IOException Description of Exception
+ */
+ public void setProcessOutputStream( InputStream is )
+ throws IOException
+ {
+ metricsOutput = is;
+ }
+
+ public void start()
+ throws IOException
+ {
+ // create the transformer handler that will be used to serialize
+ // the output.
+ TransformerFactory factory = TransformerFactory.newInstance();
+ if( !factory.getFeature( SAXTransformerFactory.FEATURE ) )
+ {
+ throw new IllegalStateException( "Invalid Transformer factory feature" );
+ }
+ try
+ {
+ metricsHandler = ( ( SAXTransformerFactory )factory ).newTransformerHandler();
+ metricsHandler.setResult( new StreamResult( new OutputStreamWriter( xmlOutputStream, "UTF-8" ) ) );
+ Transformer transformer = metricsHandler.getTransformer();
+ transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
+
+ // start the document with a 'metrics' root
+ metricsHandler.startDocument();
+ AttributesImpl attr = new AttributesImpl();
+ attr.addAttribute( "", "company", "company", "CDATA", "metamata" );
+ metricsHandler.startElement( "", "metrics", "metrics", attr );
+
+ // now parse the whole thing
+ parseOutput();
+
+ }
+ catch( Exception e )
+ {
+ e.printStackTrace();
+ throw new IOException( e.getMessage() );
+ }
+ }
+
+ /**
+ * Pretty dangerous business here.
+ */
+ public void stop()
+ {
+ try
+ {
+ // we need to pop everything and close elements that have not been
+ // closed yet.
+ while( stack.size() > 0 )
+ {
+ ElementEntry elem = ( ElementEntry )stack.pop();
+ metricsHandler.endElement( "", elem.getType(), elem.getType() );
+ }
+ // close the root
+ metricsHandler.endElement( "", "metrics", "metrics" );
+ // document is finished for good
+ metricsHandler.endDocument();
+ }
+ catch( SAXException e )
+ {
+ e.printStackTrace();
+ throw new IllegalStateException( e.getMessage() );
+ }
+ }
+
+ /**
+ * return the construct type of the element. We can hardly recognize the
+ * type of a metrics element, so we are kind of forced to do some black
+ * magic based on the name and indentation to recognize the type.
+ *
+ * @param elem the metrics element to guess for its type.
+ * @return the type of the metrics element, either PACKAGE, FILE, CLASS or
+ * METHOD.
+ */
+ protected String getConstructType( MetricsElement elem )
+ {
+ // ok no doubt, it's a file
+ if( elem.isCompilationUnit() )
+ {
+ return FILE;
+ }
+
+ // same, we're sure it's a method
+ if( elem.isMethod() )
+ {
+ return METHOD;
+ }
+
+ // if it's empty, and none of the above it should be a package
+ if( stack.size() == 0 )
+ {
+ return PACKAGE;
+ }
+
+ // ok, this is now black magic time, we will guess the type based on
+ // the previous type and its indent...
+ final ElementEntry previous = ( ElementEntry )stack.peek();
+ final String prevType = previous.getType();
+ final int prevIndent = previous.getIndent();
+ final int indent = elem.getIndent();
+ // we're just under a file with a bigger indent so it's a class
+ if( prevType.equals( FILE ) && indent > prevIndent )
+ {
+ return CLASS;
+ }
+
+ // we're just under a class with a greater or equals indent, it's a class
+ // (there might be several classes in a compilation unit and inner classes as well)
+ if( prevType.equals( CLASS ) && indent >= prevIndent )
+ {
+ return CLASS;
+ }
+
+ // we assume the other are package
+ return PACKAGE;
+ }
+
+
+ /**
+ * Create all attributes of a MetricsElement skipping those who have an
+ * empty string
+ *
+ * @param elem
+ * @return Description of the Returned Value
+ */
+ protected Attributes createAttributes( MetricsElement elem )
+ {
+ AttributesImpl impl = new AttributesImpl();
+ int i = 0;
+ String name = ATTRIBUTES[i++];
+ impl.addAttribute( "", name, name, "CDATA", elem.getName() );
+ Enumeration metrics = elem.getMetrics();
+ for( ; metrics.hasMoreElements(); i++ )
+ {
+ String value = ( String )metrics.nextElement();
+ if( value.length() > 0 )
+ {
+ name = ATTRIBUTES[i];
+ impl.addAttribute( "", name, name, "CDATA", value );
+ }
+ }
+ return impl;
+ }
+
+ /**
+ * read each line and process it
+ *
+ * @exception IOException Description of Exception
+ * @exception SAXException Description of Exception
+ */
+ protected void parseOutput()
+ throws IOException, SAXException
+ {
+ BufferedReader br = new BufferedReader( new InputStreamReader( metricsOutput ) );
+ String line = null;
+ while( ( line = br.readLine() ) != null )
+ {
+ processLine( line );
+ }
+ }
+
+ /**
+ * Process a metrics line. If the metrics is invalid and that this is not
+ * the header line, it is display as info.
+ *
+ * @param line the line to process, it is normally a line full of metrics.
+ * @exception SAXException Description of Exception
+ */
+ protected void processLine( String line )
+ throws SAXException
+ {
+ if( line.startsWith( "Construct\tV(G)\tLOC\tDIT\tNOA\tNRM\tNLM\tWMC\tRFC\tDAC\tFANOUT\tCBO\tLCOM\tNOCL" ) )
+ {
+ return;
+ }
+ try
+ {
+ MetricsElement elem = MetricsElement.parse( line );
+ startElement( elem );
+ }
+ catch( ParseException e )
+ {
+ e.printStackTrace();
+ // invalid lines are sent to the output as information, it might be anything,
+ task.log( line, Project.MSG_INFO );
+ }
+ }
+
+ /**
+ * Start a new construct. Elements are popped until we are on the same
+ * parent node, then the element type is guessed and pushed on the stack.
+ *
+ * @param elem the element to process.
+ * @throws SAXException thrown if there is a problem when sending SAX
+ * events.
+ */
+ protected void startElement( MetricsElement elem )
+ throws SAXException
+ {
+ // if there are elements in the stack we possibly need to close one or
+ // more elements previous to this one until we got its parent
+ int indent = elem.getIndent();
+ if( stack.size() > 0 )
+ {
+ ElementEntry previous = ( ElementEntry )stack.peek();
+ // close nodes until you got the parent.
+ try
+ {
+ while( indent <= previous.getIndent() && stack.size() > 0 )
+ {
+ stack.pop();
+ metricsHandler.endElement( "", previous.getType(), previous.getType() );
+ previous = ( ElementEntry )stack.peek();
+ }
+ }
+ catch( EmptyStackException ignored )
+ {}
+ }
+
+ // ok, now start the new construct
+ String type = getConstructType( elem );
+ Attributes attrs = createAttributes( elem );
+ metricsHandler.startElement( "", type, type, attrs );
+
+ // make sure we keep track of what we did, that's history
+ stack.push( new ElementEntry( type, indent ) );
+ }
+
+ /**
+ * helper class to keep track of elements via its type and indent that's all
+ * we need to guess a type.
+ *
+ * @author RT
+ */
+ private final static class ElementEntry
+ {
+ private int indent;
+ private String type;
+
+ ElementEntry( String type, int indent )
+ {
+ this.type = type;
+ this.indent = indent;
+ }
+
+ public int getIndent()
+ {
+ return indent;
+ }
+
+ public String getType()
+ {
+ return type;
+ }
+ }
+}
+
+class MetricsElement
+{
+
+ private final static NumberFormat METAMATA_NF;
+
+ private final static NumberFormat NEUTRAL_NF;
+
+ private String construct;
+
+ private int indent;
+
+ private Vector metrics;
+ static
+ {
+ METAMATA_NF = NumberFormat.getInstance();
+ METAMATA_NF.setMaximumFractionDigits( 1 );
+ NEUTRAL_NF = NumberFormat.getInstance();
+ if( NEUTRAL_NF instanceof DecimalFormat )
+ {
+ ( ( DecimalFormat )NEUTRAL_NF ).applyPattern( "###0.###;-###0.###" );
+ }
+ NEUTRAL_NF.setMaximumFractionDigits( 1 );
+ }
+
+ MetricsElement( int indent, String construct, Vector metrics )
+ {
+ this.indent = indent;
+ this.construct = construct;
+ this.metrics = metrics;
+ }
+
+ public static MetricsElement parse( String line )
+ throws ParseException
+ {
+ final Vector metrics = new Vector();
+ int pos;
+
+ // i'm using indexOf since I need to know if there are empty strings
+ // between tabs and I find it easier than with StringTokenizer
+ while( ( pos = line.indexOf( '\t' ) ) != -1 )
+ {
+ String token = line.substring( 0, pos );
+ // only parse what coudl be a valid number. ie not constructs nor no value
+ /*
+ * if (metrics.size() != 0 || token.length() != 0){
+ * Number num = METAMATA_NF.parse(token); // parse with Metamata NF
+ * token = NEUTRAL_NF.format(num.doubleValue()); // and format with a neutral NF
+ * }
+ */
+ metrics.addElement( token );
+ line = line.substring( pos + 1 );
+ }
+ metrics.addElement( line );
+
+ // there should be exactly 14 tokens (1 name + 13 metrics), if not, there is a problem !
+ if( metrics.size() != 14 )
+ {
+ throw new ParseException( "Could not parse the following line as a metrics: -->" + line + "<--", -1 );
+ }
+
+ // remove the first token it's made of the indentation string and the
+ // construct name, we'll need all this to figure out what type of
+ // construct it is since we lost all semantics :(
+ // (#indent[/]*)(#construct.*)
+ String name = ( String )metrics.elementAt( 0 );
+ metrics.removeElementAt( 0 );
+ int indent = 0;
+ pos = name.lastIndexOf( '/' );
+ if( pos != -1 )
+ {
+ name = name.substring( pos + 1 );
+ indent = pos + 1;// indentation is last position of token + 1
+ }
+ return new MetricsElement( indent, name, metrics );
+ }
+
+ public int getIndent()
+ {
+ return indent;
+ }
+
+ public Enumeration getMetrics()
+ {
+ return metrics.elements();
+ }
+
+ public String getName()
+ {
+ return construct;
+ }
+
+ public boolean isCompilationUnit()
+ {
+ return ( construct.endsWith( ".java" ) || construct.endsWith( ".class" ) );
+ }
+
+ public boolean isMethod()
+ {
+ return ( construct.endsWith( "(...)" ) || construct.endsWith( "()" ) );
+ }
+}
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MParse.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MParse.java
new file mode 100644
index 000000000..efbc76c0a
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MParse.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.Random;
+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.taskdefs.Execute;
+import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
+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;
+
+/**
+ * Simple Metamata MParse task based on the original written by Thomas Haas This version was
+ * written for Metamata 2.0 available at
+ * http://www.metamata.com
+ *
+ * @author Stephane Bailliez
+ */
+public class MParse extends Task
+{
+
+ private Path classpath = null;
+ private Path sourcepath = null;
+ private File metahome = null;
+ private File target = null;
+ private boolean verbose = false;
+ private boolean debugparser = false;
+ private boolean debugscanner = false;
+ private boolean cleanup = false;
+ private CommandlineJava cmdl = new CommandlineJava();
+ private File optionsFile = null;
+
+ public MParse()
+ {
+ cmdl.setVm( "java" );
+ cmdl.setClassname( "com.metamata.jj.MParse" );
+ }
+
+ /**
+ * create a temporary file in the current directory
+ *
+ * @return Description of the Returned Value
+ */
+ 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;
+ }
+
+ /**
+ * set the hack to cleanup the temp file
+ *
+ * @param value The new Cleanup value
+ */
+ public void setCleanup( boolean value )
+ {
+ cleanup = value;
+ }
+
+ /**
+ * set parser debug mode
+ *
+ * @param flag The new Debugparser value
+ */
+ public void setDebugparser( boolean flag )
+ {
+ debugparser = flag;
+ }
+
+ /**
+ * set scanner debug mode
+ *
+ * @param flag The new Debugscanner value
+ */
+ public void setDebugscanner( boolean flag )
+ {
+ debugscanner = flag;
+ }
+
+ /**
+ * -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 );
+ }
+ }
+
+ /**
+ * location of metamata dev environment
+ *
+ * @param metamatahome The new Metamatahome value
+ */
+ public void setMetamatahome( File metamatahome )
+ {
+ this.metahome = metamatahome;
+ }
+
+ /**
+ * the .jj file to process
+ *
+ * @param target The new Target value
+ */
+ public void setTarget( File target )
+ {
+ this.target = target;
+ }
+
+ /**
+ * set verbose mode
+ *
+ * @param flag The new Verbose value
+ */
+ public void setVerbose( boolean flag )
+ {
+ verbose = flag;
+ }
+
+ /**
+ * create a classpath entry
+ *
+ * @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();
+ }
+
+ /**
+ * creates a sourcepath entry
+ *
+ * @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();
+ _execute( handler );
+ }
+ finally
+ {
+ cleanUp();
+ }
+ }
+
+ /**
+ * 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 files
+ File[] jars = getMetamataLibs();
+ final Path classPath = cmdl.createClasspath( project );
+ for( int i = 0; i < jars.length; i++ )
+ {
+ classPath.createPathElement().setLocation( jars[i] );
+ }
+
+ // set the metamata.home property
+ final Commandline.Argument vmArgs = cmdl.createVmArgument();
+ vmArgs.setValue( "-Dmetamata.home=" + metahome.getAbsolutePath() );
+
+ // write all the options to a temp file and use it ro run the process
+ String[] options = getOptions();
+ optionsFile = createTmpFile();
+ generateOptionsFile( optionsFile, options );
+ Commandline.Argument args = cmdl.createArgument();
+ args.setLine( "-arguments " + optionsFile.getAbsolutePath() );
+ }
+
+ /**
+ * return an array of files containing the path to the needed libraries to
+ * run metamata. The file are not checked for existence. You should do this
+ * yourself if needed or simply let the forked process do it for you.
+ *
+ * @return array of jars/zips needed to run metamata.
+ */
+ protected File[] getMetamataLibs()
+ {
+ Vector files = new Vector();
+ files.addElement( new File( metahome, "lib/metamata.jar" ) );
+ files.addElement( new File( metahome, "bin/lib/JavaCC.zip" ) );
+
+ File[] array = new File[files.size()];
+ files.copyInto( array );
+ return array;
+ }
+
+ /**
+ * return all options of the command line as string elements
+ *
+ * @return The Options value
+ */
+ protected String[] getOptions()
+ {
+ Vector options = new Vector();
+ if( verbose )
+ {
+ options.addElement( "-verbose" );
+ }
+ if( debugscanner )
+ {
+ options.addElement( "-ds" );
+ }
+ if( debugparser )
+ {
+ options.addElement( "-dp" );
+ }
+ if( classpath != null )
+ {
+ options.addElement( "-classpath" );
+ options.addElement( classpath.toString() );
+ }
+ if( sourcepath != null )
+ {
+ options.addElement( "-sourcepath" );
+ options.addElement( sourcepath.toString() );
+ }
+ options.addElement( target.getAbsolutePath() );
+
+ String[] array = new String[options.size()];
+ options.copyInto( array );
+ return array;
+ }
+
+
+ /**
+ * execute the process with a specific handler
+ *
+ * @param handler Description of Parameter
+ * @exception BuildException Description of Exception
+ */
+ protected void _execute( ExecuteStreamHandler handler )
+ throws BuildException
+ {
+ // target has been checked as a .jj, see if there is a matching
+ // java file and if it is needed to run to process the grammar
+ String pathname = target.getAbsolutePath();
+ int pos = pathname.length() - ".jj".length();
+ pathname = pathname.substring( 0, pos ) + ".java";
+ File javaFile = new File( pathname );
+ if( javaFile.exists() && target.lastModified() < javaFile.lastModified() )
+ {
+ project.log( "Target is already build - skipping (" + target + ")" );
+ return;
+ }
+
+ 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 );
+ }
+ }
+
+
+ /**
+ * validate options set and resolve files and paths
+ *
+ * @throws BuildException thrown if an option has an incorrect state.
+ */
+ protected void checkOptions()
+ throws BuildException
+ {
+ // check that the home is ok.
+ if( metahome == null || !metahome.exists() )
+ {
+ throw new BuildException( "'metamatahome' must point to Metamata home directory." );
+ }
+ metahome = project.resolveFile( metahome.getPath() );
+
+ // check that the needed jar exists.
+ File[] jars = getMetamataLibs();
+ for( int i = 0; i < jars.length; i++ )
+ {
+ if( !jars[i].exists() )
+ {
+ throw new BuildException( jars[i] + " does not exist. Check your metamata installation." );
+ }
+ }
+
+ // check that the target is ok and resolve it.
+ if( target == null || !target.isFile() || !target.getName().endsWith( ".jj" ) )
+ {
+ throw new BuildException( "Invalid target: " + target );
+ }
+ target = project.resolveFile( target.getPath() );
+ }
+
+ /**
+ * clean up all the mess that we did with temporary objects
+ */
+ protected void cleanUp()
+ {
+ if( optionsFile != null )
+ {
+ optionsFile.delete();
+ optionsFile = null;
+ }
+ if( cleanup )
+ {
+ String name = target.getName();
+ int pos = name.length() - ".jj".length();
+ name = "__jj" + name.substring( 0, pos ) + ".sunjj";
+ final File sunjj = new File( target.getParent(), name );
+ if( sunjj.exists() )
+ {
+ project.log( "Removing stale file: " + sunjj.getName() );
+ sunjj.delete();
+ }
+ }
+ }
+
+ /**
+ * return the default stream handler for this task
+ *
+ * @return Description of the Returned Value
+ */
+ protected ExecuteStreamHandler createStreamHandler()
+ {
+ return new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_INFO );
+ }
+
+ /**
+ * write all options to a file with one option / line
+ *
+ * @param tofile the file to write the options to.
+ * @param options the array of options element to write to the file.
+ * @throws BuildException thrown if there is a problem while writing to the
+ * file.
+ */
+ protected void generateOptionsFile( File tofile, String[] options )
+ throws BuildException
+ {
+ FileWriter fw = null;
+ try
+ {
+ fw = new FileWriter( tofile );
+ PrintWriter pw = new PrintWriter( fw );
+ for( int i = 0; i < options.length; i++ )
+ {
+ pw.println( options[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 )
+ {}
+ }
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java
new file mode 100644
index 000000000..5d6ba1f1c
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java
@@ -0,0 +1,1106 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.net;
+import com.oroinc.net.ftp.FTPClient;
+import com.oroinc.net.ftp.FTPFile;
+import com.oroinc.net.ftp.FTPReply;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+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.FileScanner;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+import org.apache.tools.ant.types.FileSet;
+
+/**
+ * Basic FTP client that performs the following actions:
+ *
+ * send - send files to a remote server. This is the
+ * default action.
+ * get - retrive files from a remote server.
+ * del - delete files from a remote server.
+ * list - create a file listing.
+ *
+ * Note: Some FTP servers - notably the Solaris server - seem
+ * to hold data ports open after a "retr" operation, allowing them to timeout
+ * instead of shutting them down cleanly. This happens in active or passive
+ * mode, and the ports will remain open even after ending the FTP session. FTP
+ * "send" operations seem to close ports immediately. This behavior may cause
+ * problems on some systems when downloading large sets of files.
+ *
+ * @author Roger Vaughn
+ * rvaughn@seaconinc.com
+ * @author Glenn McAllister glennm@ca.ibm.com
+ *
+ * @author Magesh Umasankar
+ */
+public class FTP
+ extends Task
+{
+ protected final static int SEND_FILES = 0;
+ protected final static int GET_FILES = 1;
+ protected final static int DEL_FILES = 2;
+ protected final static int LIST_FILES = 3;
+ protected final static int MK_DIR = 4;
+
+ protected final static String[] ACTION_STRS = {
+ "sending",
+ "getting",
+ "deleting",
+ "listing",
+ "making directory"
+ };
+
+ protected final static String[] COMPLETED_ACTION_STRS = {
+ "sent",
+ "retrieved",
+ "deleted",
+ "listed",
+ "created directory"
+ };
+ private boolean binary = true;
+ private boolean passive = false;
+ private boolean verbose = false;
+ private boolean newerOnly = false;
+ private int action = SEND_FILES;
+ private Vector filesets = new Vector();
+ private Vector dirCache = new Vector();
+ private int transferred = 0;
+ private String remoteFileSep = "/";
+ private int port = 21;
+ private boolean skipFailedTransfers = false;
+ private int skipped = 0;
+ private boolean ignoreNoncriticalErrors = false;
+ private File listing;
+ private String password;
+
+ private String remotedir;
+ private String server;
+ private String userid;
+
+ /**
+ * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
+ * "mkdir" and "list".
+ *
+ * @param action The new Action value
+ * @exception BuildException Description of Exception
+ * @deprecated setAction(String) is deprecated and is replaced with
+ * setAction(FTP.Action) to make Ant's Introspection mechanism do the
+ * work and also to encapsulate operations on the type in its own
+ * class.
+ */
+ public void setAction( String action )
+ throws BuildException
+ {
+ log( "DEPRECATED - The setAction(String) method has been deprecated."
+ + " Use setAction(FTP.Action) instead." );
+ Action a = new Action();
+ a.setValue( action );
+ this.action = a.getAction();
+ }
+
+ /**
+ * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
+ * "mkdir" and "list".
+ *
+ * @param action The new Action value
+ * @exception BuildException Description of Exception
+ */
+ public void setAction( Action action )
+ throws BuildException
+ {
+ this.action = action.getAction();
+ }
+
+ /**
+ * Specifies whether to use binary-mode or text-mode transfers. Set to true
+ * to send binary mode. Binary mode is enabled by default.
+ *
+ * @param binary The new Binary value
+ */
+ public void setBinary( boolean binary )
+ {
+ this.binary = binary;
+ }
+
+ /**
+ * A synonym for setNewer. Set to true to transmit only new or changed
+ * files.
+ *
+ * @param depends The new Depends value
+ */
+ public void setDepends( boolean depends )
+ {
+ this.newerOnly = depends;
+ }
+
+ /**
+ * set the flag to skip errors on dir creation (and maybe later other server
+ * specific errors)
+ *
+ * @param ignoreNoncriticalErrors The new IgnoreNoncriticalErrors value
+ */
+ public void setIgnoreNoncriticalErrors( boolean ignoreNoncriticalErrors )
+ {
+ this.ignoreNoncriticalErrors = ignoreNoncriticalErrors;
+ }
+
+ /**
+ * The output file for the "list" action. This attribute is ignored for any
+ * other actions.
+ *
+ * @param listing The new Listing value
+ * @exception BuildException Description of Exception
+ */
+ public void setListing( File listing )
+ throws BuildException
+ {
+ this.listing = listing;
+ }
+
+ /**
+ * Set to true to transmit only files that are new or changed from their
+ * remote counterparts. The default is to transmit all files.
+ *
+ * @param newer The new Newer value
+ */
+ public void setNewer( boolean newer )
+ {
+ this.newerOnly = newer;
+ }
+
+ /**
+ * Specifies whether to use passive mode. Set to true if you are behind a
+ * firewall and cannot connect without it. Passive mode is disabled by
+ * default.
+ *
+ * @param passive The new Passive value
+ */
+ public void setPassive( boolean passive )
+ {
+ this.passive = passive;
+ }
+
+ /**
+ * Sets the login password for the given user id.
+ *
+ * @param password The new Password value
+ */
+ public void setPassword( String password )
+ {
+ this.password = password;
+ }
+
+ /**
+ * Sets the FTP port used by the remote server.
+ *
+ * @param port The new Port value
+ */
+ public void setPort( int port )
+ {
+ this.port = port;
+ }
+
+ /**
+ * Sets the remote directory where files will be placed. This may be a
+ * relative or absolute path, and must be in the path syntax expected by the
+ * remote server. No correction of path syntax will be performed.
+ *
+ * @param dir The new Remotedir value
+ */
+ public void setRemotedir( String dir )
+ {
+ this.remotedir = dir;
+ }
+
+ /**
+ * Sets the remote file separator character. This normally defaults to the
+ * Unix standard forward slash, but can be manually overridden using this
+ * call if the remote server requires some other separator. Only the first
+ * character of the string is used.
+ *
+ * @param separator The new Separator value
+ */
+ public void setSeparator( String separator )
+ {
+ remoteFileSep = separator;
+ }
+
+ /**
+ * Sets the FTP server to send files to.
+ *
+ * @param server The new Server value
+ */
+ public void setServer( String server )
+ {
+ this.server = server;
+ }
+
+
+ /**
+ * set the failed transfer flag
+ *
+ * @param skipFailedTransfers The new SkipFailedTransfers value
+ */
+ public void setSkipFailedTransfers( boolean skipFailedTransfers )
+ {
+ this.skipFailedTransfers = skipFailedTransfers;
+ }
+
+ /**
+ * Sets the login user id to use on the specified server.
+ *
+ * @param userid The new Userid value
+ */
+ public void setUserid( String userid )
+ {
+ this.userid = userid;
+ }
+
+ /**
+ * Set to true to receive notification about each file as it is transferred.
+ *
+ * @param verbose The new Verbose value
+ */
+ public void setVerbose( 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( FileSet set )
+ {
+ filesets.addElement( set );
+ }
+
+ /**
+ * Runs the task.
+ *
+ * @exception BuildException Description of Exception
+ */
+ public void execute()
+ throws BuildException
+ {
+ checkConfiguration();
+
+ FTPClient ftp = null;
+
+ try
+ {
+ log( "Opening FTP connection to " + server, Project.MSG_VERBOSE );
+
+ ftp = new FTPClient();
+
+ ftp.connect( server, port );
+ if( !FTPReply.isPositiveCompletion( ftp.getReplyCode() ) )
+ {
+ throw new BuildException( "FTP connection failed: " + ftp.getReplyString() );
+ }
+
+ log( "connected", Project.MSG_VERBOSE );
+ log( "logging in to FTP server", Project.MSG_VERBOSE );
+
+ if( !ftp.login( userid, password ) )
+ {
+ throw new BuildException( "Could not login to FTP server" );
+ }
+
+ log( "login succeeded", Project.MSG_VERBOSE );
+
+ if( binary )
+ {
+ ftp.setFileType( com.oroinc.net.ftp.FTP.IMAGE_FILE_TYPE );
+ if( !FTPReply.isPositiveCompletion( ftp.getReplyCode() ) )
+ {
+ throw new BuildException(
+ "could not set transfer type: " +
+ ftp.getReplyString() );
+ }
+ }
+
+ if( passive )
+ {
+ log( "entering passive mode", Project.MSG_VERBOSE );
+ ftp.enterLocalPassiveMode();
+ if( !FTPReply.isPositiveCompletion( ftp.getReplyCode() ) )
+ {
+ throw new BuildException(
+ "could not enter into passive mode: " +
+ ftp.getReplyString() );
+ }
+ }
+
+ // If the action is MK_DIR, then the specified remote directory is the
+ // directory to create.
+
+ if( action == MK_DIR )
+ {
+
+ makeRemoteDir( ftp, remotedir );
+
+ }
+ else
+ {
+ if( remotedir != null )
+ {
+ log( "changing the remote directory", Project.MSG_VERBOSE );
+ ftp.changeWorkingDirectory( remotedir );
+ if( !FTPReply.isPositiveCompletion( ftp.getReplyCode() ) )
+ {
+ throw new BuildException(
+ "could not change remote directory: " +
+ ftp.getReplyString() );
+ }
+ }
+ log( ACTION_STRS[action] + " files" );
+ transferFiles( ftp );
+ }
+
+ }
+ catch( IOException ex )
+ {
+ throw new BuildException( "error during FTP transfer: " + ex );
+ }
+ finally
+ {
+ if( ftp != null && ftp.isConnected() )
+ {
+ try
+ {
+ log( "disconnecting", Project.MSG_VERBOSE );
+ ftp.logout();
+ ftp.disconnect();
+ }
+ catch( IOException ex )
+ {
+ // ignore it
+ }
+ }
+ }
+ }
+
+ /**
+ * Retrieve a single file to the remote host. filename may
+ * contain a relative path specification. The file will then be retreived
+ * using the entire relative path spec - no attempt is made to change
+ * directories. It is anticipated that this may eventually cause problems
+ * with some FTP servers, but it simplifies the coding.
+ *
+ * @param ftp Description of Parameter
+ * @param dir Description of Parameter
+ * @param filename Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void getFile( FTPClient ftp, String dir, String filename )
+ throws IOException, BuildException
+ {
+ OutputStream outstream = null;
+ try
+ {
+ File file = project.resolveFile( new File( dir, filename ).getPath() );
+
+ if( newerOnly && isUpToDate( ftp, file, resolveFile( filename ) ) )
+ return;
+
+ if( verbose )
+ {
+ log( "transferring " + filename + " to " + file.getAbsolutePath() );
+ }
+
+ File pdir = new File( file.getParent() );// stay 1.1 compatible
+ if( !pdir.exists() )
+ {
+ pdir.mkdirs();
+ }
+ outstream = new BufferedOutputStream( new FileOutputStream( file ) );
+ ftp.retrieveFile( resolveFile( filename ), outstream );
+
+ if( !FTPReply.isPositiveCompletion( ftp.getReplyCode() ) )
+ {
+ String s = "could not get file: " + ftp.getReplyString();
+ if( skipFailedTransfers == true )
+ {
+ log( s, Project.MSG_WARN );
+ skipped++;
+ }
+ else
+ {
+ throw new BuildException( s );
+ }
+
+ }
+ else
+ {
+ log( "File " + file.getAbsolutePath() + " copied from " + server,
+ Project.MSG_VERBOSE );
+ transferred++;
+ }
+ }
+ finally
+ {
+ if( outstream != null )
+ {
+ try
+ {
+ outstream.close();
+ }
+ catch( IOException ex )
+ {
+ // ignore it
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks to see if the remote file is current as compared with the local
+ * file. Returns true if the remote file is up to date.
+ *
+ * @param ftp Description of Parameter
+ * @param localFile Description of Parameter
+ * @param remoteFile Description of Parameter
+ * @return The UpToDate value
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected boolean isUpToDate( FTPClient ftp, File localFile, String remoteFile )
+ throws IOException, BuildException
+ {
+ log( "checking date for " + remoteFile, Project.MSG_VERBOSE );
+
+ FTPFile[] files = ftp.listFiles( remoteFile );
+
+ // For Microsoft's Ftp-Service an Array with length 0 is
+ // returned if configured to return listings in "MS-DOS"-Format
+ if( files == null || files.length == 0 )
+ {
+ // If we are sending files, then assume out of date.
+ // If we are getting files, then throw an error
+
+ if( action == SEND_FILES )
+ {
+ log( "Could not date test remote file: " + remoteFile
+ + "assuming out of date.", Project.MSG_VERBOSE );
+ return false;
+ }
+ else
+ {
+ throw new BuildException( "could not date test remote file: " +
+ ftp.getReplyString() );
+ }
+ }
+
+ long remoteTimestamp = files[0].getTimestamp().getTime().getTime();
+ long localTimestamp = localFile.lastModified();
+ if( this.action == SEND_FILES )
+ {
+ return remoteTimestamp > localTimestamp;
+ }
+ else
+ {
+ return localTimestamp > remoteTimestamp;
+ }
+ }
+
+ /**
+ * Checks to see that all required parameters are set.
+ *
+ * @exception BuildException Description of Exception
+ */
+ protected void checkConfiguration()
+ throws BuildException
+ {
+ if( server == null )
+ {
+ throw new BuildException( "server attribute must be set!" );
+ }
+ if( userid == null )
+ {
+ throw new BuildException( "userid attribute must be set!" );
+ }
+ if( password == null )
+ {
+ throw new BuildException( "password attribute must be set!" );
+ }
+
+ if( ( action == LIST_FILES ) && ( listing == null ) )
+ {
+ throw new BuildException( "listing attribute must be set for list action!" );
+ }
+
+ if( action == MK_DIR && remotedir == null )
+ {
+ throw new BuildException( "remotedir attribute must be set for mkdir action!" );
+ }
+ }
+
+ /**
+ * Creates all parent directories specified in a complete relative pathname.
+ * Attempts to create existing directories will not cause errors.
+ *
+ * @param ftp Description of Parameter
+ * @param filename Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void createParents( FTPClient ftp, String filename )
+ throws IOException, BuildException
+ {
+ Vector parents = new Vector();
+ File dir = new File( filename );
+ String dirname;
+
+ while( ( dirname = dir.getParent() ) != null )
+ {
+ dir = new File( dirname );
+ parents.addElement( dir );
+ }
+
+ for( int i = parents.size() - 1; i >= 0; i-- )
+ {
+ dir = ( File )parents.elementAt( i );
+ if( !dirCache.contains( dir ) )
+ {
+ log( "creating remote directory " + resolveFile( dir.getPath() ),
+ Project.MSG_VERBOSE );
+ ftp.makeDirectory( resolveFile( dir.getPath() ) );
+ // Both codes 550 and 553 can be produced by FTP Servers
+ // to indicate that an attempt to create a directory has
+ // failed because the directory already exists.
+ int result = ftp.getReplyCode();
+ if( !FTPReply.isPositiveCompletion( result ) &&
+ ( result != 550 ) && ( result != 553 ) &&
+ !ignoreNoncriticalErrors )
+ {
+ throw new BuildException(
+ "could not create directory: " +
+ ftp.getReplyString() );
+ }
+ dirCache.addElement( dir );
+ }
+ }
+ }
+
+ /**
+ * Delete a file from the remote host.
+ *
+ * @param ftp Description of Parameter
+ * @param filename Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void delFile( FTPClient ftp, String filename )
+ throws IOException, BuildException
+ {
+ if( verbose )
+ {
+ log( "deleting " + filename );
+ }
+
+ if( !ftp.deleteFile( resolveFile( filename ) ) )
+ {
+ String s = "could not delete file: " + ftp.getReplyString();
+ if( skipFailedTransfers == true )
+ {
+ log( s, Project.MSG_WARN );
+ skipped++;
+ }
+ else
+ {
+ throw new BuildException( s );
+ }
+ }
+ else
+ {
+ log( "File " + filename + " deleted from " + server, Project.MSG_VERBOSE );
+ transferred++;
+ }
+ }
+
+ /**
+ * List information about a single file from the remote host. filename
+ * may contain a relative path specification. The file listing will then be
+ * retrieved using the entire relative path spec - no attempt is made to
+ * change directories. It is anticipated that this may eventually cause
+ * problems with some FTP servers, but it simplifies the coding.
+ *
+ * @param ftp Description of Parameter
+ * @param bw Description of Parameter
+ * @param filename Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void listFile( FTPClient ftp, BufferedWriter bw, String filename )
+ throws IOException, BuildException
+ {
+ if( verbose )
+ {
+ log( "listing " + filename );
+ }
+
+ FTPFile ftpfile = ftp.listFiles( resolveFile( filename ) )[0];
+ bw.write( ftpfile.toString() );
+ bw.newLine();
+
+ transferred++;
+ }
+
+ /**
+ * Create the specified directory on the remote host.
+ *
+ * @param ftp The FTP client connection
+ * @param dir The directory to create (format must be correct for host type)
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void makeRemoteDir( FTPClient ftp, String dir )
+ throws IOException, BuildException
+ {
+ if( verbose )
+ {
+ log( "creating directory: " + dir );
+ }
+
+ if( !ftp.makeDirectory( dir ) )
+ {
+ // codes 521, 550 and 553 can be produced by FTP Servers
+ // to indicate that an attempt to create a directory has
+ // failed because the directory already exists.
+
+ int rc = ftp.getReplyCode();
+ if( !( ignoreNoncriticalErrors && ( rc == 550 || rc == 553 || rc == 521 ) ) )
+ {
+ throw new BuildException( "could not create directory: " +
+ ftp.getReplyString() );
+ }
+
+ if( verbose )
+ {
+ log( "directory already exists" );
+ }
+ }
+ else
+ {
+ if( verbose )
+ {
+ log( "directory created OK" );
+ }
+ }
+ }
+
+ /**
+ * Correct a file path to correspond to the remote host requirements. This
+ * implementation currently assumes that the remote end can handle
+ * Unix-style paths with forward-slash separators. This can be overridden
+ * with the separator task parameter. No attempt is made to
+ * determine what syntax is appropriate for the remote host.
+ *
+ * @param file Description of Parameter
+ * @return Description of the Returned Value
+ */
+ protected String resolveFile( String file )
+ {
+ return file.replace( System.getProperty( "file.separator" ).charAt( 0 ),
+ remoteFileSep.charAt( 0 ) );
+ }
+
+ /**
+ * Sends a single file to the remote host. filename may contain
+ * a relative path specification. When this is the case, sendFile
+ * will attempt to create any necessary parent directories before sending
+ * the file. The file will then be sent using the entire relative path spec
+ * - no attempt is made to change directories. It is anticipated that this
+ * may eventually cause problems with some FTP servers, but it simplifies
+ * the coding.
+ *
+ * @param ftp Description of Parameter
+ * @param dir Description of Parameter
+ * @param filename Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void sendFile( FTPClient ftp, String dir, String filename )
+ throws IOException, BuildException
+ {
+ InputStream instream = null;
+ try
+ {
+ File file = project.resolveFile( new File( dir, filename ).getPath() );
+
+ if( newerOnly && isUpToDate( ftp, file, resolveFile( filename ) ) )
+ return;
+
+ if( verbose )
+ {
+ log( "transferring " + file.getAbsolutePath() );
+ }
+
+ instream = new BufferedInputStream( new FileInputStream( file ) );
+
+ createParents( ftp, filename );
+
+ ftp.storeFile( resolveFile( filename ), instream );
+ boolean success = FTPReply.isPositiveCompletion( ftp.getReplyCode() );
+ if( !success )
+ {
+ String s = "could not put file: " + ftp.getReplyString();
+ if( skipFailedTransfers == true )
+ {
+ log( s, Project.MSG_WARN );
+ skipped++;
+ }
+ else
+ {
+ throw new BuildException( s );
+ }
+
+ }
+ else
+ {
+
+ log( "File " + file.getAbsolutePath() +
+ " copied to " + server,
+ Project.MSG_VERBOSE );
+ transferred++;
+ }
+ }
+ finally
+ {
+ if( instream != null )
+ {
+ try
+ {
+ instream.close();
+ }
+ catch( IOException ex )
+ {
+ // ignore it
+ }
+ }
+ }
+ }
+
+ /**
+ * For each file in the fileset, do the appropriate action: send, get,
+ * delete, or list.
+ *
+ * @param ftp Description of Parameter
+ * @param fs Description of Parameter
+ * @return Description of the Returned Value
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected int transferFiles( FTPClient ftp, FileSet fs )
+ throws IOException, BuildException
+ {
+ FileScanner ds;
+
+ if( action == SEND_FILES )
+ {
+ ds = fs.getDirectoryScanner( project );
+ }
+ else
+ {
+ ds = new FTPDirectoryScanner( ftp );
+ fs.setupDirectoryScanner( ds, project );
+ ds.scan();
+ }
+
+ String[] dsfiles = ds.getIncludedFiles();
+ String dir = null;
+ if( ( ds.getBasedir() == null ) && ( ( action == SEND_FILES ) || ( action == GET_FILES ) ) )
+ {
+ throw new BuildException( "the dir attribute must be set for send and get actions" );
+ }
+ else
+ {
+ if( ( action == SEND_FILES ) || ( action == GET_FILES ) )
+ {
+ dir = ds.getBasedir().getAbsolutePath();
+ }
+ }
+
+ // If we are doing a listing, we need the output stream created now.
+ BufferedWriter bw = null;
+ if( action == LIST_FILES )
+ {
+ File pd = new File( listing.getParent() );
+ if( !pd.exists() )
+ {
+ pd.mkdirs();
+ }
+ bw = new BufferedWriter( new FileWriter( listing ) );
+ }
+
+ for( int i = 0; i < dsfiles.length; i++ )
+ {
+ switch ( action )
+ {
+ case SEND_FILES:
+ {
+ sendFile( ftp, dir, dsfiles[i] );
+ break;
+ }
+
+ case GET_FILES:
+ {
+ getFile( ftp, dir, dsfiles[i] );
+ break;
+ }
+
+ case DEL_FILES:
+ {
+ delFile( ftp, dsfiles[i] );
+ break;
+ }
+
+ case LIST_FILES:
+ {
+ listFile( ftp, bw, dsfiles[i] );
+ break;
+ }
+
+ default:
+ {
+ throw new BuildException( "unknown ftp action " + action );
+ }
+ }
+ }
+
+ if( action == LIST_FILES )
+ {
+ bw.close();
+ }
+
+ return dsfiles.length;
+ }
+
+ /**
+ * Sends all files specified by the configured filesets to the remote
+ * server.
+ *
+ * @param ftp Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void transferFiles( FTPClient ftp )
+ throws IOException, BuildException
+ {
+ transferred = 0;
+ skipped = 0;
+
+ if( filesets.size() == 0 )
+ {
+ throw new BuildException( "at least one fileset must be specified." );
+ }
+ else
+ {
+ // get files from filesets
+ for( int i = 0; i < filesets.size(); i++ )
+ {
+ FileSet fs = ( FileSet )filesets.elementAt( i );
+ if( fs != null )
+ {
+ transferFiles( ftp, fs );
+ }
+ }
+ }
+
+ log( transferred + " files " + COMPLETED_ACTION_STRS[action] );
+ if( skipped != 0 )
+ {
+ log( skipped + " files were not successfully " + COMPLETED_ACTION_STRS[action] );
+ }
+ }
+
+ public static class Action extends EnumeratedAttribute
+ {
+
+ private final static String[] validActions = {
+ "send", "put", "recv", "get", "del", "delete", "list", "mkdir"
+ };
+
+ public int getAction()
+ {
+ String actionL = getValue().toLowerCase( Locale.US );
+ if( actionL.equals( "send" ) ||
+ actionL.equals( "put" ) )
+ {
+ return SEND_FILES;
+ }
+ else if( actionL.equals( "recv" ) ||
+ actionL.equals( "get" ) )
+ {
+ return GET_FILES;
+ }
+ else if( actionL.equals( "del" ) ||
+ actionL.equals( "delete" ) )
+ {
+ return DEL_FILES;
+ }
+ else if( actionL.equals( "list" ) )
+ {
+ return LIST_FILES;
+ }
+ else if( actionL.equals( "mkdir" ) )
+ {
+ return MK_DIR;
+ }
+ return SEND_FILES;
+ }
+
+ public String[] getValues()
+ {
+ return validActions;
+ }
+ }
+
+ protected class FTPDirectoryScanner extends DirectoryScanner
+ {
+ protected FTPClient ftp = null;
+
+ public FTPDirectoryScanner( FTPClient ftp )
+ {
+ super();
+ this.ftp = ftp;
+ }
+
+ 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];
+ }
+
+ filesIncluded = new Vector();
+ filesNotIncluded = new Vector();
+ filesExcluded = new Vector();
+ dirsIncluded = new Vector();
+ dirsNotIncluded = new Vector();
+ dirsExcluded = new Vector();
+
+ try
+ {
+ String cwd = ftp.printWorkingDirectory();
+ scandir( ".", "", true );// always start from the current ftp working dir
+ ftp.changeWorkingDirectory( cwd );
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Unable to scan FTP server: ", e );
+ }
+ }
+
+ protected void scandir( String dir, String vpath, boolean fast )
+ {
+ try
+ {
+ if( !ftp.changeWorkingDirectory( dir ) )
+ {
+ return;
+ }
+
+ FTPFile[] newfiles = ftp.listFiles();
+ if( newfiles == null )
+ {
+ ftp.changeToParentDirectory();
+ return;
+ }
+
+ for( int i = 0; i < newfiles.length; i++ )
+ {
+ FTPFile file = newfiles[i];
+ if( !file.getName().equals( "." ) && !file.getName().equals( ".." ) )
+ {
+ if( file.isDirectory() )
+ {
+ String name = file.getName();
+ if( isIncluded( name ) )
+ {
+ if( !isExcluded( name ) )
+ {
+ dirsIncluded.addElement( name );
+ if( fast )
+ {
+ scandir( name, vpath + name + File.separator, fast );
+ }
+ }
+ else
+ {
+ dirsExcluded.addElement( name );
+ }
+ }
+ else
+ {
+ dirsNotIncluded.addElement( name );
+ if( fast && couldHoldIncluded( name ) )
+ {
+ scandir( name, vpath + name + File.separator, fast );
+ }
+ }
+ if( !fast )
+ {
+ scandir( name, vpath + name + File.separator, fast );
+ }
+ }
+ else
+ {
+ if( file.isFile() )
+ {
+ String name = vpath + file.getName();
+ if( isIncluded( name ) )
+ {
+ if( !isExcluded( name ) )
+ {
+ filesIncluded.addElement( name );
+ }
+ else
+ {
+ filesExcluded.addElement( name );
+ }
+ }
+ else
+ {
+ filesNotIncluded.addElement( name );
+ }
+ }
+ }
+ }
+ }
+ ftp.changeToParentDirectory();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Error while communicating with FTP server: ", e );
+ }
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/MimeMail.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/MimeMail.java
new file mode 100644
index 000000000..1a498c6df
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/MimeMail.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.net;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;// Standard SDK imports
+import java.util.Properties;
+import java.util.Vector;//imported for data source and handler
+import javax.activation.DataHandler;
+import javax.activation.FileDataSource;
+import javax.mail.Message;
+import javax.mail.MessagingException;//imported for the mail api
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;// Ant imports
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+
+
+/**
+ * A task to send SMTP email. This version has near identical syntax to the
+ * SendEmail task, but is MIME aware. It also requires Sun's mail.jar and
+ * activation.jar to compile and execute, which puts it clearly into the very
+ * optional category.
+ *
+ * @author glenn_twiggs@bmc.com
+ * @author steve_l@iseran.com steve loughran
+ * @author erik@hatcher.net Erik Hatcher
+ * @author paulo.gaspar@krankikom.de Paulo Gaspar
+ * @created 01 May 2001
+ */
+public class MimeMail extends Task
+{
+ /**
+ * failure flag
+ */
+ private boolean failOnError = true;
+
+ /**
+ * sender
+ */
+ private String from = null;
+
+ /**
+ * host running SMTP
+ */
+ private String mailhost = "localhost";
+
+ /**
+ * any text
+ */
+ private String message = null;
+
+ /**
+ * message file (mutually exclusive from message)
+ */
+ private File messageFile = null;
+
+ /**
+ * TO recipients
+ */
+ private String toList = null;
+
+ /**
+ * CC (Carbon Copy) recipients
+ */
+ protected String ccList = null;
+
+ /**
+ * BCC (Blind Carbon Copy) recipients
+ */
+ protected String bccList = null;
+
+ /**
+ * subject field
+ */
+ private String subject = null;
+
+ /**
+ * file list
+ */
+ private Vector filesets = new Vector();
+
+ /**
+ * type of the text message, plaintext by default but text/html or text/xml
+ * is quite feasible
+ */
+ private String messageMimeType = "text/plain";
+
+ /**
+ * Creates new instance
+ */
+ public MimeMail() { }
+
+
+ // helper method to add recipients
+ private static void addRecipients( MimeMessage msg,
+ Message.RecipientType recipType,
+ String addrUserName,
+ String addrList
+ )
+ throws MessagingException, BuildException
+ {
+ if( ( null == addrList ) || ( addrList.trim().length() <= 0 ) )
+ return;
+
+ try
+ {
+ InternetAddress[] addrArray = InternetAddress.parse( addrList );
+
+ if( ( null == addrArray ) || ( 0 == addrArray.length ) )
+ throw new BuildException( "Empty " + addrUserName + " recipients list was specified" );
+
+ msg.setRecipients( recipType, addrArray );
+ }
+ catch( AddressException ae )
+ {
+ throw new BuildException( "Invalid " + addrUserName + " recipient list" );
+ }
+ }
+
+ /**
+ * Sets the toList parameter of this build task.
+ *
+ * @param bccList The new BccList value
+ */
+ public void setBccList( String bccList )
+ {
+ this.bccList = bccList;
+ }
+
+ /**
+ * Sets the toList parameter of this build task.
+ *
+ * @param ccList The new CcList value
+ */
+ public void setCcList( String ccList )
+ {
+ this.ccList = ccList;
+ }
+
+
+ /**
+ * Sets the FailOnError attribute of the MimeMail object
+ *
+ * @param failOnError The new FailOnError value
+ */
+ public void setFailOnError( boolean failOnError )
+ {
+ this.failOnError = failOnError;
+ }
+
+
+ /**
+ * Sets the "from" parameter of this build task.
+ *
+ * @param from Email address of sender.
+ */
+ public void setFrom( String from )
+ {
+ this.from = from;
+ }
+
+
+ /**
+ * Sets the mailhost parameter of this build task.
+ *
+ * @param mailhost Mail host name.
+ */
+ public void setMailhost( String mailhost )
+ {
+ this.mailhost = mailhost;
+ }
+
+
+ /**
+ * Sets the message parameter of this build task.
+ *
+ * @param message Message body of this email.
+ */
+ public void setMessage( String message )
+ {
+ this.message = message;
+ }
+
+ public void setMessageFile( File messageFile )
+ {
+ this.messageFile = messageFile;
+ }
+
+
+ /**
+ * set type of the text message, plaintext by default but text/html or
+ * text/xml is quite feasible
+ *
+ * @param type The new MessageMimeType value
+ */
+ public void setMessageMimeType( String type )
+ {
+ this.messageMimeType = type;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 );
+ }
+
+ /**
+ * here is where the mail is sent
+ *
+ * @exception MessagingException Description of Exception
+ * @exception AddressException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ public void doMail()
+ throws MessagingException, AddressException, BuildException
+ {
+ Properties props = new Properties();
+ props.put( "mail.smtp.host", mailhost );
+
+ //Aside, the JDK is clearly unaware of the scottish 'session', which
+ //involves excessive quantities of alcohol :-)
+ Session sesh = Session.getDefaultInstance( props, null );
+
+ //create the message
+ MimeMessage msg = new MimeMessage( sesh );
+
+ //set the sender
+ log( "message sender: " + from, Project.MSG_VERBOSE );
+ msg.setFrom( new InternetAddress( from ) );
+
+ // add recipient lists
+ addRecipients( msg, Message.RecipientType.TO, "To", toList );
+ addRecipients( msg, Message.RecipientType.CC, "Cc", ccList );
+ addRecipients( msg, Message.RecipientType.BCC, "Bcc", bccList );
+
+ if( subject != null )
+ {
+ log( "subject: " + subject, Project.MSG_VERBOSE );
+ msg.setSubject( subject );
+ }
+
+ //now the complex bit; adding multiple mime objects. And guessing
+ //the file type
+ MimeMultipart attachments = new MimeMultipart();
+
+ //first a message
+ if( messageFile != null )
+ {
+ int size = ( int )messageFile.length();
+ byte data[] = new byte[size];
+
+ try
+ {
+ FileInputStream inStream = new FileInputStream( messageFile );
+ inStream.read( data );
+ inStream.close();
+ message = new String( data );
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( e );
+ }
+ }
+
+ if( message != null )
+ {
+ MimeBodyPart textbody = new MimeBodyPart();
+ textbody.setContent( message, messageMimeType );
+ attachments.addBodyPart( textbody );
+ }
+
+ for( int i = 0; i < filesets.size(); i++ )
+ {
+ FileSet fs = ( FileSet )filesets.elementAt( i );
+ if( fs != null )
+ {
+ DirectoryScanner ds = fs.getDirectoryScanner( project );
+ String[] dsfiles = ds.getIncludedFiles();
+ File baseDir = ds.getBasedir();
+
+ for( int j = 0; j < dsfiles.length; j++ )
+ {
+ File file = new File( baseDir, dsfiles[j] );
+ MimeBodyPart body;
+ body = new MimeBodyPart();
+ if( !file.exists() || !file.canRead() )
+ {
+ throw new BuildException( "File \"" + file.getAbsolutePath()
+ + "\" does not exist or is not readable." );
+ }
+ log( "Attaching " + file.toString() + " - " + file.length() + " bytes",
+ Project.MSG_VERBOSE );
+ FileDataSource fileData = new FileDataSource( file );
+ DataHandler fileDataHandler = new DataHandler( fileData );
+ body.setDataHandler( fileDataHandler );
+ body.setFileName( file.getName() );
+ attachments.addBodyPart( body );
+ }// for j
+ }// if (fs != null)
+ }// for i
+
+ msg.setContent( attachments );
+ log( "sending email " );
+ Transport.send( msg );
+ }
+
+
+ /**
+ * 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();
+ doMail();
+ }
+ 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()
+ {
+ if( from == null )
+ {
+ throw new BuildException( "Attribute \"from\" is required." );
+ }
+
+ if( ( toList == null ) && ( ccList == null ) && ( bccList == null ) )
+ {
+ throw new BuildException( "Attribute \"toList\", \"ccList\" or \"bccList\" is required." );
+ }
+
+ if( message == null && filesets.isEmpty() && messageFile == null )
+ {
+ throw new BuildException( "FileSet, \"message\", or \"messageFile\" is required." );
+ }
+
+ if( message != null && messageFile != null )
+ {
+ throw new BuildException( "Only one of \"message\" or \"messageFile\" may be specified." );
+ }
+ }
+}
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/TelnetTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/TelnetTask.java
new file mode 100644
index 000000000..f2d1e7c46
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/TelnetTask.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.net;
+import com.oroinc.net.telnet.TelnetClient;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Calendar;
+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;
+
+/**
+ * Class to provide automated telnet protocol support for the Ant build tool
+ *
+ * @author ScottCarlson@email.com
+ * @version $Revision$
+ */
+
+public class TelnetTask extends Task
+{
+ /**
+ * The userid to login with, if automated login is used
+ */
+ private String userid = null;
+
+ /**
+ * The password to login with, if automated login is used
+ */
+ private String password = null;
+
+ /**
+ * The server to connect to.
+ */
+ private String server = null;
+
+ /**
+ * The tcp port to connect to.
+ */
+ private int port = 23;
+
+ /**
+ * The Object which handles the telnet session.
+ */
+ private AntTelnetClient telnet = null;
+
+ /**
+ * The list of read/write commands for this session
+ */
+ private Vector telnetTasks = new Vector();
+
+ /**
+ * If true, adds a CR to beginning of login script
+ */
+ private boolean addCarriageReturn = false;
+
+ /**
+ * Default time allowed for waiting for a valid response for all child
+ * reads. A value of 0 means no limit.
+ */
+ private Integer defaultTimeout = null;
+
+ /**
+ * Set the tcp port to connect to attribute
+ *
+ * @param b The new InitialCR value
+ */
+ public void setInitialCR( boolean b )
+ {
+ this.addCarriageReturn = b;
+ }
+
+ /**
+ * Set the password attribute
+ *
+ * @param p The new Password value
+ */
+ public void setPassword( String p )
+ {
+ this.password = p;
+ }
+
+ /**
+ * Set the tcp port to connect to attribute
+ *
+ * @param p The new Port value
+ */
+ public void setPort( int p )
+ {
+ this.port = p;
+ }
+
+ /**
+ * Set the server address attribute
+ *
+ * @param m The new Server value
+ */
+ public void setServer( String m )
+ {
+ this.server = m;
+ }
+
+ /**
+ * Change the default timeout to wait for valid responses
+ *
+ * @param i The new Timeout value
+ */
+ public void setTimeout( Integer i )
+ {
+ this.defaultTimeout = i;
+ }
+
+ /**
+ * Set the userid attribute
+ *
+ * @param u The new Userid value
+ */
+ public void setUserid( String u )
+ {
+ this.userid = u;
+ }
+
+ /**
+ * A subTask <read> tag was found. Create the object, Save it in our
+ * list, and return it.
+ *
+ * @return Description of the Returned Value
+ */
+
+ public TelnetSubTask createRead()
+ {
+ TelnetSubTask task = ( TelnetSubTask )new TelnetRead();
+ telnetTasks.addElement( task );
+ return task;
+ }
+
+ /**
+ * A subTask <write> tag was found. Create the object, Save it in our
+ * list, and return it.
+ *
+ * @return Description of the Returned Value
+ */
+ public TelnetSubTask createWrite()
+ {
+ TelnetSubTask task = ( TelnetSubTask )new TelnetWrite();
+ telnetTasks.addElement( task );
+ return task;
+ }
+
+ /**
+ * Verify that all parameters are included. Connect and possibly login
+ * Iterate through the list of Reads and writes
+ *
+ * @exception BuildException Description of Exception
+ */
+ public void execute()
+ throws BuildException
+ {
+ /**
+ * A server name is required to continue
+ */
+ if( server == null )
+ throw new BuildException( "No Server Specified" );
+ /**
+ * A userid and password must appear together if they appear. They are
+ * not required.
+ */
+ if( userid == null && password != null )
+ throw new BuildException( "No Userid Specified" );
+ if( password == null && userid != null )
+ throw new BuildException( "No Password Specified" );
+
+ /**
+ * Create the telnet client object
+ */
+ telnet = new AntTelnetClient();
+ try
+ {
+ telnet.connect( server, port );
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Can't connect to " + server );
+ }
+ /**
+ * Login if userid and password were specified
+ */
+ if( userid != null && password != null )
+ login();
+ /**
+ * Process each sub command
+ */
+ Enumeration tasksToRun = telnetTasks.elements();
+ while( tasksToRun != null && tasksToRun.hasMoreElements() )
+ {
+ TelnetSubTask task = ( TelnetSubTask )tasksToRun.nextElement();
+ if( task instanceof TelnetRead && defaultTimeout != null )
+ ( ( TelnetRead )task ).setDefaultTimeout( defaultTimeout );
+ task.execute( telnet );
+ }
+ }
+
+ /**
+ * Process a 'typical' login. If it differs, use the read and write tasks
+ * explicitely
+ */
+ private void login()
+ {
+ if( addCarriageReturn )
+ telnet.sendString( "\n", true );
+ telnet.waitForString( "ogin:" );
+ telnet.sendString( userid, true );
+ telnet.waitForString( "assword:" );
+ telnet.sendString( password, false );
+ }
+
+ /**
+ * This class handles the abstraction of the telnet protocol. Currently it
+ * is a wrapper around ORO 's NetComponents
+ *
+ * @author RT
+ */
+ public class AntTelnetClient extends TelnetClient
+ {
+
+ /**
+ * Write this string to the telnet session.
+ *
+ * @param s Description of Parameter
+ * @param echoString Description of Parameter
+ * @parm echoString Logs string sent
+ */
+ public void sendString( String s, boolean echoString )
+ {
+ OutputStream os = this.getOutputStream();
+ try
+ {
+ os.write( ( s + "\n" ).getBytes() );
+ if( echoString )
+ log( s, Project.MSG_INFO );
+ os.flush();
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( e );
+ }
+ }
+
+ /**
+ * Read from the telnet session until the string we are waiting for is
+ * found
+ *
+ * @param s Description of Parameter
+ * @parm s The string to wait on
+ */
+ public void waitForString( String s )
+ {
+ waitForString( s, null );
+ }
+
+ /**
+ * Read from the telnet session until the string we are waiting for is
+ * found or the timeout has been reached
+ *
+ * @param s Description of Parameter
+ * @param timeout Description of Parameter
+ * @parm s The string to wait on
+ * @parm timeout The maximum number of seconds to wait
+ */
+ public void waitForString( String s, Integer timeout )
+ {
+ InputStream is = this.getInputStream();
+ try
+ {
+ StringBuffer sb = new StringBuffer();
+ if( timeout == null || timeout.intValue() == 0 )
+ {
+ while( sb.toString().indexOf( s ) == -1 )
+ {
+ sb.append( ( char )is.read() );
+ }
+ }
+ else
+ {
+ Calendar endTime = Calendar.getInstance();
+ endTime.add( Calendar.SECOND, timeout.intValue() );
+ while( sb.toString().indexOf( s ) == -1 )
+ {
+ while( Calendar.getInstance().before( endTime ) &&
+ is.available() == 0 )
+ {
+ Thread.sleep( 250 );
+ }
+ if( is.available() == 0 )
+ throw new BuildException( "Response Timed-Out", getLocation() );
+ sb.append( ( char )is.read() );
+ }
+ }
+ log( sb.toString(), Project.MSG_INFO );
+ }
+ catch( BuildException be )
+ {
+ throw be;
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( e );
+ }
+ }
+ }
+
+ /**
+ * This class reads the output from the connected server until the required
+ * string is found.
+ *
+ * @author RT
+ */
+ public class TelnetRead extends TelnetSubTask
+ {
+ private Integer timeout = null;
+
+ /**
+ * Sets the default timeout if none has been set already
+ *
+ * @param defaultTimeout The new DefaultTimeout value
+ */
+ public void setDefaultTimeout( Integer defaultTimeout )
+ {
+ if( timeout == null )
+ timeout = defaultTimeout;
+ }
+
+ /**
+ * Override any default timeouts
+ *
+ * @param i The new Timeout value
+ */
+ public void setTimeout( Integer i )
+ {
+ this.timeout = i;
+ }
+
+ public void execute( AntTelnetClient telnet )
+ throws BuildException
+ {
+ telnet.waitForString( taskString, timeout );
+ }
+ }
+
+ /**
+ * This class is the parent of the Read and Write tasks. It handles the
+ * common attributes for both.
+ *
+ * @author RT
+ */
+ public class TelnetSubTask
+ {
+ protected String taskString = "";
+
+ public void setString( String s )
+ {
+ taskString += s;
+ }
+
+ public void addText( String s )
+ {
+ setString( s );
+ }
+
+ public void execute( AntTelnetClient telnet )
+ throws BuildException
+ {
+ throw new BuildException( "Shouldn't be able instantiate a SubTask directly" );
+ }
+ }
+
+ /**
+ * This class sends text to the connected server
+ *
+ * @author RT
+ */
+ public class TelnetWrite extends TelnetSubTask
+ {
+ private boolean echoString = true;
+
+ public void setEcho( boolean b )
+ {
+ echoString = b;
+ }
+
+ public void execute( AntTelnetClient telnet )
+ throws BuildException
+ {
+ telnet.sendString( taskString, echoString );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Add.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Add.java
new file mode 100644
index 000000000..9630aa7ff
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Add.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.perforce;
+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;
+
+/**
+ * P4Add - add the specified files to perforce. Example Usage:
+ *
+ *
+ *
+ * Function
+ *
+ *
+ *
+ * Command
+ *
+ *
+ *
+ *
+ *
+ * Add files using P4USER, P4PORT and P4CLIENT settings specified
+ *
+ *
+ *
+ * <P4add
+ * P4view="//projects/foo/main/source/..."
+ * P4User="fbloggs"
+ * P4Port="km01:1666"
+ * P4Client="fbloggsclient">
+ * <fileset basedir="dir" includes="**/*.java">
+ * </p4add>
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Add files using P4USER, P4PORT and P4CLIENT settings defined in
+ * environment
+ *
+ *
+ *
+ * <P4add P4view="//projects/foo/main/source/..." />
+ * <fileset basedir="dir" includes="**/*.java">
+ * </p4add>
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Specify the length of command line arguments to pass to each invocation
+ * of p4
+ *
+ *
+ *
+ * <p4add Commandlength="450">
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @author Les Hughes
+ * @author Anli Shundi
+ */
+public class P4Add extends P4Base
+{
+ private String addCmd = "";
+ private Vector filesets = new Vector();
+ private int m_cmdLength = 450;
+
+ private int m_changelist;
+
+ public void setChangelist( int changelist )
+ throws BuildException
+ {
+ if( changelist <= 0 )
+ throw new BuildException( "P4Add: Changelist# should be a positive number" );
+
+ this.m_changelist = changelist;
+ }
+
+ public void setCommandlength( int len )
+ throws BuildException
+ {
+ if( len <= 0 )
+ throw new BuildException( "P4Add: Commandlength should be a positive number" );
+ this.m_cmdLength = len;
+ }
+
+ public void addFileset( FileSet set )
+ {
+ filesets.addElement( set );
+ }
+
+ public void execute()
+ throws BuildException
+ {
+
+ if( P4View != null )
+ {
+ addCmd = P4View;
+ }
+
+ P4CmdOpts = ( m_changelist > 0 ) ? ( "-c " + m_changelist ) : "";
+
+ StringBuffer filelist = new StringBuffer();
+
+ 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();
+ if( srcFiles != null )
+ {
+ for( int j = 0; j < srcFiles.length; j++ )
+ {
+ File f = new File( ds.getBasedir(), srcFiles[j] );
+ filelist.append( " " ).append( '"' ).append( f.getAbsolutePath() ).append( '"' );
+ if( filelist.length() > m_cmdLength )
+ {
+ execP4Add( filelist );
+ filelist.setLength( 0 );
+ }
+ }
+ if( filelist.length() > 0 )
+ {
+ execP4Add( filelist );
+ }
+ }
+ else
+ {
+ log( "No files specified to add!", Project.MSG_WARN );
+ }
+ }
+
+ }
+
+ private void execP4Add( StringBuffer list )
+ {
+ log( "Execing add " + P4CmdOpts + " " + addCmd + list, Project.MSG_INFO );
+
+ execP4Command( "-s add " + P4CmdOpts + " " + addCmd + list, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Base.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Base.java
new file mode 100644
index 000000000..dd93d117c
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Base.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.perforce;
+import java.io.IOException;
+import org.apache.oro.text.perl.Perl5Util;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Execute;
+import org.apache.tools.ant.types.Commandline;
+
+
+/**
+ * Base class for Perforce (P4) ANT tasks. See individual task for example
+ * usage.
+ *
+ * @author Les Hughes
+ * @see P4Sync
+ * @see P4Have
+ * @see P4Change
+ * @see P4Edit
+ * @see P4Submit
+ * @see P4Label
+ * @see org.apache.tools.ant.taskdefs.Exec
+ */
+public abstract class P4Base extends org.apache.tools.ant.Task
+{
+
+ /**
+ * Perl5 regexp in Java - cool eh?
+ */
+ protected Perl5Util util = null;
+
+ //P4 runtime directives
+ /**
+ * Perforce Server Port (eg KM01:1666)
+ */
+ protected String P4Port = "";
+ /**
+ * Perforce Client (eg myclientspec)
+ */
+ protected String P4Client = "";
+ /**
+ * Perforce User (eg fbloggs)
+ */
+ protected String P4User = "";
+ /**
+ * Perforce view for commands (eg //projects/foobar/main/source/... )
+ */
+ protected String P4View = "";
+
+ //P4 g-opts and cmd opts (rtfm)
+ /**
+ * Perforce 'global' opts. Forms half of low level API
+ */
+ protected String P4Opts = "";
+ /**
+ * Perforce command opts. Forms half of low level API
+ */
+ protected String P4CmdOpts = "";
+ /**
+ * The OS shell to use (cmd.exe or /bin/sh)
+ */
+ protected String shell;
+
+ public void setClient( String P4Client )
+ {
+ this.P4Client = "-c" + P4Client;
+ }
+
+ public void setCmdopts( String P4CmdOpts )
+ {
+ this.P4CmdOpts = P4CmdOpts;
+ }
+
+ //Setters called by Ant
+ public void setPort( String P4Port )
+ {
+ this.P4Port = "-p" + P4Port;
+ }
+
+ public void setUser( String P4User )
+ {
+ this.P4User = "-u" + P4User;
+ }
+
+ public void setView( String P4View )
+ {
+ this.P4View = P4View;
+ }
+
+ public void init()
+ {
+
+ util = new Perl5Util();
+
+ //Get default P4 settings from environment - Mark would have done something cool with
+ //introspection here.....:-)
+ String tmpprop;
+ if( ( tmpprop = project.getProperty( "p4.port" ) ) != null )
+ setPort( tmpprop );
+ if( ( tmpprop = project.getProperty( "p4.client" ) ) != null )
+ setClient( tmpprop );
+ if( ( tmpprop = project.getProperty( "p4.user" ) ) != null )
+ setUser( tmpprop );
+ }
+
+ protected void execP4Command( String command )
+ throws BuildException
+ {
+ execP4Command( command, null );
+ }
+
+ /**
+ * Execute P4 command assembled by subclasses.
+ *
+ * @param command The command to run
+ * @param handler A P4Handler to process any input and output
+ * @exception BuildException Description of Exception
+ */
+ protected void execP4Command( String command, P4Handler handler )
+ throws BuildException
+ {
+ try
+ {
+
+ Commandline commandline = new Commandline();
+ commandline.setExecutable( "p4" );
+
+ //Check API for these - it's how CVS does it...
+ if( P4Port != null && P4Port.length() != 0 )
+ {
+ commandline.createArgument().setValue( P4Port );
+ }
+ if( P4User != null && P4User.length() != 0 )
+ {
+ commandline.createArgument().setValue( P4User );
+ }
+ if( P4Client != null && P4Client.length() != 0 )
+ {
+ commandline.createArgument().setValue( P4Client );
+ }
+ commandline.createArgument().setLine( command );
+
+ String[] cmdline = commandline.getCommandline();
+ String cmdl = "";
+ for( int i = 0; i < cmdline.length; i++ )
+ {
+ cmdl += cmdline[i] + " ";
+ }
+
+ log( "Execing " + cmdl, Project.MSG_VERBOSE );
+
+ if( handler == null )
+ handler = new SimpleP4OutputHandler( this );
+
+ Execute exe = new Execute( handler, null );
+
+ exe.setAntRun( project );
+
+ exe.setCommandline( commandline.getCommandline() );
+
+ try
+ {
+ exe.execute();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( e );
+ }
+ finally
+ {
+ try
+ {
+ handler.stop();
+ }
+ catch( Exception e )
+ {}
+ }
+
+
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( "Problem exec'ing P4 command: " + e.getMessage() );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Change.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Change.java
new file mode 100644
index 000000000..5068a9d13
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Change.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.taskdefs.optional.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * P4Change - grab a new changelist from Perforce. P4Change creates a new
+ * changelist in perforce. P4Change sets the property ${p4.change} with the new
+ * changelist number. This should then be passed into p4edit and p4submit.
+ *
+ * @author Les Hughes
+ * @see P4Edit
+ * @see P4Submit
+ */
+public class P4Change extends P4Base
+{
+
+ protected String emptyChangeList = null;
+ protected String description = "AutoSubmit By Ant";
+
+ /*
+ * Set Description Variable.
+ */
+ public void setDescription( String desc )
+ {
+ this.description = desc;
+ }
+
+
+ public String getEmptyChangeList()
+ throws BuildException
+ {
+ final StringBuffer stringbuf = new StringBuffer();
+
+ execP4Command( "change -o",
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ if( !util.match( "/^#/", line ) )
+ {
+ if( util.match( "/error/", line ) )
+ {
+
+ log( "Client Error", Project.MSG_VERBOSE );
+ throw new BuildException( "Perforce Error, check client settings and/or server" );
+ }
+ else if( util.match( "//", line ) )
+ {
+
+ // we need to escape the description in case there are /
+ description = backslash( description );
+ line = util.substitute( "s//" + description + "/", line );
+
+ }
+ else if( util.match( "/\\/\\//", line ) )
+ {
+ //Match "//" for begining of depot filespec
+ return;
+ }
+
+ stringbuf.append( line );
+ stringbuf.append( "\n" );
+
+ }
+ }
+ } );
+
+ return stringbuf.toString();
+ }
+
+ public void execute()
+ throws BuildException
+ {
+
+ if( emptyChangeList == null )
+ emptyChangeList = getEmptyChangeList();
+ final Project myProj = project;
+
+ P4Handler handler =
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ if( util.match( "/Change/", line ) )
+ {
+
+ //Remove any non-numerical chars - should leave the change number
+ line = util.substitute( "s/[^0-9]//g", line );
+
+ int changenumber = Integer.parseInt( line );
+ log( "Change Number is " + changenumber, Project.MSG_INFO );
+ myProj.setProperty( "p4.change", "" + changenumber );
+
+ }
+ else if( util.match( "/error/", line ) )
+ {
+ throw new BuildException( "Perforce Error, check client settings and/or server" );
+ }
+
+ }
+ };
+
+ handler.setOutput( emptyChangeList );
+
+ execP4Command( "change -i", handler );
+ }
+
+ /**
+ * Ensure that a string is backslashing slashes so that it does not confuse
+ * them with Perl substitution delimiter in Oro. Backslashes are always
+ * backslashes in a string unless they escape the delimiter.
+ *
+ * @param value the string to backslash for slashes
+ * @return the backslashed string
+ * @see < a href="http://jakarta.apache.org/oro/api/org/apache/oro/text/perl/Perl5Util.html#substitute(java.lang.String,%20java.lang.String)">
+ * Oro
+ */
+ protected String backslash( String value )
+ {
+ final StringBuffer buf = new StringBuffer( value.length() );
+ final int len = value.length();
+ for( int i = 0; i < len; i++ )
+ {
+ char c = value.charAt( i );
+ if( c == '/' )
+ {
+ buf.append( '\\' );
+ }
+ buf.append( c );
+ }
+ return buf.toString();
+ }
+
+}//EoF
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Counter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Counter.java
new file mode 100644
index 000000000..59949b617
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Counter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * P4Counter - Obtain or set the value of a counter. P4Counter can be used to
+ * either print the value of a counter to the output stream for the project (by
+ * setting the "name" attribute only), to set a property based on the value of a
+ * counter (by setting the "property" attribute) or to set the counter on the
+ * perforce server (by setting the "value" attribute). Example Usage:
+ * <p4counter name="${p4.counter}" property=${p4.change}"/>
+ *
+ * @author Kirk Wylie
+ */
+
+public class P4Counter extends P4Base
+{
+ public String counter = null;
+ public String property = null;
+ public boolean shouldSetValue = false;
+ public boolean shouldSetProperty = false;
+ public int value = 0;
+
+ public void setName( String counter )
+ {
+ this.counter = counter;
+ }
+
+ public void setProperty( String property )
+ {
+ this.property = property;
+ shouldSetProperty = true;
+ }
+
+ public void setValue( int value )
+ {
+ this.value = value;
+ shouldSetValue = true;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+
+ if( ( counter == null ) || counter.length() == 0 )
+ {
+ throw new BuildException( "No counter specified to retrieve" );
+ }
+
+ if( shouldSetValue && shouldSetProperty )
+ {
+ throw new BuildException( "Cannot both set the value of the property and retrieve the value of the property." );
+ }
+
+ String command = "counter " + P4CmdOpts + " " + counter;
+ if( !shouldSetProperty )
+ {
+ // NOTE kirk@radik.com 04-April-2001 -- If you put in the -s, you
+ // have to start running through regular expressions here. Much easier
+ // to just not include the scripting information than to try to add it
+ // and strip it later.
+ command = "-s " + command;
+ }
+ if( shouldSetValue )
+ {
+ command += " " + value;
+ }
+
+ if( shouldSetProperty )
+ {
+ final Project myProj = project;
+
+ P4Handler handler =
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( "P4Counter retrieved line \"" + line + "\"", Project.MSG_VERBOSE );
+ try
+ {
+ value = Integer.parseInt( line );
+ myProj.setProperty( property, "" + value );
+ }
+ catch( NumberFormatException nfe )
+ {
+ throw new BuildException( "Perforce error. Could not retrieve counter value." );
+ }
+ }
+ };
+
+ execP4Command( command, handler );
+ }
+ else
+ {
+ execP4Command( command, new SimpleP4OutputHandler( this ) );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Delete.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Delete.java
new file mode 100644
index 000000000..f711fd2ca
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Delete.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.optional.perforce;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * P4Delete - checkout file(s) for delete. Example Usage:
+ * <p4delete change="${p4.change}" view="//depot/project/foo.txt" />
+ * Simple re-write of P4Edit changing 'edit' to 'delete'.
+ * ToDo: What to do if file is already open in one of our changelists perhaps
+ * (See also {@link P4Edit P4Edit})?
+ *
+ *
+ * @author Mike Roberts , Les Hughes
+ */
+public class P4Delete extends P4Base
+{
+
+ public String change = null;
+
+ public void setChange( String change )
+ {
+ this.change = change;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ if( change != null )
+ P4CmdOpts = "-c " + change;
+ if( P4View == null )
+ throw new BuildException( "No view specified to delete" );
+ execP4Command( "-s delete " + P4CmdOpts + " " + P4View, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Edit.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Edit.java
new file mode 100644
index 000000000..c5b10ebc8
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Edit.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.optional.perforce;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * P4Edit - checkout file(s) for edit. Example Usage:
+ * <p4edit change="${p4.change}" view="//depot/project/foo.txt" />
+ *
+ * @author Les Hughes ToDo: Should
+ * call reopen if file is already open in one of our changelists perhaps?
+ */
+
+public class P4Edit extends P4Base
+{
+
+ public String change = null;
+
+ public void setChange( String change )
+ {
+ this.change = change;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ if( change != null )
+ P4CmdOpts = "-c " + change;
+ if( P4View == null )
+ throw new BuildException( "No view specified to edit" );
+ execP4Command( "-s edit " + P4CmdOpts + " " + P4View, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Handler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Handler.java
new file mode 100644
index 000000000..7175096c3
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Handler.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.taskdefs.optional.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
+
+/**
+ * Interface for p4 job output stream handler. Classes implementing this
+ * interface can be called back by P4Base.execP4Command();
+ *
+ * @author Les Hughes
+ */
+public interface P4Handler extends ExecuteStreamHandler
+{
+
+ public void process( String line )
+ throws BuildException;
+
+ public void setOutput( String line )
+ throws BuildException;
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4HandlerAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4HandlerAdapter.java
new file mode 100644
index 000000000..552817a5b
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4HandlerAdapter.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.perforce;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.SequenceInputStream;
+import org.apache.tools.ant.BuildException;
+
+public abstract class P4HandlerAdapter implements P4Handler
+{
+
+ String p4input = "";//Input
+ InputStream es;//OUtput
+ InputStream is;
+
+ OutputStream os;
+
+ //set any data to be written to P4's stdin - messy, needs work
+ public void setOutput( String p4Input )
+ {
+ this.p4input = p4Input;
+ }
+
+ public void setProcessErrorStream( InputStream is )
+ throws IOException
+ {
+ this.es = is;
+ }//Error
+
+ public void setProcessInputStream( OutputStream os )
+ throws IOException
+ {
+ this.os = os;
+ }
+
+ public void setProcessOutputStream( InputStream is )
+ throws IOException
+ {
+ this.is = is;
+ }
+
+ public abstract void process( String line );
+
+
+ public void start()
+ throws BuildException
+ {
+
+ try
+ {
+ //First write any output to P4
+ if( p4input != null && p4input.length() > 0 && os != null )
+ {
+ os.write( p4input.getBytes() );
+ os.flush();
+ os.close();
+ }
+
+ //Now read any input and process
+
+ BufferedReader input = new BufferedReader(
+ new InputStreamReader(
+ new SequenceInputStream( is, es ) ) );
+
+ String line;
+ while( ( line = input.readLine() ) != null )
+ {
+ process( line );
+ }
+
+ input.close();
+
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( e );
+ }
+ }
+
+ public void stop() { }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Have.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Have.java
new file mode 100644
index 000000000..406b08b52
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Have.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.taskdefs.optional.perforce;
+import org.apache.tools.ant.BuildException;
+
+
+/**
+ * P4Have - lists files currently on client. P4Have simply dumps the current
+ * file version info into the Ant log (or stdout).
+ *
+ * @author Les Hughes
+ */
+public class P4Have extends P4Base
+{
+
+ public void execute()
+ throws BuildException
+ {
+ execP4Command( "have " + P4CmdOpts + " " + P4View, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Label.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Label.java
new file mode 100644
index 000000000..277256019
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Label.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.perforce;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+
+/**
+ * P4Label - create a Perforce Label. P4Label inserts a label into perforce
+ * reflecting the current client contents. Label name defaults to AntLabel if
+ * none set. Example Usage:
+ * <P4Label name="MyLabel-${TSTAMP}-${DSTAMP}" desc="Auto Build Label" />
+ *
+ *
+ * @author Les Hughes
+ */
+public class P4Label extends P4Base
+{
+ protected String desc;
+ protected String lock;
+
+ protected String name;
+
+ public void setDesc( String desc )
+ {
+ this.desc = desc;
+ }
+
+ public void setLock( String lock )
+ {
+ this.lock = lock;
+ }
+
+ public void setName( String name )
+ {
+ this.name = name;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ log( "P4Label exec:", Project.MSG_INFO );
+
+ if( P4View == null || P4View.length() < 1 )
+ {
+ log( "View not set, assuming //depot/...", Project.MSG_WARN );
+ P4View = "//depot/...";
+ }
+
+ if( desc == null || desc.length() < 1 )
+ {
+ log( "Label Description not set, assuming 'AntLabel'", Project.MSG_WARN );
+ desc = "AntLabel";
+ }
+
+ if( lock != null && !lock.equalsIgnoreCase( "locked" ) )
+ {
+ log( "lock attribute invalid - ignoring", Project.MSG_WARN );
+ }
+
+ if( name == null || name.length() < 1 )
+ {
+ SimpleDateFormat formatter = new SimpleDateFormat( "yyyy.MM.dd-hh:mm" );
+ Date now = new Date();
+ name = "AntLabel-" + formatter.format( now );
+ log( "name not set, assuming '" + name + "'", Project.MSG_WARN );
+ }
+
+ //We have to create a unlocked label first
+ String newLabel =
+ "Label: " + name + "\n" +
+ "Description: " + desc + "\n" +
+ "Options: unlocked\n" +
+ "View: " + P4View + "\n";
+
+ P4Handler handler =
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( line, Project.MSG_VERBOSE );
+ }
+ };
+
+ handler.setOutput( newLabel );
+
+ execP4Command( "label -i", handler );
+
+ execP4Command( "labelsync -l " + name,
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( line, Project.MSG_VERBOSE );
+ }
+ } );
+
+ log( "Created Label " + name + " (" + desc + ")", Project.MSG_INFO );
+
+ //Now lock if required
+ if( lock != null && lock.equalsIgnoreCase( "locked" ) )
+ {
+
+ log( "Modifying lock status to 'locked'", Project.MSG_INFO );
+
+ final StringBuffer labelSpec = new StringBuffer();
+
+ //Read back the label spec from perforce,
+ //Replace Options
+ //Submit back to Perforce
+
+ handler =
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( line, Project.MSG_VERBOSE );
+
+ if( util.match( "/^Options:/", line ) )
+ {
+ line = "Options: " + lock;
+ }
+
+ labelSpec.append( line + "\n" );
+ }
+ };
+
+
+ execP4Command( "label -o " + name, handler );
+ log( labelSpec.toString(), Project.MSG_DEBUG );
+
+ log( "Now locking label...", Project.MSG_VERBOSE );
+ handler =
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( line, Project.MSG_VERBOSE );
+ }
+ };
+
+ handler.setOutput( labelSpec.toString() );
+ execP4Command( "label -i", handler );
+ }
+
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4OutputHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4OutputHandler.java
new file mode 100644
index 000000000..fdc248dc7
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4OutputHandler.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.perforce;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Interface for p4 job output stream handler. Classes implementing this
+ * interface can be called back by P4Base.execP4Command();
+ *
+ * @author Les Hughes
+ */
+public interface P4OutputHandler
+{
+
+ public void process( String line )
+ throws BuildException;
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Reopen.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Reopen.java
new file mode 100644
index 000000000..ce1031a3a
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Reopen.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.optional.perforce;
+import org.apache.tools.ant.BuildException;
+
+/*
+ * P4Reopen - move files to a new changelist
+ *
+ * @author Les Hughes
+ */
+public class P4Reopen extends P4Base
+{
+
+ private String toChange = "";
+
+ public void setToChange( String toChange )
+ throws BuildException
+ {
+ if( toChange == null && !toChange.equals( "" ) )
+ throw new BuildException( "P4Reopen: tochange cannot be null or empty" );
+
+ this.toChange = toChange;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ if( P4View == null )
+ if( P4View == null )
+ throw new BuildException( "No view specified to reopen" );
+ execP4Command( "-s reopen -c " + toChange + " " + P4View, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Revert.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Revert.java
new file mode 100644
index 000000000..f96d1862c
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Revert.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.perforce;
+import org.apache.tools.ant.BuildException;
+
+/*
+ * P4Revert - revert open files or files in a changelist
+ *
+ * @author Les Hughes
+ */
+public class P4Revert extends P4Base
+{
+
+ private String revertChange = null;
+ private boolean onlyUnchanged = false;
+
+ public void setChange( String revertChange )
+ throws BuildException
+ {
+ if( revertChange == null && !revertChange.equals( "" ) )
+ throw new BuildException( "P4Revert: change cannot be null or empty" );
+
+ this.revertChange = revertChange;
+
+ }
+
+ public void setRevertOnlyUnchanged( boolean onlyUnchanged )
+ {
+ this.onlyUnchanged = onlyUnchanged;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+
+ /*
+ * Here we can either revert any unchanged files in a changelist
+ * or
+ * any files regardless of whether they have been changed or not
+ *
+ *
+ * The whole process also accepts a p4 filespec
+ */
+ String p4cmd = "-s revert";
+ if( onlyUnchanged )
+ p4cmd += " -a";
+
+ if( revertChange != null )
+ p4cmd += " -c " + revertChange;
+
+ execP4Command( p4cmd + " " + P4View, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Submit.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Submit.java
new file mode 100644
index 000000000..07ebb1e97
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Submit.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.optional.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * P4Submit - submit a numbered changelist to Perforce. Note: P4Submit
+ * cannot (yet) submit the default changelist. This shouldn't be a problem with
+ * the ANT API as the usual flow is P4Change to create a new numbered change
+ * followed by P4Edit then P4Submit. Example Usage:-
+ * <p4submit change="${p4.change}" />
+ *
+ * @author Les Hughes
+ */
+public class P4Submit extends P4Base
+{
+
+ //ToDo: If dealing with default cl need to parse out
+ public String change;
+
+ public void setChange( String change )
+ {
+ this.change = change;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ if( change != null )
+ {
+ execP4Command( "submit -c " + change,
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( line, Project.MSG_VERBOSE );
+ }
+ }
+ );
+
+ }
+ else
+ {
+ //here we'd parse the output from change -o into submit -i
+ //in order to support default change.
+ throw new BuildException( "No change specified (no support for default change yet...." );
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Sync.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Sync.java
new file mode 100644
index 000000000..8c6e9bcc6
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Sync.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.optional.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * P4Sync - synchronise client space to a perforce depot view. The API allows
+ * additional functionality of the "p4 sync" command (such as "p4 sync -f
+ * //...#have" or other exotic invocations).
Example Usage:
+ *
+ *
+ *
+ * Function
+ *
+ *
+ *
+ * Command
+ *
+ *
+ *
+ *
+ *
+ * Sync to head using P4USER, P4PORT and P4CLIENT settings specified
+ *
+ *
+ *
+ * <P4Sync
+ * P4view="//projects/foo/main/source/..."
+ * P4User="fbloggs"
+ * P4Port="km01:1666"
+ * P4Client="fbloggsclient" />
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Sync to head using P4USER, P4PORT and P4CLIENT settings defined in
+ * environment
+ *
+ *
+ *
+ * <P4Sync P4view="//projects/foo/main/source/..." />
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Force a re-sync to head, refreshing all files
+ *
+ *
+ *
+ * <P4Sync force="yes" P4view="//projects/foo/main/source/..." />
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Sync to a label
+ *
+ *
+ *
+ * <P4Sync label="myPerforceLabel" />
+ *
+ *
+ *
+ *
+ *
+ * ToDo: Add decent label error handling for non-exsitant labels
+ *
+ * @author Les Hughes
+ */
+public class P4Sync extends P4Base
+{
+ private String syncCmd = "";
+
+ String label;
+
+
+ public void setForce( String force )
+ throws BuildException
+ {
+ if( force == null && !label.equals( "" ) )
+ throw new BuildException( "P4Sync: If you want to force, set force to non-null string!" );
+ P4CmdOpts = "-f";
+ }
+
+ public void setLabel( String label )
+ throws BuildException
+ {
+ if( label == null && !label.equals( "" ) )
+ throw new BuildException( "P4Sync: Labels cannot be Null or Empty" );
+
+ this.label = label;
+
+ }
+
+ public void execute()
+ throws BuildException
+ {
+
+ if( P4View != null )
+ {
+ syncCmd = P4View;
+ }
+
+ if( label != null && !label.equals( "" ) )
+ {
+ syncCmd = syncCmd + "@" + label;
+ }
+
+ log( "Execing sync " + P4CmdOpts + " " + syncCmd, Project.MSG_VERBOSE );
+
+ execP4Command( "-s sync " + P4CmdOpts + " " + syncCmd, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/SimpleP4OutputHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/SimpleP4OutputHandler.java
new file mode 100644
index 000000000..fc8e97e66
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/SimpleP4OutputHandler.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+public class SimpleP4OutputHandler extends P4HandlerAdapter
+{
+
+ P4Base parent;
+
+ public SimpleP4OutputHandler( P4Base parent )
+ {
+ this.parent = parent;
+ }
+
+ public void process( String line )
+ throws BuildException
+ {
+ if( parent.util.match( "/^exit/", line ) )
+ return;
+
+ //Throw exception on errors (except up-to-date)
+ //p4 -s is unpredicatable. For example a server down
+ //does not return error: markup
+ //
+ //Some forms producing commands (p4 -s change -o) do tag the output
+ //others don't.....
+ //Others mark errors as info, for example edit a file
+ //which is already open for edit.....
+ //Just look for error: - catches most things....
+
+ if( parent.util.match( "/error:/", line ) && !parent.util.match( "/up-to-date/", line ) )
+ {
+ throw new BuildException( line );
+ }
+
+ parent.log( parent.util.substitute( "s/^.*: //", line ), Project.MSG_INFO );
+
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/package.html b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/package.html
new file mode 100644
index 000000000..125fc2aaf
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/package.html
@@ -0,0 +1,26 @@
+
+ANT Tasks for Perforce integration.
+
+These tasks provide basic P4 capabilities to automated ANT-based build systems. Note:
+the tasks in this package are linked against the Jakarta ORO 2.0 library which
+brings the power of Perl 5 regular expressions to Java.
+
+These tasks also require you to have the p4 (or p4.exe) client in your path.
+
+@see Jakarta Project
+@see Perforce
+
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Sync
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Label
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Have
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Change
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Edit
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Submit
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Counter
+
+
+
+@author Les Hughes
+
+
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/pvcs/Pvcs.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/pvcs/Pvcs.java
new file mode 100644
index 000000000..64ad0ad85
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/pvcs/Pvcs.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.pvcs;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.Enumeration;
+import java.util.Random;
+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.LogOutputStream;
+import org.apache.tools.ant.taskdefs.LogStreamHandler;
+import org.apache.tools.ant.taskdefs.PumpStreamHandler;
+import org.apache.tools.ant.types.Commandline;
+
+/**
+ * A task that fetches source files from a PVCS archive 19-04-2001
+ *
+ * The task now has a more robust parser. It allows for platform independant
+ * file paths and supports file names with () . Thanks to Erik Husby for
+ * bringing the bug to my attention. 27-04-2001
+ *
+ * UNC paths are now handled properly. Fix provided by Don Jeffery. He also
+ * added an UpdateOnly flag that, when true, conditions the PVCS get
+ * using the -U option to only update those files that have a modification time
+ * (in PVCS) that is newer than the existing workfile.
+ *
+ * @author Thomas Christensen
+ * @author Don Jeffery
+ * @author Steven E. Newton
+ */
+public class Pvcs extends org.apache.tools.ant.Task
+{
+ /**
+ * Constant for the thing to execute
+ */
+ private final static String PCLI_EXE = "pcli";
+
+ /**
+ * Constant for the PCLI listversionedfiles recursive i a format "get"
+ * understands
+ */
+ private final static String PCLI_LVF_ARGS = "lvf -z -aw";
+
+ /**
+ * Constant for the thing to execute
+ */
+ private final static String GET_EXE = "get";
+ private String filenameFormat;
+ private String force;
+ private boolean ignorerc;
+ private String label;
+ private String lineStart;
+ private String promotiongroup;
+ private String pvcsProject;
+ private Vector pvcsProjects;
+ private String pvcsbin;
+ private String repository;
+ private boolean updateOnly;
+ private String workspace;
+
+ /**
+ * Creates a Pvcs object
+ */
+ public Pvcs()
+ {
+ super();
+ pvcsProject = null;
+ pvcsProjects = new Vector();
+ workspace = null;
+ repository = null;
+ pvcsbin = null;
+ force = null;
+ promotiongroup = null;
+ label = null;
+ ignorerc = false;
+ updateOnly = false;
+ lineStart = "\"P:";
+ filenameFormat = "{0}_arc({1})";
+ }
+
+ public void setFilenameFormat( String f )
+ {
+ filenameFormat = f;
+ }
+
+ /**
+ * Specifies the value of the force argument
+ *
+ * @param f The new Force value
+ */
+ public void setForce( String f )
+ {
+ if( f != null && f.equalsIgnoreCase( "yes" ) )
+ force = "yes";
+ else
+ force = "no";
+ }
+
+ /**
+ * If set to true the return value from executing the pvcs commands are
+ * ignored.
+ *
+ * @param b The new IgnoreReturnCode value
+ */
+ public void setIgnoreReturnCode( boolean b )
+ {
+ ignorerc = b;
+ }
+
+ /**
+ * Specifies the name of the label argument
+ *
+ * @param l The new Label value
+ */
+ public void setLabel( String l )
+ {
+ label = l;
+ }
+
+ public void setLineStart( String l )
+ {
+ lineStart = l;
+ }
+
+ /**
+ * Specifies the name of the promotiongroup argument
+ *
+ * @param w The new Promotiongroup value
+ */
+ public void setPromotiongroup( String w )
+ {
+ promotiongroup = w;
+ }
+
+ /**
+ * Specifies the location of the PVCS bin directory
+ *
+ * @param bin The new Pvcsbin value
+ */
+ public void setPvcsbin( String bin )
+ {
+ pvcsbin = bin;
+ }
+
+ /**
+ * Specifies the name of the project in the PVCS repository
+ *
+ * @param prj String
+ */
+ public void setPvcsproject( String prj )
+ {
+ pvcsProject = prj;
+ }
+
+ /**
+ * Specifies the network name of the PVCS repository
+ *
+ * @param repo String
+ */
+ public void setRepository( String repo )
+ {
+ repository = repo;
+ }
+
+ /**
+ * If set to true files are gotten only if newer than existing local files.
+ *
+ * @param l The new UpdateOnly value
+ */
+ public void setUpdateOnly( boolean l )
+ {
+ updateOnly = l;
+ }
+
+ /**
+ * Specifies the name of the workspace to store retrieved files
+ *
+ * @param ws String
+ */
+ public void setWorkspace( String ws )
+ {
+ workspace = ws;
+ }
+
+ public String getFilenameFormat()
+ {
+ return filenameFormat;
+ }
+
+ /**
+ * Get value of force
+ *
+ * @return String
+ */
+ public String getForce()
+ {
+ return force;
+ }
+
+ /**
+ * Get value of ignorereturncode
+ *
+ * @return String
+ */
+ public boolean getIgnoreReturnCode()
+ {
+ return ignorerc;
+ }
+
+ /**
+ * Get value of label
+ *
+ * @return String
+ */
+ public String getLabel()
+ {
+ return label;
+ }
+
+ public String getLineStart()
+ {
+ return lineStart;
+ }
+
+ /**
+ * Get value of promotiongroup
+ *
+ * @return String
+ */
+ public String getPromotiongroup()
+ {
+ return promotiongroup;
+ }
+
+ /**
+ * Get name of the PVCS bin directory
+ *
+ * @return String
+ */
+ public String getPvcsbin()
+ {
+ return pvcsbin;
+ }
+
+ /**
+ * Get name of the project in the PVCS repository
+ *
+ * @return String
+ */
+ public String getPvcsproject()
+ {
+ return pvcsProject;
+ }
+
+ /**
+ * Get name of the project in the PVCS repository
+ *
+ * @return Vector
+ */
+ public Vector getPvcsprojects()
+ {
+ return pvcsProjects;
+ }
+
+ /**
+ * Get network name of the PVCS repository
+ *
+ * @return String
+ */
+ public String getRepository()
+ {
+ return repository;
+ }
+
+ public boolean getUpdateOnly()
+ {
+ return updateOnly;
+ }
+
+ /**
+ * Get name of the workspace to store the retrieved files
+ *
+ * @return String
+ */
+ public String getWorkspace()
+ {
+ return workspace;
+ }
+
+ /**
+ * handles <pvcsproject> subelements
+ *
+ * @param p The feature to be added to the Pvcsproject attribute
+ */
+ public void addPvcsproject( PvcsProject p )
+ {
+ pvcsProjects.addElement( p );
+ }
+
+ /**
+ * @exception org.apache.tools.ant.BuildException Something is stopping the
+ * build...
+ */
+ public void execute()
+ throws org.apache.tools.ant.BuildException
+ {
+ Project aProj = getProject();
+ int result = 0;
+
+ if( repository == null || repository.trim().equals( "" ) )
+ throw new BuildException( "Required argument repository not specified" );
+
+ // Check workspace exists
+ // Launch PCLI listversionedfiles -z -aw
+ // Capture output
+ // build the command line from what we got the format is
+ Commandline commandLine = new Commandline();
+ commandLine.setExecutable( getExecutable( PCLI_EXE ) );
+
+ commandLine.createArgument().setValue( "lvf" );
+ commandLine.createArgument().setValue( "-z" );
+ commandLine.createArgument().setValue( "-aw" );
+ if( getWorkspace() != null )
+ commandLine.createArgument().setValue( "-sp" + getWorkspace() );
+ commandLine.createArgument().setValue( "-pr" + getRepository() );
+
+ // default pvcs project is "/"
+ if( getPvcsproject() == null && getPvcsprojects().isEmpty() )
+ pvcsProject = "/";
+
+ if( getPvcsproject() != null )
+ commandLine.createArgument().setValue( getPvcsproject() );
+ if( !getPvcsprojects().isEmpty() )
+ {
+ Enumeration e = getPvcsprojects().elements();
+ while( e.hasMoreElements() )
+ {
+ String projectName = ( ( PvcsProject )e.nextElement() ).getName();
+ if( projectName == null || ( projectName.trim() ).equals( "" ) )
+ throw new BuildException( "name is a required attribute of pvcsproject" );
+ commandLine.createArgument().setValue( projectName );
+ }
+ }
+
+ File tmp = null;
+ File tmp2 = null;
+ try
+ {
+ Random rand = new Random( System.currentTimeMillis() );
+ tmp = new File( "pvcs_ant_" + rand.nextLong() + ".log" );
+ tmp2 = new File( "pvcs_ant_" + rand.nextLong() + ".log" );
+ log( "Executing " + commandLine.toString(), Project.MSG_VERBOSE );
+ result = runCmd( commandLine, new PumpStreamHandler( new FileOutputStream( tmp ), new LogOutputStream( this, Project.MSG_WARN ) ) );
+ if( result != 0 && !ignorerc )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+
+ if( !tmp.exists() )
+ throw new BuildException( "Communication between ant and pvcs failed. No output generated from executing PVCS commandline interface \"pcli\" and \"get\"" );
+
+ // Create folders in workspace
+ log( "Creating folders", Project.MSG_INFO );
+ createFolders( tmp );
+
+ // Massage PCLI lvf output transforming '\' to '/' so get command works appropriately
+ massagePCLI( tmp, tmp2 );
+
+ // Launch get on output captured from PCLI lvf
+ commandLine.clearArgs();
+ commandLine.setExecutable( getExecutable( GET_EXE ) );
+
+ if( getForce() != null && getForce().equals( "yes" ) )
+ commandLine.createArgument().setValue( "-Y" );
+ else
+ commandLine.createArgument().setValue( "-N" );
+
+ if( getPromotiongroup() != null )
+ commandLine.createArgument().setValue( "-G" + getPromotiongroup() );
+ else
+ {
+ if( getLabel() != null )
+ commandLine.createArgument().setValue( "-r" + getLabel() );
+ }
+
+ if( updateOnly )
+ {
+ commandLine.createArgument().setValue( "-U" );
+ }
+
+ commandLine.createArgument().setValue( "@" + tmp2.getAbsolutePath() );
+ log( "Getting files", Project.MSG_INFO );
+ log( "Executing " + commandLine.toString(), Project.MSG_VERBOSE );
+ result = runCmd( commandLine, new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ) );
+ if( result != 0 && !ignorerc )
+ {
+ String msg = "Failed executing: " + commandLine.toString() + ". Return code was " + result;
+ throw new BuildException( msg, location );
+ }
+
+ }
+ catch( FileNotFoundException e )
+ {
+ String msg = "Failed executing: " + commandLine.toString() + ". Exception: " + e.getMessage();
+ throw new BuildException( msg, location );
+ }
+ catch( IOException e )
+ {
+ String msg = "Failed executing: " + commandLine.toString() + ". Exception: " + e.getMessage();
+ throw new BuildException( msg, location );
+ }
+ catch( ParseException e )
+ {
+ String msg = "Failed executing: " + commandLine.toString() + ". Exception: " + e.getMessage();
+ throw new BuildException( msg, location );
+ }
+ finally
+ {
+ if( tmp != null )
+ {
+ tmp.delete();
+ }
+ if( tmp2 != null )
+ {
+ tmp2.delete();
+ }
+ }
+ }
+
+
+ protected int runCmd( Commandline cmd, ExecuteStreamHandler out )
+ {
+ try
+ {
+ Project aProj = getProject();
+ Execute exe = new Execute( out );
+ exe.setAntRun( aProj );
+ exe.setWorkingDirectory( aProj.getBaseDir() );
+ exe.setCommandline( cmd.getCommandline() );
+ return exe.execute();
+ }
+ catch( java.io.IOException e )
+ {
+ String msg = "Failed executing: " + cmd.toString() + ". Exception: " + e.getMessage();
+ throw new BuildException( msg, location );
+ }
+ }
+
+ private String getExecutable( String exe )
+ {
+ StringBuffer correctedExe = new StringBuffer();
+ if( getPvcsbin() != null )
+ if( pvcsbin.endsWith( File.separator ) )
+ correctedExe.append( pvcsbin );
+ else
+ correctedExe.append( pvcsbin ).append( File.separator );
+ return correctedExe.append( exe ).toString();
+ }
+
+ /**
+ * Parses the file and creates the folders specified in the output section
+ *
+ * @param file Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception ParseException Description of Exception
+ */
+ private void createFolders( File file )
+ throws IOException, ParseException
+ {
+ BufferedReader in = new BufferedReader( new FileReader( file ) );
+ MessageFormat mf = new MessageFormat( getFilenameFormat() );
+ String line = in.readLine();
+ while( line != null )
+ {
+ log( "Considering \"" + line + "\"", Project.MSG_VERBOSE );
+ if( line.startsWith( "\"\\" ) ||
+ line.startsWith( "\"/" ) ||
+ line.startsWith( getLineStart() ) )
+ {
+ Object[] objs = mf.parse( line );
+ String f = ( String )objs[1];
+ // Extract the name of the directory from the filename
+ int index = f.lastIndexOf( File.separator );
+ if( index > -1 )
+ {
+ File dir = new File( f.substring( 0, index ) );
+ if( !dir.exists() )
+ {
+ log( "Creating " + dir.getAbsolutePath(), Project.MSG_VERBOSE );
+ if( dir.mkdirs() )
+ {
+ log( "Created " + dir.getAbsolutePath(), Project.MSG_INFO );
+ }
+ else
+ {
+ log( "Failed to create " + dir.getAbsolutePath(), Project.MSG_INFO );
+ }
+ }
+ else
+ {
+ log( dir.getAbsolutePath() + " exists. Skipping", Project.MSG_VERBOSE );
+ }
+ }
+ else
+ {
+ log( "File separator problem with " + line,
+ Project.MSG_WARN );
+ }
+ }
+ else
+ {
+ log( "Skipped \"" + line + "\"", Project.MSG_VERBOSE );
+ }
+ line = in.readLine();
+ }
+ }
+
+ /**
+ * Simple hack to handle the PVCS command-line tools botch when handling UNC
+ * notation.
+ *
+ * @param in Description of Parameter
+ * @param out Description of Parameter
+ * @exception FileNotFoundException Description of Exception
+ * @exception IOException Description of Exception
+ */
+ private void massagePCLI( File in, File out )
+ throws FileNotFoundException, IOException
+ {
+ BufferedReader inReader = new BufferedReader( new FileReader( in ) );
+ BufferedWriter outWriter = new BufferedWriter( new FileWriter( out ) );
+ String s = null;
+ while( ( s = inReader.readLine() ) != null )
+ {
+ String sNormal = s.replace( '\\', '/' );
+ outWriter.write( sNormal );
+ outWriter.newLine();
+ }
+ inReader.close();
+ outWriter.close();
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/pvcs/PvcsProject.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/pvcs/PvcsProject.java
new file mode 100644
index 000000000..524de2f1b
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/pvcs/PvcsProject.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.pvcs;
+
+
+/**
+ * class to handle <pvcsprojec> elements
+ *
+ * @author RT
+ */
+public class PvcsProject
+{
+ private String name;
+
+ public PvcsProject()
+ {
+ super();
+ }
+
+ public void setName( String name )
+ {
+ PvcsProject.this.name = name;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/scm/AntStarTeamCheckOut.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/scm/AntStarTeamCheckOut.java
new file mode 100644
index 000000000..1c7832216
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/scm/AntStarTeamCheckOut.java
@@ -0,0 +1,1113 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.scm;
+import com.starbase.starteam.Folder;
+import com.starbase.starteam.Item;
+import com.starbase.starteam.Property;
+import com.starbase.starteam.Server;
+import com.starbase.starteam.StarTeamFinder;
+import com.starbase.starteam.Type;
+import com.starbase.starteam.View;
+import com.starbase.util.Platform;
+import java.util.StringTokenizer;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;
+
+/**
+ * Checks out files from a specific StarTeam server, project, view, and folder.
+ *
+ *
+ * This program logs in to a StarTeam server and opens up the specified project
+ * and view. Then, it searches through that view for the given folder (or, if
+ * you prefer, it uses the root folder). Beginning with that folder and
+ * optionally continuing recursivesly, AntStarTeamCheckOut compares each file
+ * with your include and exclude filters and checks it out only if appropriate.
+ *
+ *
+ * Checked out files go to a directory you specify under the subfolder named for
+ * the default StarTeam path to the view. That is, if you entered
+ * /home/cpovirk/work as the target folder, your project was named "OurProject,"
+ * the given view was named "TestView," and that view is stored by default at
+ * "C:\projects\Test," your files would be checked out to
+ * /home/cpovirk/work/Test." I avoided using the project name in the path
+ * because you may want to keep several versions of the same project on your
+ * computer, and I didn't want to use the view name, as there may be many "Test"
+ * or "Version 1.0" views, for example. This system's success, of course,
+ * depends on what you set the default path to in StarTeam.
+ *
+ * You can set AntStarTeamCheckOut to verbose or quiet mode. Also, it has a
+ * safeguard against overwriting the files on your computer: If the target
+ * directory you specify already exists, the program will throw a
+ * BuildException. To override the exception, set force to true.
+ *
+ *
+ * This program makes use of functions from the StarTeam API. As a result
+ * AntStarTeamCheckOut is available only to licensed users of StarTeam and
+ * requires the StarTeam SDK to function. You must have starteam-sdk.jar
+ * in your classpath to run this program. For more information about the
+ * StarTeam API and how to license it, see the link below.
+ *
+ * @author Chris Povirk
+ * @author JC Mann
+ * @author Jeff Gettle
+ * @author Steve Cohen
+ * @version 1.0
+ * @see StarBase Web Site
+ */
+public class AntStarTeamCheckOut extends org.apache.tools.ant.Task
+{
+
+ /**
+ * This constant sets the filter to include all files. This default has the
+ * same result as setIncludes("*").
+ *
+ * @see #getIncludes()
+ * @see #setIncludes(String includes)
+ */
+ public final static String DEFAULT_INCLUDESETTING = "*";
+
+ /**
+ * This disables the exclude filter by default. In other words, no files are
+ * excluded. This setting is equivalent to setExcludes(null).
+ *
+ * @see #getExcludes()
+ * @see #setExcludes(String excludes)
+ */
+ public final static String DEFAULT_EXCLUDESETTING = null;
+
+ /**
+ * The default folder to search; the root folder. Since AntStarTeamCheckOut
+ * searches subfolders, by default it processes an entire view.
+ *
+ * @see #getFolderName()
+ * @see #setFolderName(String folderName)
+ */
+ public final static String DEFAULT_FOLDERSETTING = null;
+
+ /**
+ * This is used when formatting the output. The directory name is displayed
+ * only when it changes.
+ */
+ private Folder prevFolder = null;
+
+ /**
+ * This field keeps count of the number of files checked out.
+ */
+ private int checkedOut = 0;
+
+ // Change these through their GET and SET methods.
+
+ /**
+ * The name of the server you wish to connect to.
+ */
+ private String serverName = null;
+
+ /**
+ * The port on the server used for StarTeam.
+ */
+ private int serverPort = -1;
+
+ /**
+ * The name of your project.
+ */
+ private String projectName = null;
+
+ /**
+ * The name of the folder you want to check out files from. All subfolders
+ * will be searched, as well.
+ */
+ private String folderName = DEFAULT_FOLDERSETTING;
+
+ /**
+ * The view that the files you want are in.
+ */
+ private String viewName = null;
+
+ /**
+ * Your username on the StarTeam server.
+ */
+ private String username = null;
+
+ /**
+ * Your StarTeam password.
+ */
+ private String password = null;
+
+ /**
+ * The path to the root folder you want to check out to. This is a local
+ * directory.
+ */
+ private String targetFolder = null;
+
+ /**
+ * If force set to true, AntStarTeamCheckOut will overwrite files in the
+ * target directory.
+ */
+ private boolean force = false;
+
+ /**
+ * When verbose is true, the program will display all files and directories
+ * as they are checked out.
+ */
+ private boolean verbose = false;
+
+ /**
+ * Set recursion to false to check out files in only the given folder and
+ * not in its subfolders.
+ */
+ private boolean recursion = true;
+
+ // These fields deal with includes and excludes
+
+ /**
+ * All files that fit this pattern are checked out.
+ */
+ private String includes = DEFAULT_INCLUDESETTING;
+
+ /**
+ * All files fitting this pattern are ignored.
+ */
+ private String excludes = DEFAULT_EXCLUDESETTING;
+
+ /**
+ * The file delimitor on the user's system.
+ */
+ private String delim = Platform.getFilePathDelim();
+
+ /**
+ * whether to use the Starteam "default folder" when calculating the target
+ * paths to which files are checked out (false) or if targetFolder
+ * represents an absolute mapping to folderName.
+ */
+ private boolean targetFolderAbsolute = false;
+
+
+ /**
+ * convenient method to check for conditions
+ *
+ * @param value Description of Parameter
+ * @param msg Description of Parameter
+ * @exception BuildException Description of Exception
+ */
+ private static void assertTrue( boolean value, String msg )
+ throws BuildException
+ {
+ if( !value )
+ {
+ throw new BuildException( msg );
+ }
+ }
+
+ /**
+ * Sets the exclude filter. When filtering files, AntStarTeamCheckOut uses
+ * an unmodified version of DirectoryScanner's match
+ * method, so here are the patterns straight from the Ant source code:
+ *
+ * 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.
+ *
+ * Separate multiple exlcude filters by spaces , not commas as Ant
+ * uses. For example, if you want to check out all files except .XML and
+ * .HTML files, you would put the following line in your program: setExcludes("*.XML *.HTML");
+ * Finally, note that filters have no effect on the directories that
+ * are scanned; you could not skip over all files in directories whose names
+ * begin with "project," for instance.
+ *
+ * Treatment of overlapping inlcudes and excludes: To give a simplistic
+ * example suppose that you set your include filter to "*.htm *.html" and
+ * your exclude filter to "index.*". What happens to index.html?
+ * AntStarTeamCheckOut will not check out index.html, as it matches an
+ * exclude filter ("index.*"), even though it matches the include filter, as
+ * well.
+ *
+ * Please also read the following sections before using filters:
+ *
+ * @param excludes A string of filter patterns to exclude. Separate the
+ * patterns by spaces.
+ * @see #setIncludes(String includes)
+ * @see #getIncludes()
+ * @see #getExcludes()
+ */
+ public void setExcludes( String excludes )
+ {
+ this.excludes = excludes;
+ }
+
+ /**
+ * Sets the folderName attribute to the given value. To search
+ * the root folder, use a slash or backslash, or simply don't set a folder
+ * at all.
+ *
+ * @param folderName The subfolder from which to check out files.
+ * @see #getFolderName()
+ */
+ public void setFolderName( String folderName )
+ {
+ this.folderName = folderName;
+ }
+
+ /**
+ * Sets the force attribute to the given value.
+ *
+ * @param force if true, it overwrites files in the target directory. By
+ * default it set to false as a safeguard. Note that if the target
+ * directory does not exist, this setting has no effect.
+ * @see #getForce()
+ */
+ public void setForce( boolean force )
+ {
+ this.force = force;
+ }
+
+ // Begin filter getters and setters
+
+ /**
+ * Sets the include filter. When filtering files, AntStarTeamCheckOut uses
+ * an unmodified version of DirectoryScanner's match
+ * method, so here are the patterns straight from the Ant source code:
+ *
+ * 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.
+ *
+ * Separate multiple inlcude filters by spaces , not commas as Ant
+ * uses. For example, if you want to check out all .java and .class\ files,
+ * you would put the following line in your program: setIncludes("*.java *.class");
+ * Finally, note that filters have no effect on the directories that
+ * are scanned; you could not check out files from directories with names
+ * beginning only with "build," for instance. Of course, you could limit
+ * AntStarTeamCheckOut to a particular folder and its subfolders with the
+ * setFolderName(String folderName) command.
+ *
+ * Treatment of overlapping inlcudes and excludes: To give a simplistic
+ * example suppose that you set your include filter to "*.htm *.html" and
+ * your exclude filter to "index.*". What happens to index.html?
+ * AntStarTeamCheckOut will not check out index.html, as it matches an
+ * exclude filter ("index.*"), even though it matches the include filter, as
+ * well.
+ *
+ * Please also read the following sections before using filters:
+ *
+ * @param includes A string of filter patterns to include. Separate the
+ * patterns by spaces.
+ * @see #getIncludes()
+ * @see #setExcludes(String excludes)
+ * @see #getExcludes()
+ */
+ public void setIncludes( String includes )
+ {
+ this.includes = includes;
+ }
+
+ /**
+ * Sets the password attribute to the given value.
+ *
+ * @param password Your password for the specified StarTeam server.
+ * @see #getPassword()
+ */
+ public void setPassword( String password )
+ {
+ this.password = password;
+ }
+
+ /**
+ * Sets the projectName attribute to the given value.
+ *
+ * @param projectName The StarTeam project to search.
+ * @see #getProjectName()
+ */
+ public void setProjectName( String projectName )
+ {
+ this.projectName = projectName;
+ }
+
+ /**
+ * Turns recursion on or off.
+ *
+ * @param recursion if it is true, the default, subfolders are searched
+ * recursively for files to check out. Otherwise, only files specified
+ * by folderName are scanned.
+ * @see #getRecursion()
+ */
+ public void setRecursion( boolean recursion )
+ {
+ this.recursion = recursion;
+ }
+
+ // Begin SET and GET methods
+
+ /**
+ * Sets the serverName attribute to the given value.
+ *
+ * @param serverName The name of the server you wish to connect to.
+ * @see #getServerName()
+ */
+ public void setServerName( String serverName )
+ {
+ this.serverName = serverName;
+ }
+
+ /**
+ * Sets the serverPort attribute to the given value. The given
+ * value must be a valid integer, but it must be a string object.
+ *
+ * @param serverPort A string containing the port on the StarTeam server to
+ * use.
+ * @see #getServerPort()
+ */
+ public void setServerPort( int serverPort )
+ {
+ this.serverPort = serverPort;
+ }
+
+ /**
+ * Sets the targetFolder attribute to the given value.
+ *
+ * @param targetFolder The new TargetFolder value
+ * @see #getTargetFolder()
+ */
+ public void setTargetFolder( String targetFolder )
+ {
+ this.targetFolder = targetFolder;
+ }
+
+ /**
+ * sets the property that indicates whether or not the Star Team "default
+ * folder" is to be used when calculation paths for items on the target
+ * (false) or if targetFolder is an absolute mapping to the root folder
+ * named by foldername.
+ *
+ * @param targetFolderAbsolute true if the absolute mapping is to
+ * be used. false (the default) if the "default folder" is to
+ * be factored in.
+ * @see #getTargetFolderAbsolute()
+ */
+ public void setTargetFolderAbsolute( boolean targetFolderAbsolute )
+ {
+ this.targetFolderAbsolute = targetFolderAbsolute;
+ }
+
+ /**
+ * Sets the username attribute to the given value.
+ *
+ * @param username Your username for the specified StarTeam server.
+ * @see #getUsername()
+ */
+ public void setUsername( String username )
+ {
+ this.username = username;
+ }
+
+ /**
+ * Sets the verbose attribute to the given value.
+ *
+ * @param verbose whether to display all files as it checks them out. By
+ * default it is false, so the program only displays the total number
+ * of files unless you override this default.
+ * @see #getVerbose()
+ */
+ public void setVerbose( boolean verbose )
+ {
+ this.verbose = verbose;
+ }
+
+ /**
+ * Sets the viewName attribute to the given value.
+ *
+ * @param viewName The view to find the specified folder in.
+ * @see #getViewName()
+ */
+ public void setViewName( String viewName )
+ {
+ this.viewName = viewName;
+ }
+
+ /**
+ * Gets the patterns from the exclude filter. Rather that duplicate the
+ * details of AntStarTeanCheckOut's filtering here, refer to these links:
+ *
+ * @return A string of filter patterns separated by spaces.
+ * @see #setExcludes(String excludes)
+ * @see #setIncludes(String includes)
+ * @see #getIncludes()
+ */
+ public String getExcludes()
+ {
+ return excludes;
+ }
+
+ /**
+ * Gets the folderName attribute.
+ *
+ * @return The subfolder from which to check out files. All subfolders will
+ * be searched, as well.
+ * @see #setFolderName(String folderName)
+ */
+ public String getFolderName()
+ {
+ return folderName;
+ }
+
+ /**
+ * Gets the force attribute.
+ *
+ * @return whether to continue if the target directory exists.
+ * @see #setForce(boolean)
+ */
+ public boolean getForce()
+ {
+ return force;
+ }
+
+ /**
+ * Gets the patterns from the include filter. Rather that duplicate the
+ * details of AntStarTeanCheckOut's filtering here, refer to these links:
+ *
+ * @return A string of filter patterns separated by spaces.
+ * @see #setIncludes(String includes)
+ * @see #setExcludes(String excludes)
+ * @see #getExcludes()
+ */
+ public String getIncludes()
+ {
+ return includes;
+ }
+
+ /**
+ * Gets the password attribute.
+ *
+ * @return The password given by the user.
+ * @see #setPassword(String password)
+ */
+ public String getPassword()
+ {
+ return password;
+ }
+
+ /**
+ * Gets the projectName attribute.
+ *
+ * @return The StarTeam project to search.
+ * @see #setProjectName(String projectName)
+ */
+ public String getProjectName()
+ {
+ return projectName;
+ }
+
+ /**
+ * Gets the recursion attribute, which tells
+ * AntStarTeamCheckOut whether to search subfolders when checking out files.
+ *
+ * @return whether to search subfolders when checking out files.
+ * @see #setRecursion(boolean)
+ */
+ public boolean getRecursion()
+ {
+ return recursion;
+ }
+
+ /**
+ * Gets the serverName attribute.
+ *
+ * @return The StarTeam server to log in to.
+ * @see #setServerName(String serverName)
+ */
+ public String getServerName()
+ {
+ return serverName;
+ }
+
+ /**
+ * Gets the serverPort attribute.
+ *
+ * @return A string containing the port on the StarTeam server to use.
+ * @see #setServerPort(int)
+ */
+ public int getServerPort()
+ {
+ return serverPort;
+ }
+
+ /**
+ * Gets the targetFolder attribute.
+ *
+ * @return The target path on the local machine to check out to.
+ * @see #setTargetFolder(String targetFolder)
+ */
+ public String getTargetFolder()
+ {
+ return targetFolder;
+ }
+
+
+ /**
+ * returns whether the StarTeam default path is factored into calculated
+ * target path locations (false) or whether targetFolder is an absolute
+ * mapping to the root folder named by folderName
+ *
+ * @return returns true if absolute mapping is used, false if it is not
+ * used.
+ * @see #setTargetFolderAbsolute(boolean)
+ */
+ public boolean getTargetFolderAbsolute()
+ {
+ return this.targetFolderAbsolute;
+ }
+
+ /**
+ * Gets the username attribute.
+ *
+ * @return The username given by the user.
+ * @see #setUsername(String username)
+ */
+ public String getUsername()
+ {
+ return username;
+ }
+
+ /**
+ * Gets the verbose attribute.
+ *
+ * @return whether to display all files as it checks them out.
+ * @see #setVerbose(boolean verbose)
+ */
+ public boolean getVerbose()
+ {
+ return verbose;
+ }
+
+ /**
+ * Gets the viewName attribute.
+ *
+ * @return The view to find the specified folder in.
+ * @see #setViewName(String viewName)
+ */
+ public String getViewName()
+ {
+ return viewName;
+ }
+
+ /**
+ * Do the execution.
+ *
+ * @exception BuildException
+ */
+ public void execute()
+ throws BuildException
+ {
+ // Connect to the StarTeam server, and log on.
+ Server s = getServer();
+
+ try
+ {
+ // Search the items on this server.
+ runServer( s );
+ }
+ finally
+ {
+ // Disconnect from the server.
+ s.disconnect();
+ }
+ // after you are all of the properties are ok, do your thing
+ // with StarTeam. If there are any kind of exceptions then
+ // send the message to the project log.
+
+ // Tell how many files were checked out.
+ log( checkedOut + " files checked out." );
+ }
+
+ /**
+ * Get the primary descriptor of the given item type. Returns null if there
+ * isn't one. In practice, all item types have a primary descriptor.
+ *
+ * @param t An item type. At this point it will always be "file".
+ * @return The specified item's primary descriptor.
+ */
+ protected Property getPrimaryDescriptor( Type t )
+ {
+ Property[] properties = t.getProperties();
+ for( int i = 0; i < properties.length; i++ )
+ {
+ Property p = properties[i];
+ if( p.isPrimaryDescriptor() )
+ {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the secondary descriptor of the given item type. Returns null if
+ * there isn't one.
+ *
+ * @param t An item type. At this point it will always be "file".
+ * @return The specified item's secondary descriptor. There may not be one
+ * for every file.
+ */
+ protected Property getSecondaryDescriptor( Type t )
+ {
+ Property[] properties = t.getProperties();
+ for( int i = 0; i < properties.length; i++ )
+ {
+ Property p = properties[i];
+ if( p.isDescriptor() && !p.isPrimaryDescriptor() )
+ {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates and logs in to a StarTeam server.
+ *
+ * @return A StarTeam server.
+ */
+ protected Server getServer()
+ {
+ // Simplest constructor, uses default encryption algorithm and compression level.
+ Server s = new Server( getServerName(), getServerPort() );
+
+ // Optional; logOn() connects if necessary.
+ s.connect();
+
+ // Logon using specified user name and password.
+ s.logOn( getUsername(), getPassword() );
+
+ return s;
+ }
+
+ protected void checkParameters()
+ throws BuildException
+ {
+ // Check all of the properties that are required.
+ assertTrue( getServerName() != null, "ServerName must be set." );
+ assertTrue( getServerPort() != -1, "ServerPort must be set." );
+ assertTrue( getProjectName() != null, "ProjectName must be set." );
+ assertTrue( getViewName() != null, "ViewName must be set." );
+ assertTrue( getUsername() != null, "Username must be set." );
+ assertTrue( getPassword() != null, "Password must be set." );
+ assertTrue( getTargetFolder() != null, "TargetFolder must be set." );
+
+ // Because of the way I create the full target path, there
+ // must be NO slash at the end of targetFolder and folderName
+ // However, if the slash or backslash is the only character, leave it alone
+ if( ( getTargetFolder().endsWith( "/" ) ||
+ getTargetFolder().endsWith( "\\" ) ) && getTargetFolder().length() > 1 )
+ {
+ setTargetFolder( getTargetFolder().substring( 0, getTargetFolder().length() - 1 ) );
+ }
+
+ // Check to see if the target directory exists.
+ java.io.File dirExist = new java.io.File( getTargetFolder() );
+ if( dirExist.isDirectory() && !getForce() )
+ {
+ throw new BuildException( "Target directory exists. Set \"force\" to \"true\" " +
+ "to continue anyway." );
+ }
+ }
+
+ /**
+ * Formats a property value for display to the user.
+ *
+ * @param p An item property to format.
+ * @param value
+ * @return A string containing the property, which is truncated to 35
+ * characters for display.
+ */
+ protected String formatForDisplay( Property p, Object value )
+ {
+ if( p.getTypeCode() == Property.Types.TEXT )
+ {
+ String str = value.toString();
+ if( str.length() > 35 )
+ {
+ str = str.substring( 0, 32 ) + "...";
+ }
+ return "\"" + str + "\"";
+ }
+ else
+ {
+ if( p.getTypeCode() == Property.Types.ENUMERATED )
+ {
+ return "\"" + p.getEnumDisplayName( ( ( Integer )value ).intValue() ) + "\"";
+ }
+ else
+ {
+ return value.toString();
+ }
+ }
+ }
+
+ /**
+ * Convenient method to see if a string match a one pattern in given set of
+ * space-separated patterns.
+ *
+ * @param patterns the space-separated list of patterns.
+ * @param pName the name to look for matching.
+ * @return whether the name match at least one pattern.
+ */
+ protected boolean matchPatterns( String patterns, String pName )
+ {
+ if( patterns == null )
+ {
+ return false;
+ }
+ StringTokenizer exStr = new StringTokenizer( patterns, " " );
+ while( exStr.hasMoreTokens() )
+ {
+ if( DirectoryScanner.match( exStr.nextToken(), pName ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Searches for files in the given folder. This method is recursive and thus
+ * searches all subfolders.
+ *
+ * @param s A StarTeam server.
+ * @param p A valid project on the server.
+ * @param v A view name from the specified project.
+ * @param t An item type which is currently always "file".
+ * @param f The folder to search.
+ * @param tgt Target folder on local machine
+ */
+ protected void runFolder( Server s,
+ com.starbase.starteam.Project p,
+ View v,
+ Type t,
+ Folder f,
+ java.io.File tgt )
+ {
+ // Process all items in this folder.
+ Item[] items = f.getItems( t.getName() );
+ for( int i = 0; i < items.length; i++ )
+ {
+ runItem( s, p, v, t, f, items[i], tgt );
+ }
+
+ // Process all subfolders recursively if recursion is on.
+ if( getRecursion() )
+ {
+ Folder[] subfolders = f.getSubFolders();
+ for( int i = 0; i < subfolders.length; i++ )
+ {
+ runFolder( s, p, v, t, subfolders[i], new java.io.File( tgt, subfolders[i].getName() ) );
+ }
+ }
+ }
+
+ /**
+ * Check out one file if it matches the include filter but not the exclude
+ * filter.
+ *
+ * @param s A StarTeam server.
+ * @param p A valid project on the server.
+ * @param v A view name from the specified project.
+ * @param t An item type which is currently always "file".
+ * @param f The folder the file is localed in.
+ * @param item The file to check out.
+ * @param tgt target folder on local machine
+ */
+ protected void runItem( Server s,
+ com.starbase.starteam.Project p,
+ View v,
+ Type t,
+ Folder f,
+ Item item,
+ java.io.File tgt )
+ {
+ // Get descriptors for this item type.
+ Property p1 = getPrimaryDescriptor( t );
+ Property p2 = getSecondaryDescriptor( t );
+
+ String pName = ( String )item.get( p1.getName() );
+ if( !shouldCheckout( pName ) )
+ {
+ return;
+ }
+
+ // VERBOSE MODE ONLY
+ if( getVerbose() )
+ {
+ // Show folder only if changed.
+ boolean bShowHeader = ( f != prevFolder );
+ if( bShowHeader )
+ {
+ // We want to display the folder the same way you would
+ // enter it on the command line ... so we remove the
+ // View name (which is also the name of the root folder,
+ // and therefore shows up at the start of the path).
+ String strFolder = f.getFolderHierarchy();
+ int i = strFolder.indexOf( delim );
+ if( i >= 0 )
+ {
+ strFolder = strFolder.substring( i + 1 );
+ }
+ log( " Folder: \"" + strFolder + "\"" );
+ prevFolder = f;
+
+ // If we displayed the project, view, item type, or folder,
+ // then show the list of relevant item properties.
+ StringBuffer header = new StringBuffer( " Item" );
+ header.append( ",\t" ).append( p1.getDisplayName() );
+ if( p2 != null )
+ {
+ header.append( ",\t" ).append( p2.getDisplayName() );
+ }
+ log( header.toString() );
+ }
+
+ // Finally, show the Item properties ...
+ // Always show the ItemID.
+ StringBuffer itemLine = new StringBuffer( " " );
+ itemLine.append( item.getItemID() );
+
+ // Show the primary descriptor.
+ // There should always be one.
+ itemLine.append( ",\t" ).append( formatForDisplay( p1, item.get( p1.getName() ) ) );
+
+ // Show the secondary descriptor, if there is one.
+ // Some item types have one, some don't.
+ if( p2 != null )
+ {
+ itemLine.append( ",\t" ).append( formatForDisplay( p2, item.get( p2.getName() ) ) );
+ }
+
+ // Show if the file is locked.
+ int locker = item.getLocker();
+ if( locker > -1 )
+ {
+ itemLine.append( ",\tLocked by " ).append( locker );
+ }
+ else
+ {
+ itemLine.append( ",\tNot locked" );
+ }
+ log( itemLine.toString() );
+ }
+ // END VERBOSE ONLY
+
+ // Check it out; also ugly.
+
+ // Change the item to be checked out to a StarTeam File.
+ com.starbase.starteam.File remote = ( com.starbase.starteam.File )item;
+
+ // The local file name is simply the local target path (tgt) which has
+ // been passed recursively down from the top of the tree, with the item's name appended.
+ java.io.File local = new java.io.File( tgt, ( String )item.get( p1.getName() ) );
+
+ try
+ {
+ remote.checkoutTo( local, Item.LockType.UNCHANGED, false, true, true );
+ checkedOut++;
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( "Failed to checkout '" + local + "'", e );
+ }
+ }
+
+ /**
+ * Searches for the given view in the project.
+ *
+ * @param s A StarTeam server.
+ * @param p A valid project on the given server.
+ */
+ protected void runProject( Server s, com.starbase.starteam.Project p )
+ {
+ View[] views = p.getViews();
+ for( int i = 0; i < views.length; i++ )
+ {
+ View v = views[i];
+ if( v.getName().equals( getViewName() ) )
+ {
+ if( getVerbose() )
+ {
+ log( "Found " + getProjectName() + delim + getViewName() + delim );
+ }
+ runType( s, p, v, s.typeForName( ( String )s.getTypeNames().FILE ) );
+ break;
+ }
+ }
+ }
+
+ /**
+ * Searches for the specified project on the server.
+ *
+ * @param s A StarTeam server.
+ */
+ protected void runServer( Server s )
+ {
+ com.starbase.starteam.Project[] projects = s.getProjects();
+ for( int i = 0; i < projects.length; i++ )
+ {
+ com.starbase.starteam.Project p = projects[i];
+
+ if( p.getName().equals( getProjectName() ) )
+ {
+ if( getVerbose() )
+ {
+ log( "Found " + getProjectName() + delim );
+ }
+ runProject( s, p );
+ break;
+ }
+ }
+ }
+
+ /**
+ * Searches for folders in the given view.
+ *
+ * @param s A StarTeam server.
+ * @param p A valid project on the server.
+ * @param v A view name from the specified project.
+ * @param t An item type which is currently always "file".
+ */
+ protected void runType( Server s, com.starbase.starteam.Project p, View v, Type t )
+ {
+ // This is ugly; checking for the root folder.
+ Folder f = v.getRootFolder();
+ if( getFolderName() != null )
+ {
+ if( getFolderName().equals( "\\" ) || getFolderName().equals( "/" ) )
+ {
+ setFolderName( null );
+ }
+ else
+ {
+ f = StarTeamFinder.findFolder( v.getRootFolder(), getFolderName() );
+ assertTrue( null != f, "ERROR: " + getProjectName() + delim + getViewName() + delim +
+ v.getRootFolder() + delim + getFolderName() + delim +
+ " does not exist." );
+ }
+ }
+
+ if( getVerbose() && getFolderName() != null )
+ {
+ log( "Found " + getProjectName() + delim + getViewName() +
+ delim + getFolderName() + delim + "\n" );
+ }
+
+ // For performance reasons, it is important to pre-fetch all the
+ // properties we'll need for all the items we'll be searching.
+
+ // We always display the ItemID (OBJECT_ID) and primary descriptor.
+ int nProperties = 2;
+
+ // We'll need this item type's primary descriptor.
+ Property p1 = getPrimaryDescriptor( t );
+
+ // Does this item type have a secondary descriptor?
+ // If so, we'll need it.
+ Property p2 = getSecondaryDescriptor( t );
+ if( p2 != null )
+ {
+ nProperties++;
+ }
+
+ // Now, build an array of the property names.
+ String[] strNames = new String[nProperties];
+ int iProperty = 0;
+ strNames[iProperty++] = s.getPropertyNames().OBJECT_ID;
+ strNames[iProperty++] = p1.getName();
+ if( p2 != null )
+ {
+ strNames[iProperty++] = p2.getName();
+ }
+
+ // Pre-fetch the item properties and cache them.
+ f.populateNow( t.getName(), strNames, -1 );
+
+ // Now, search for items in the selected folder.
+ runFolder( s, p, v, t, f, calcTargetFolder( v, f ) );
+
+ // Free up the memory used by the cached items.
+ f.discardItems( t.getName(), -1 );
+ }
+
+ /**
+ * Look if the file should be checked out. Don't check it out if It fits no
+ * include filters and It fits an exclude filter.
+ *
+ * @param pName the item name to look for being included.
+ * @return whether the file should be checked out or not.
+ */
+ protected boolean shouldCheckout( String pName )
+ {
+ boolean includeIt = matchPatterns( getIncludes(), pName );
+ boolean excludeIt = matchPatterns( getExcludes(), pName );
+ return ( includeIt && !excludeIt );
+ }
+
+ /**
+ * returns a file object that defines the root of the local checkout tree
+ * Depending on the value of targetFolderAbsolute, this will be either the
+ * targetFolder exactly as set by the user or the path formed by appending
+ * the default folder onto the specified target folder.
+ *
+ * @param v view from which the file is checked out, supplies the "default
+ * folder"
+ * @param rootSourceFolder root folder of the checkout operation in Star
+ * Team
+ * @return an object referencing the local file
+ * @see getTargetFolderAbsolute()
+ */
+ private java.io.File calcTargetFolder( View v, Folder rootSourceFolder )
+ {
+ java.io.File root = new java.io.File( getTargetFolder() );
+ if( !getTargetFolderAbsolute() )
+ {
+ // Create a variable dir that contains the name of
+ // the StarTeam folder that is the root folder in this view.
+ // Get the default path to the current view.
+ String defaultPath = v.getDefaultPath();
+
+ // convert whatever separator char is in starteam to that of the target system.
+ defaultPath = defaultPath.replace( '/', java.io.File.separatorChar );
+ defaultPath = defaultPath.replace( '\\', java.io.File.separatorChar );
+
+ java.io.File dir = new java.io.File( defaultPath );
+ String dirName = dir.getName();
+
+ // If it ends with separator then strip it off
+ if( dirName.endsWith( delim ) )
+ {
+ dirName = dirName.substring( 0, dirName.length() - 1 );
+ }
+
+ // Replace the projectName in the file's absolute path to the viewName.
+ // This makes the root target of a checkout operation equal to:
+ // targetFolder + dirName
+ StringTokenizer pathTokenizer = new StringTokenizer( rootSourceFolder.getFolderHierarchy(), delim );
+ String currentToken = null;
+ boolean foundRoot = false;
+ while( pathTokenizer.hasMoreTokens() )
+ {
+ currentToken = pathTokenizer.nextToken();
+ if( currentToken.equals( getProjectName() ) && !foundRoot )
+ {
+ currentToken = dirName;
+ foundRoot = true;// only want to do this the first time
+ }
+ root = new java.io.File( root, currentToken );
+ }
+ }
+
+ return root;
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/CovMerge.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/CovMerge.java
new file mode 100644
index 000000000..23abe6fe7
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/CovMerge.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+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.taskdefs.Execute;
+import org.apache.tools.ant.taskdefs.LogStreamHandler;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.FileSet;
+
+/**
+ * Convenient task to run the snapshot merge utility for JProbe Coverage.
+ *
+ * @author Stephane Bailliez
+ */
+public class CovMerge extends Task
+{
+
+ /**
+ * coverage home, it is mandatory
+ */
+ private File home = null;
+
+ /**
+ * the name of the output snapshot
+ */
+ private File tofile = null;
+
+ /**
+ * the filesets that will get all snapshots to merge
+ */
+ private Vector filesets = new Vector();
+
+ private boolean verbose;
+
+ //---------------- the tedious job begins here
+
+ public CovMerge() { }
+
+ /**
+ * set the coverage home. it must point to JProbe coverage directories where
+ * are stored native librairies and jars
+ *
+ * @param value The new Home value
+ */
+ public void setHome( File value )
+ {
+ this.home = value;
+ }
+
+ /**
+ * Set the output snapshot file
+ *
+ * @param value The new Tofile value
+ */
+ public void setTofile( File value )
+ {
+ this.tofile = value;
+ }
+
+ /**
+ * run the merging in verbose mode
+ *
+ * @param flag The new Verbose value
+ */
+ public void setVerbose( boolean flag )
+ {
+ this.verbose = flag;
+ }
+
+ /**
+ * add a fileset containing the snapshots to include/exclude
+ *
+ * @param fs The feature to be added to the Fileset attribute
+ */
+ public void addFileset( FileSet fs )
+ {
+ filesets.addElement( fs );
+ }
+
+ /**
+ * execute the jpcovmerge by providing a parameter file
+ *
+ * @exception BuildException Description of Exception
+ */
+ public void execute()
+ throws BuildException
+ {
+ checkOptions();
+
+ File paramfile = createParamFile();
+ try
+ {
+ Commandline cmdl = new Commandline();
+ cmdl.setExecutable( new File( home, "jpcovmerge" ).getAbsolutePath() );
+ if( verbose )
+ {
+ cmdl.createArgument().setValue( "-v" );
+ }
+ cmdl.createArgument().setValue( "-jp_paramfile=" + paramfile.getAbsolutePath() );
+
+ LogStreamHandler handler = new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN );
+ Execute exec = new Execute( handler );
+ log( cmdl.toString(), Project.MSG_VERBOSE );
+ exec.setCommandline( cmdl.getCommandline() );
+
+ // JProbe process always return 0 so we will not be
+ // able to check for failure ! :-(
+ int exitValue = exec.execute();
+ if( exitValue != 0 )
+ {
+ throw new BuildException( "JProbe Coverage Merging failed (" + exitValue + ")" );
+ }
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Failed to run JProbe Coverage Merge: " + e );
+ }
+ finally
+ {
+ //@todo should be removed once switched to JDK1.2
+ paramfile.delete();
+ }
+ }
+
+ /**
+ * get the snapshots from the filesets
+ *
+ * @return The Snapshots value
+ */
+ protected File[] getSnapshots()
+ {
+ 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( getProject() );
+ ds.scan();
+ String[] f = ds.getIncludedFiles();
+ for( int j = 0; j < f.length; j++ )
+ {
+ String pathname = f[j];
+ 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;
+ }
+
+ /**
+ * check for mandatory options
+ *
+ * @exception BuildException Description of Exception
+ */
+ protected void checkOptions()
+ throws BuildException
+ {
+ if( tofile == null )
+ {
+ throw new BuildException( "'tofile' attribute must be set." );
+ }
+
+ // check coverage home
+ if( home == null || !home.isDirectory() )
+ {
+ throw new BuildException( "Invalid home directory. Must point to JProbe home directory" );
+ }
+ home = new File( home, "coverage" );
+ File jar = new File( home, "coverage.jar" );
+ if( !jar.exists() )
+ {
+ throw new BuildException( "Cannot find Coverage directory: " + home );
+ }
+ }
+
+
+ /**
+ * create the parameters file that contains all file to merge and the output
+ * filename.
+ *
+ * @return Description of the Returned Value
+ * @exception BuildException Description of Exception
+ */
+ protected File createParamFile()
+ throws BuildException
+ {
+ File[] snapshots = getSnapshots();
+ File file = createTmpFile();
+ FileWriter fw = null;
+ try
+ {
+ fw = new FileWriter( file );
+ PrintWriter pw = new PrintWriter( fw );
+ for( int i = 0; i < snapshots.length; i++ )
+ {
+ pw.println( snapshots[i].getAbsolutePath() );
+ }
+ // last file is the output snapshot
+ pw.println( project.resolveFile( tofile.getPath() ) );
+ pw.flush();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "I/O error while writing to " + file, e );
+ }
+ finally
+ {
+ if( fw != null )
+ {
+ try
+ {
+ fw.close();
+ }
+ catch( IOException ignored )
+ {
+ }
+ }
+ }
+ return file;
+ }
+
+ /**
+ * create a temporary file in the current dir (For JDK1.1 support)
+ *
+ * @return Description of the Returned Value
+ */
+ protected File createTmpFile()
+ {
+ final long rand = ( new Random( System.currentTimeMillis() ) ).nextLong();
+ File file = new File( "jpcovmerge" + rand + ".tmp" );
+ return file;
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/CovReport.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/CovReport.java
new file mode 100644
index 000000000..287b559d2
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/CovReport.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka;
+import java.io.File;
+import java.io.IOException;
+import java.util.Vector;
+import javax.xml.transform.OutputKeys;
+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 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.EnumeratedAttribute;
+import org.apache.tools.ant.types.Path;
+import org.w3c.dom.Document;
+
+
+/**
+ * Convenient task to run the snapshot merge utility for JProbe Coverage 3.0.
+ *
+ * @author Stephane Bailliez
+ */
+public class CovReport extends Task
+{
+ /*
+ * jpcoverport [options] -output=file -snapshot=snapshot.jpc
+ * jpcovreport [options] [-paramfile=file] -output= -snapshot=
+ * Generate a report based on the indicated snapshot
+ * -paramfile=file
+ * A text file containing the report generation options.
+ * -format=(html|text|xml) defaults to html
+ * The format of the generated report.
+ * -type=(executive|summary|detailed|verydetailed) defaults to detailed
+ * The type of report to be generated. For -format=xml,
+ * use -type=verydetailed to include source code lines.
+ * Note: A very detailed report can be VERY large.
+ * -percent=num Min 1 Max 101 Default 101
+ * An integer representing a percentage of coverage.
+ * Only methods with test case coverage less than the
+ * percentage are included in reports.
+ * -filters=string
+ * A comma-separated list of filters in the form
+ * .:V, where V can be I for Include or
+ * E for Exclude. For the default package, omit .
+ * -filters_method=string
+ * Optional. A comma-separated list of methods that
+ * correspond one-to-one with the entries in -filters.
+ * -output=string Must be specified
+ * The absolute path and file name for the generated
+ * report file.
+ * -snapshot=string Must be specified
+ * The absolute path and file name of the snapshot file.
+ * -inc_src_text=(on|off) defaults to on
+ * Include text of the source code lines.
+ * Only applies for -format=xml and -type=verydetailed.
+ * -sourcepath=string defaults to .
+ * A semicolon-separated list of source paths.
+ * *
+ * ** coverage home, mandatory
+ */
+ private File home = null;
+
+ /**
+ * format of generated report, optional
+ */
+ private String format = null;
+
+ /**
+ * the name of the output snapshot, mandatory
+ */
+ private File tofile = null;
+
+ /**
+ * type of report, optional
+ */
+ private String type = null;
+
+ /**
+ * threshold value for printing methods, optional
+ */
+ private Integer percent = null;
+
+ /**
+ * comma separated list of filters (???)
+ */
+ private String filters = null;
+
+ /**
+ * name of the snapshot file to create report from
+ */
+ private File snapshot = null;
+
+ /**
+ * sourcepath to use
+ */
+ private Path sourcePath = null;
+
+ /**
+ * include the text for each line of code (xml report verydetailed)
+ */
+ private boolean includeSource = true;
+
+ private Path coveragePath = null;
+
+ /**
+ */
+ private Reference reference = null;
+
+
+ public CovReport() { }
+
+ /**
+ * set the filters
+ *
+ * @param values The new Filters value
+ */
+ public void setFilters( String values )
+ {
+ this.filters = values;
+ }
+
+ /**
+ * set the format of the report html|text|xml
+ *
+ * @param value The new Format value
+ */
+ public void setFormat( ReportFormat value )
+ {
+ this.format = value.getValue();
+ }
+
+
+ /**
+ * Set the coverage home. it must point to JProbe coverage directories where
+ * are stored native libraries and jars.
+ *
+ * @param value The new Home value
+ */
+ public void setHome( File value )
+ {
+ this.home = value;
+ }
+
+ /**
+ * include source code lines. XML report only
+ *
+ * @param value The new Includesource value
+ */
+ public void setIncludesource( boolean value )
+ {
+ this.includeSource = value;
+ }
+
+ /**
+ * sets the threshold printing method 0-100
+ *
+ * @param value The new Percent value
+ */
+ public void setPercent( Integer value )
+ {
+ this.percent = value;
+ }
+
+ public void setSnapshot( File value )
+ {
+ this.snapshot = value;
+ }
+
+ /**
+ * Set the output snapshot file
+ *
+ * @param value The new Tofile value
+ */
+ public void setTofile( File value )
+ {
+ this.tofile = value;
+ }
+
+ /**
+ * sets the report type executive|summary|detailed|verydetailed
+ *
+ * @param value The new Type value
+ */
+ public void setType( ReportType value )
+ {
+ this.type = value.getValue();
+ }
+
+ //@todo to remove
+ public Path createCoveragepath()
+ {
+ if( coveragePath == null )
+ {
+ coveragePath = new Path( project );
+ }
+ return coveragePath.createPath();
+ }
+
+ public Reference createReference()
+ {
+ if( reference == null )
+ {
+ reference = new Reference();
+ }
+ return reference;
+ }
+
+ public Path createSourcepath()
+ {
+ if( sourcePath == null )
+ {
+ sourcePath = new Path( project );
+ }
+ return sourcePath.createPath();
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ checkOptions();
+ try
+ {
+ Commandline cmdl = new Commandline();
+ // we need to run Coverage from his directory due to dll/jar issues
+ cmdl.setExecutable( new File( home, "jpcovreport" ).getAbsolutePath() );
+ String[] params = getParameters();
+ for( int i = 0; i < params.length; i++ )
+ {
+ cmdl.createArgument().setValue( params[i] );
+ }
+
+ // use the custom handler for stdin issues
+ LogStreamHandler handler = new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN );
+ Execute exec = new Execute( handler );
+ log( cmdl.toString(), Project.MSG_VERBOSE );
+ exec.setCommandline( cmdl.getCommandline() );
+ int exitValue = exec.execute();
+ if( exitValue != 0 )
+ {
+ throw new BuildException( "JProbe Coverage Report failed (" + exitValue + ")" );
+ }
+ log( "coveragePath: " + coveragePath, Project.MSG_VERBOSE );
+ log( "format: " + format, Project.MSG_VERBOSE );
+ if( reference != null && "xml".equals( format ) )
+ {
+ reference.createEnhancedXMLReport();
+ }
+
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Failed to execute JProbe Coverage Report.", e );
+ }
+ }
+
+
+ protected String[] getParameters()
+ {
+ Vector v = new Vector();
+ if( format != null )
+ {
+ v.addElement( "-format=" + format );
+ }
+ if( type != null )
+ {
+ v.addElement( "-type=" + type );
+ }
+ if( percent != null )
+ {
+ v.addElement( "-percent=" + percent );
+ }
+ if( filters != null )
+ {
+ v.addElement( "-filters=" + filters );
+ }
+ v.addElement( "-output=" + project.resolveFile( tofile.getPath() ) );
+ v.addElement( "-snapshot=" + project.resolveFile( snapshot.getPath() ) );
+ // as a default -sourcepath use . in JProbe, so use project .
+ if( sourcePath == null )
+ {
+ sourcePath = new Path( project );
+ sourcePath.createPath().setLocation( project.resolveFile( "." ) );
+ }
+ v.addElement( "-sourcepath=" + sourcePath );
+
+ if( "verydetailed".equalsIgnoreCase( format ) && "xml".equalsIgnoreCase( type ) )
+ {
+ v.addElement( "-inc_src_text=" + ( includeSource ? "on" : "off" ) );
+ }
+
+ String[] params = new String[v.size()];
+ v.copyInto( params );
+ return params;
+ }
+
+ /**
+ * check for mandatory options
+ *
+ * @exception BuildException Description of Exception
+ */
+ protected void checkOptions()
+ throws BuildException
+ {
+ if( tofile == null )
+ {
+ throw new BuildException( "'tofile' attribute must be set." );
+ }
+ if( snapshot == null )
+ {
+ throw new BuildException( "'snapshot' attribute must be set." );
+ }
+ if( home == null )
+ {
+ throw new BuildException( "'home' attribute must be set to JProbe home directory" );
+ }
+ home = new File( home, "coverage" );
+ File jar = new File( home, "coverage.jar" );
+ if( !jar.exists() )
+ {
+ throw new BuildException( "Cannot find Coverage directory: " + home );
+ }
+ if( reference != null && !"xml".equals( format ) )
+ {
+ log( "Ignored reference. It cannot be used in non XML report." );
+ reference = null;// nullify it so that there is no ambiguity
+ }
+
+ }
+
+ public static class ReportFormat extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"html", "text", "xml"};
+ }
+ }
+
+ public static class ReportType extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"executive", "summary", "detailed", "verydetailed"};
+ }
+ }
+
+
+ public class Reference
+ {
+ protected Path classPath;
+ protected ReportFilters filters;
+
+ public Path createClasspath()
+ {
+ if( classPath == null )
+ {
+ classPath = new Path( CovReport.this.project );
+ }
+ return classPath.createPath();
+ }
+
+ public ReportFilters createFilters()
+ {
+ if( filters == null )
+ {
+ filters = new ReportFilters();
+ }
+ return filters;
+ }
+
+ protected void createEnhancedXMLReport()
+ throws BuildException
+ {
+ // we need a classpath element
+ if( classPath == null )
+ {
+ throw new BuildException( "Need a 'classpath' element." );
+ }
+ // and a valid one...
+ String[] paths = classPath.list();
+ if( paths.length == 0 )
+ {
+ throw new BuildException( "Coverage path is invalid. It does not contain any existing path." );
+ }
+ // and we need at least one filter include/exclude.
+ if( filters == null || filters.size() == 0 )
+ {
+ createFilters();
+ log( "Adding default include filter to *.*()", Project.MSG_VERBOSE );
+ ReportFilters.Include include = new ReportFilters.Include();
+ filters.addInclude( include );
+ }
+ try
+ {
+ log( "Creating enhanced XML report", Project.MSG_VERBOSE );
+ XMLReport report = new XMLReport( CovReport.this, tofile );
+ report.setReportFilters( filters );
+ report.setJProbehome( new File( home.getParent() ) );
+ Document doc = report.createDocument( paths );
+ TransformerFactory tfactory = TransformerFactory.newInstance();
+ Transformer transformer = tfactory.newTransformer();
+ transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
+ transformer.setOutputProperty( OutputKeys.METHOD, "xml" );
+ Source src = new DOMSource( doc );
+ Result res = new StreamResult( "file:///" + tofile.toString() );
+ transformer.transform( src, res );
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( "Error while performing enhanced XML report from file " + tofile, e );
+ }
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Coverage.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Coverage.java
new file mode 100644
index 000000000..3dc028c44
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Coverage.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Random;
+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.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.EnumeratedAttribute;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Convenient task to run Sitraka JProbe Coverage from Ant. Options are pretty
+ * numerous, you'd better check the manual for a full descriptions of options.
+ * (not that simple since they differ from the online help, from the usage
+ * command line and from the examples...)
+ *
+ * For additional information, visit
+ * www.sitraka.com
+ *
+ * @author Stephane Bailliez
+ */
+public class Coverage extends Task
+{
+
+ protected Commandline cmdl = new Commandline();
+
+ protected CommandlineJava cmdlJava = new CommandlineJava();
+
+ protected String function = "coverage";
+
+ protected boolean applet = false;
+
+ /**
+ * this is a somewhat annoying thing, set it to never
+ */
+ protected String exitPrompt = "never";
+
+ protected Filters filters = new Filters();
+
+ protected String finalSnapshot = "coverage";
+
+ protected String recordFromStart = "coverage";
+
+ protected boolean trackNatives = false;
+
+ protected int warnLevel = 0;
+
+ protected Vector filesets = new Vector();
+
+ protected File home;
+
+ protected File inputFile;
+
+ protected File javaExe;
+
+ protected String seedName;
+
+ protected File snapshotDir;
+
+ protected Socket socket;
+
+ protected Triggers triggers;
+
+ protected String vm;
+
+ protected File workingDir;
+
+
+ //---------------- the tedious job begins here
+
+ public Coverage() { }
+
+ /**
+ * default to false unless file is htm or html
+ *
+ * @param value The new Applet value
+ */
+ public void setApplet( boolean value )
+ {
+ applet = value;
+ }
+
+ /**
+ * classname to run as standalone or runner for filesets
+ *
+ * @param value The new Classname value
+ */
+ public void setClassname( String value )
+ {
+ cmdlJava.setClassname( value );
+ }
+
+ /**
+ * always, error, never
+ *
+ * @param value The new Exitprompt value
+ */
+ public void setExitprompt( String value )
+ {
+ exitPrompt = value;
+ }
+
+ /**
+ * none, coverage, all. can be null, default to none
+ *
+ * @param value The new Finalsnapshot value
+ */
+ public void setFinalsnapshot( String value )
+ {
+ finalSnapshot = value;
+ }
+
+ //--------- setters used via reflection --
+
+ /**
+ * set the coverage home directory where are libraries, jars and jplauncher
+ *
+ * @param value The new Home value
+ */
+ public void setHome( File value )
+ {
+ home = value;
+ }
+
+ public void setInputfile( File value )
+ {
+ inputFile = value;
+ }
+
+ public void setJavaexe( File value )
+ {
+ javaExe = value;
+ }
+
+ /**
+ * all, coverage, none
+ *
+ * @param value The new Recordfromstart value
+ */
+ public void setRecordfromstart( Recordfromstart value )
+ {
+ recordFromStart = value.getValue();
+ }
+
+ /**
+ * seed name for snapshot file. can be null, default to snap
+ *
+ * @param value The new Seedname value
+ */
+ public void setSeedname( String value )
+ {
+ seedName = value;
+ }
+
+ public void setSnapshotdir( File value )
+ {
+ snapshotDir = value;
+ }
+
+ public void setTracknatives( boolean value )
+ {
+ trackNatives = value;
+ }
+
+ /**
+ * jdk117, jdk118 or java2, can be null, default to java2
+ *
+ * @param value The new Vm value
+ */
+ public void setVm( Javavm value )
+ {
+ vm = value.getValue();
+ }
+
+ public void setWarnlevel( Integer value )
+ {
+ warnLevel = value.intValue();
+ }
+
+ public void setWorkingdir( File value )
+ {
+ workingDir = value;
+ }
+
+ /**
+ * the classnames to execute
+ *
+ * @param fs The feature to be added to the Fileset attribute
+ */
+ public void addFileset( FileSet fs )
+ {
+ filesets.addElement( fs );
+ }
+
+ /**
+ * the command arguments
+ *
+ * @return Description of the Returned Value
+ */
+ public Commandline.Argument createArg()
+ {
+ return cmdlJava.createArgument();
+ }
+
+ /**
+ * classpath to run the files
+ *
+ * @return Description of the Returned Value
+ */
+ public Path createClasspath()
+ {
+ return cmdlJava.createClasspath( project ).createPath();
+ }
+
+ public Filters createFilters()
+ {
+ return filters;
+ }
+
+ //
+
+ /**
+ * the jvm arguments
+ *
+ * @return Description of the Returned Value
+ */
+ public Commandline.Argument createJvmarg()
+ {
+ return cmdlJava.createVmArgument();
+ }
+
+ public Socket createSocket()
+ {
+ if( socket == null )
+ {
+ socket = new Socket();
+ }
+ return socket;
+ }
+
+ public Triggers createTriggers()
+ {
+ if( triggers == null )
+ {
+ triggers = new Triggers();
+ }
+ return triggers;
+ }
+
+ /**
+ * execute the jplauncher by providing a parameter file
+ *
+ * @exception BuildException Description of Exception
+ */
+ public void execute()
+ throws BuildException
+ {
+ File paramfile = null;
+ // if an input file is used, all other options are ignored...
+ if( inputFile == null )
+ {
+ checkOptions();
+ paramfile = createParamFile();
+ }
+ else
+ {
+ paramfile = inputFile;
+ }
+ try
+ {
+ // we need to run Coverage from his directory due to dll/jar issues
+ cmdl.setExecutable( new File( home, "jplauncher" ).getAbsolutePath() );
+ cmdl.createArgument().setValue( "-jp_input=" + paramfile.getAbsolutePath() );
+
+ // use the custom handler for stdin issues
+ LogStreamHandler handler = new CoverageStreamHandler( this );
+ Execute exec = new Execute( handler );
+ log( cmdl.toString(), Project.MSG_VERBOSE );
+ exec.setCommandline( cmdl.getCommandline() );
+ int exitValue = exec.execute();
+ if( exitValue != 0 )
+ {
+ throw new BuildException( "JProbe Coverage failed (" + exitValue + ")" );
+ }
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Failed to execute JProbe Coverage.", e );
+ }
+ finally
+ {
+ //@todo should be removed once switched to JDK1.2
+ if( inputFile == null && paramfile != null )
+ {
+ paramfile.delete();
+ }
+ }
+ }
+
+ /**
+ * return the command line parameters. Parameters can either be passed to
+ * the command line and stored to a file (then use the
+ * -jp_input=<filename>) if they are too numerous.
+ *
+ * @return The Parameters value
+ */
+ protected String[] getParameters()
+ {
+ Vector params = new Vector();
+ params.addElement( "-jp_function=" + function );
+ if( vm != null )
+ {
+ params.addElement( "-jp_vm=" + vm );
+ }
+ if( javaExe != null )
+ {
+ params.addElement( "-jp_java_exe=" + project.resolveFile( javaExe.getPath() ) );
+ }
+ params.addElement( "-jp_working_dir=" + workingDir.getPath() );
+ params.addElement( "-jp_snapshot_dir=" + snapshotDir.getPath() );
+ params.addElement( "-jp_record_from_start=" + recordFromStart );
+ params.addElement( "-jp_warn=" + warnLevel );
+ if( seedName != null )
+ {
+ params.addElement( "-jp_output_file=" + seedName );
+ }
+ params.addElement( "-jp_filter=" + filters.toString() );
+ if( triggers != null )
+ {
+ params.addElement( "-jp_trigger=" + triggers.toString() );
+ }
+ if( finalSnapshot != null )
+ {
+ params.addElement( "-jp_final_snapshot=" + finalSnapshot );
+ }
+ params.addElement( "-jp_exit_prompt=" + exitPrompt );
+ //params.addElement("-jp_append=" + append);
+ params.addElement( "-jp_track_natives=" + trackNatives );
+ //.... now the jvm
+ // arguments
+ String[] vmargs = cmdlJava.getVmCommand().getArguments();
+ for( int i = 0; i < vmargs.length; i++ )
+ {
+ params.addElement( vmargs[i] );
+ }
+ // classpath
+ Path classpath = cmdlJava.getClasspath();
+ if( classpath != null && classpath.size() > 0 )
+ {
+ params.addElement( "-classpath " + classpath.toString() );
+ }
+ // classname (runner or standalone)
+ if( cmdlJava.getClassname() != null )
+ {
+ params.addElement( cmdlJava.getClassname() );
+ }
+ // arguments for classname
+ String[] args = cmdlJava.getJavaCommand().getArguments();
+ for( int i = 0; i < args.length; i++ )
+ {
+ params.addElement( args[i] );
+ }
+
+ String[] array = new String[params.size()];
+ params.copyInto( array );
+ return array;
+ }
+
+ /**
+ * wheck what is necessary to check, Coverage will do the job for us
+ *
+ * @exception BuildException Description of Exception
+ */
+ protected void checkOptions()
+ throws BuildException
+ {
+ // check coverage home
+ if( home == null || !home.isDirectory() )
+ {
+ throw new BuildException( "Invalid home directory. Must point to JProbe home directory" );
+ }
+ home = new File( home, "coverage" );
+ File jar = new File( home, "coverage.jar" );
+ if( !jar.exists() )
+ {
+ throw new BuildException( "Cannot find Coverage directory: " + home );
+ }
+
+ // make sure snapshot dir exists and is resolved
+ if( snapshotDir == null )
+ {
+ snapshotDir = new File( "." );
+ }
+ snapshotDir = project.resolveFile( snapshotDir.getPath() );
+ if( !snapshotDir.isDirectory() || !snapshotDir.exists() )
+ {
+ throw new BuildException( "Snapshot directory does not exists :" + snapshotDir );
+ }
+ if( workingDir == null )
+ {
+ workingDir = new File( "." );
+ }
+ workingDir = project.resolveFile( workingDir.getPath() );
+
+ // check for info, do your best to select the java executable.
+ // JProbe 3.0 fails if there is no javaexe option. So
+ if( javaExe == null && ( vm == null || "java2".equals( vm ) ) )
+ {
+ String version = System.getProperty( "java.version" );
+ // make we are using 1.2+, if it is, then do your best to
+ // get a javaexe
+ if( !version.startsWith( "1.1" ) )
+ {
+ if( vm == null )
+ {
+ vm = "java2";
+ }
+ // if we are here obviously it is java2
+ String home = System.getProperty( "java.home" );
+ boolean isUnix = File.separatorChar == '/';
+ javaExe = isUnix ? new File( home, "bin/java" ) : new File( home, "/bin/java.exe" );
+ }
+ }
+ }
+
+
+ /**
+ * create the parameter file from the given options. The file is created
+ * with a random name in the current directory.
+ *
+ * @return the file object where are written the configuration to run JProbe
+ * Coverage
+ * @throws BuildException thrown if something bad happens while writing the
+ * arguments to the file.
+ */
+ protected File createParamFile()
+ throws BuildException
+ {
+ //@todo change this when switching to JDK 1.2 and use File.createTmpFile()
+ File file = createTmpFile();
+ log( "Creating parameter file: " + file, Project.MSG_VERBOSE );
+
+ // options need to be one per line in the parameter file
+ // so write them all in a single string
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter( sw );
+ String[] params = getParameters();
+ for( int i = 0; i < params.length; i++ )
+ {
+ pw.println( params[i] );
+ }
+ pw.flush();
+ log( "JProbe Coverage parameters:\n" + sw.toString(), Project.MSG_VERBOSE );
+
+ // now write them to the file
+ FileWriter fw = null;
+ try
+ {
+ fw = new FileWriter( file );
+ fw.write( sw.toString() );
+ fw.flush();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Could not write parameter file " + file, e );
+ }
+ finally
+ {
+ if( fw != null )
+ {
+ try
+ {
+ fw.close();
+ }
+ catch( IOException ignored )
+ {
+ }
+ }
+ }
+ return file;
+ }
+
+ /**
+ * create a temporary file in the current dir (For JDK1.1 support)
+ *
+ * @return Description of the Returned Value
+ */
+ protected File createTmpFile()
+ {
+ final long rand = ( new Random( System.currentTimeMillis() ) ).nextLong();
+ File file = new File( "jpcoverage" + rand + ".tmp" );
+ return file;
+ }
+
+ public static class Finalsnapshot extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"coverage", "none", "all"};
+ }
+ }
+
+ public static class Javavm extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"java2", "jdk118", "jdk117"};
+ }
+ }
+
+ public static class Recordfromstart extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"coverage", "none", "all"};
+ }
+ }
+
+ /**
+ * specific pumper to avoid those nasty stdin issues
+ *
+ * @author RT
+ */
+ static class CoverageStreamHandler extends LogStreamHandler
+ {
+ CoverageStreamHandler( Task task )
+ {
+ super( task, Project.MSG_INFO, Project.MSG_WARN );
+ }
+
+ /**
+ * there are some issues concerning all JProbe executable In our case a
+ * 'Press ENTER to close this window..." will be displayed in the
+ * current window waiting for enter. So I'm closing the stream right
+ * away to avoid problems.
+ *
+ * @param os The new ProcessInputStream value
+ */
+ public void setProcessInputStream( OutputStream os )
+ {
+ try
+ {
+ os.close();
+ }
+ catch( IOException ignored )
+ {
+ }
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Filters.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Filters.java
new file mode 100644
index 000000000..6b6c9b345
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Filters.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.optional.sitraka;
+import java.util.Vector;
+
+/**
+ * Filters information from coverage, somewhat similar to a FileSet .
+ *
+ * @author Stephane Bailliez
+ */
+public class Filters
+{
+
+ /**
+ * default regexp to exclude everything
+ */
+ public final static String DEFAULT_EXCLUDE = "*.*():E";
+
+ /**
+ * say whether we should use the default excludes or not
+ */
+ protected boolean defaultExclude = true;
+
+ /**
+ * user defined filters
+ */
+ protected Vector filters = new Vector();
+
+ public Filters() { }
+
+ public void setDefaultExclude( boolean value )
+ {
+ defaultExclude = value;
+ }
+
+ public void addExclude( Exclude excl )
+ {
+ filters.addElement( excl );
+ }
+
+ public void addInclude( Include incl )
+ {
+ filters.addElement( incl );
+ }
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ final int size = filters.size();
+ if( defaultExclude )
+ {
+ buf.append( DEFAULT_EXCLUDE );
+ if( size > 0 )
+ {
+ buf.append( ',' );
+ }
+ }
+ for( int i = 0; i < size; i++ )
+ {
+ buf.append( filters.elementAt( i ).toString() );
+ if( i < size - 1 )
+ {
+ buf.append( ',' );
+ }
+ }
+ return buf.toString();
+ }
+
+ public static class Exclude extends FilterElement
+ {
+ public String toString()
+ {
+ return super.toString() + ":E" + ( enabled ? "" : "#" );
+ }
+ }
+
+ public abstract static class FilterElement
+ {
+ protected String method = "*";// default is all methods
+ protected boolean enabled = true;
+ protected String clazz;
+
+ public void setClass( String value )
+ {
+ clazz = value;
+ }
+
+ public void setEnabled( boolean value )
+ {
+ enabled = value;
+ }
+
+ public void setMethod( String value )
+ {
+ method = value;
+ }// default is enable
+
+ public void setName( String value )
+ {// this one is deprecated.
+ clazz = value;
+ }
+
+ public String toString()
+ {
+ return clazz + "." + method + "()";
+ }
+ }
+
+ public static class Include extends FilterElement
+ {
+ public String toString()
+ {
+ return super.toString() + ":I" + ( enabled ? "" : "#" );
+ }
+ }
+}
+
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/ReportFilters.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/ReportFilters.java
new file mode 100644
index 000000000..d8b5f2307
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/ReportFilters.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka;
+import java.util.Vector;
+import org.apache.tools.ant.util.regexp.RegexpMatcher;
+import org.apache.tools.ant.util.regexp.RegexpMatcherFactory;
+
+/**
+ * Filters information from coverage, somewhat similar to a FileSet .
+ *
+ * @author Stephane Bailliez
+ */
+public class ReportFilters
+{
+
+ /**
+ * user defined filters
+ */
+ protected Vector filters = new Vector();
+
+ /**
+ * cached matcher for each filter
+ */
+ protected Vector matchers = null;
+
+ public ReportFilters() { }
+
+ /**
+ * Check whether a given <classname><method>() is accepted by
+ * the list of filters or not.
+ *
+ * @param methodname the full method name in the format
+ * <classname><method>()
+ * @return Description of the Returned Value
+ */
+ public boolean accept( String methodname )
+ {
+ // I'm deferring matcher instantiations at runtime to avoid computing
+ // the filters at parsing time
+ if( matchers == null )
+ {
+ createMatchers();
+ }
+ boolean result = false;
+ // assert filters.size() == matchers.size()
+ final int size = filters.size();
+ for( int i = 0; i < size; i++ )
+ {
+ FilterElement filter = ( FilterElement )filters.elementAt( i );
+ RegexpMatcher matcher = ( RegexpMatcher )matchers.elementAt( i );
+ if( filter instanceof Include )
+ {
+ result = result || matcher.matches( methodname );
+ }
+ else if( filter instanceof Exclude )
+ {
+ result = result && !matcher.matches( methodname );
+ }
+ else
+ {
+ //not possible
+ throw new IllegalArgumentException( "Invalid filter element: " + filter.getClass().getName() );
+ }
+ }
+ return result;
+ }
+
+ public void addExclude( Exclude excl )
+ {
+ filters.addElement( excl );
+ }
+
+ public void addInclude( Include incl )
+ {
+ filters.addElement( incl );
+ }
+
+ public int size()
+ {
+ return filters.size();
+ }
+
+ /**
+ * should be called only once to cache matchers
+ */
+ protected void createMatchers()
+ {
+ RegexpMatcherFactory factory = new RegexpMatcherFactory();
+ final int size = filters.size();
+ matchers = new Vector();
+ for( int i = 0; i < size; i++ )
+ {
+ FilterElement filter = ( FilterElement )filters.elementAt( i );
+ RegexpMatcher matcher = factory.newRegexpMatcher();
+ String pattern = filter.getAsPattern();
+ matcher.setPattern( pattern );
+ matchers.addElement( matcher );
+ }
+ }
+
+ /**
+ * concrete exclude class
+ *
+ * @author RT
+ */
+ public static class Exclude extends FilterElement
+ {
+ }
+
+
+ /**
+ * default abstract filter element class
+ *
+ * @author RT
+ */
+ public abstract static class FilterElement
+ {
+ protected String clazz = "*";// default is all classes
+ protected String method = "*";// default is all methods
+
+ public void setClass( String value )
+ {
+ clazz = value;
+ }
+
+ public void setMethod( String value )
+ {
+ method = value;
+ }
+
+ public String getAsPattern()
+ {
+ StringBuffer buf = new StringBuffer( toString() );
+ StringUtil.replace( buf, ".", "\\." );
+ StringUtil.replace( buf, "*", ".*" );
+ StringUtil.replace( buf, "(", "\\(" );
+ StringUtil.replace( buf, ")", "\\)" );
+ return buf.toString();
+ }
+
+ public String toString()
+ {
+ return clazz + "." + method + "()";
+ }
+ }
+
+ /**
+ * concrete include class
+ *
+ * @author RT
+ */
+ public static class Include extends FilterElement
+ {
+ }
+}
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Socket.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Socket.java
new file mode 100644
index 000000000..2199077a8
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Socket.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.sitraka;
+
+/**
+ * Socket element for connection. <socket/> defaults to host
+ * 127.0.0.1 and port 4444 Otherwise it requires the host and port attributes to
+ * be set: <socket host="e;175.30.12.1"e;
+ * port="e;4567"e;/>
+ *
+ * @author RT
+ */
+public class Socket
+{
+
+ /**
+ * default to localhost
+ */
+ private String host = "127.0.0.1";
+
+ /**
+ * default to 4444
+ */
+ private int port = 4444;
+
+ public void setHost( String value )
+ {
+ host = value;
+ }
+
+ public void setPort( Integer value )
+ {
+ port = value.intValue();
+ }
+
+ /**
+ * if no host is set, returning ':<port>', will take localhost
+ *
+ * @return Description of the Returned Value
+ */
+ public String toString()
+ {
+ return host + ":" + port;
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/StringUtil.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/StringUtil.java
new file mode 100644
index 000000000..5675959ed
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/StringUtil.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.taskdefs.optional.sitraka;
+
+/**
+ * String utilities method.
+ *
+ * @author Stephane Bailliez
+ */
+public final class StringUtil
+{
+ /**
+ * private constructor, it's a utility class
+ */
+ private StringUtil() { }
+
+ /**
+ * Replaces all occurences of find with replacement in the
+ * source StringBuffer.
+ *
+ * @param src the original string buffer to modify.
+ * @param find the string to be replaced.
+ * @param replacement the replacement string for find matches.
+ */
+ public static void replace( StringBuffer src, String find, String replacement )
+ {
+ int index = 0;
+ while( index < src.length() )
+ {
+ index = src.toString().indexOf( find, index );
+ if( index == -1 )
+ {
+ break;
+ }
+ src.delete( index, index + find.length() );
+ src.insert( index, replacement );
+ index += replacement.length() + 1;
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Triggers.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Triggers.java
new file mode 100644
index 000000000..c7d716f44
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Triggers.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.optional.sitraka;
+import java.util.Hashtable;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Trigger information. It will return as a command line argument by calling the
+ * toString() method.
+ *
+ * @author Stephane Bailliez
+ */
+public class Triggers
+{
+
+ /**
+ * mapping of actions to cryptic command line mnemonics
+ */
+ private final static Hashtable actionMap = new Hashtable( 3 );
+
+ /**
+ * mapping of events to cryptic command line mnemonics
+ */
+ private final static Hashtable eventMap = new Hashtable( 3 );
+
+ protected Vector triggers = new Vector();
+
+ static
+ {
+ actionMap.put( "enter", "E" );
+ actionMap.put( "exit", "X" );
+ // clear|pause|resume|snapshot|suspend|exit
+ eventMap.put( "clear", "C" );
+ eventMap.put( "pause", "P" );
+ eventMap.put( "resume", "R" );
+ eventMap.put( "snapshot", "S" );
+ eventMap.put( "suspend", "A" );
+ eventMap.put( "exit", "X" );
+ }
+
+ public Triggers() { }
+
+ public void addMethod( Method method )
+ {
+ triggers.addElement( method );
+ }
+
+ // -jp_trigger=ClassName.*():E:S,ClassName.MethodName():X:X
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ final int size = triggers.size();
+ for( int i = 0; i < size; i++ )
+ {
+ buf.append( triggers.elementAt( i ).toString() );
+ if( i < size - 1 )
+ {
+ buf.append( ',' );
+ }
+ }
+ return buf.toString();
+ }
+
+
+ public static class Method
+ {
+ protected String action;
+ protected String event;
+ protected String name;
+ protected String param;
+
+ public void setAction( String value )
+ throws BuildException
+ {
+ if( actionMap.get( value ) == null )
+ {
+ throw new BuildException( "Invalid action, must be one of " + actionMap );
+ }
+ action = value;
+ }
+
+ public void setEvent( String value )
+ {
+ if( eventMap.get( value ) == null )
+ {
+ throw new BuildException( "Invalid event, must be one of " + eventMap );
+ }
+ event = value;
+ }
+
+ public void setName( String value )
+ {
+ name = value;
+ }
+
+ public void setParam( String value )
+ {
+ param = value;
+ }
+
+ // return ::[:param]
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append( name ).append( ":" );//@todo name must not be null, check for it
+ buf.append( eventMap.get( event ) ).append( ":" );
+ buf.append( actionMap.get( action ) );
+ if( param != null )
+ {
+ buf.append( ":" ).append( param );
+ }
+ return buf.toString();
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/XMLReport.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/XMLReport.java
new file mode 100644
index 000000000..f6ce7d400
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/XMLReport.java
@@ -0,0 +1,676 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka;
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.NoSuchElementException;
+import java.util.Vector;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.ClassFile;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.ClassPathLoader;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.MethodInfo;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.Utils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+/**
+ * Little hack to process XML report from JProbe. It will fix some reporting
+ * errors from JProbe 3.0 and makes use of a reference classpath to add
+ * classes/methods that were not reported by JProbe as being used (ie loaded)
+ *
+ * @author Stephane Bailliez
+ */
+public class XMLReport
+{
+
+ /**
+ * mapping of class names to ClassFiles from the reference
+ * classpath. It is used to filter the JProbe report.
+ */
+ protected Hashtable classFiles;
+
+ /**
+ * mapping classname / class node for faster access
+ */
+ protected Hashtable classMap;
+
+ /**
+ * the XML file to process just from CovReport
+ */
+ protected File file;
+
+ /**
+ * method filters
+ */
+ protected ReportFilters filters;
+
+ /**
+ * jprobe home path. It is used to get the DTD
+ */
+ protected File jprobeHome;
+
+ /**
+ * mapping package name / package node for faster access
+ */
+ protected Hashtable pkgMap;
+
+ /**
+ * parsed document
+ */
+ protected Document report;
+ /**
+ * task caller, can be null, used for logging purpose
+ */
+ protected Task task;
+
+ /**
+ * create a new XML report, logging will be on stdout
+ *
+ * @param file Description of Parameter
+ */
+ public XMLReport( File file )
+ {
+ this( null, file );
+ }
+
+ /**
+ * create a new XML report, logging done on the task
+ *
+ * @param task Description of Parameter
+ * @param file Description of Parameter
+ */
+ public XMLReport( Task task, File file )
+ {
+ this.file = file;
+ this.task = task;
+ }
+
+ private static DocumentBuilder newBuilder()
+ {
+ try
+ {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringComments( true );
+ factory.setValidating( false );
+ return factory.newDocumentBuilder();
+ }
+ catch( Exception e )
+ {
+ throw new ExceptionInInitializerError( e );
+ }
+ }
+
+ /**
+ * set the JProbe home path. Used to get the DTD
+ *
+ * @param home The new JProbehome value
+ */
+ public void setJProbehome( File home )
+ {
+ jprobeHome = home;
+ }
+
+ /**
+ * set the
+ *
+ * @param filters The new ReportFilters value
+ */
+ public void setReportFilters( ReportFilters filters )
+ {
+ this.filters = filters;
+ }
+
+ /**
+ * create the whole new document
+ *
+ * @param classPath Description of Parameter
+ * @return Description of the Returned Value
+ * @exception Exception Description of Exception
+ */
+ public Document createDocument( String[] classPath )
+ throws Exception
+ {
+
+ // Iterate over the classpath to identify reference classes
+ classFiles = new Hashtable();
+ ClassPathLoader cpl = new ClassPathLoader( classPath );
+ Enumeration enum = cpl.loaders();
+ while( enum.hasMoreElements() )
+ {
+ ClassPathLoader.FileLoader fl = ( ClassPathLoader.FileLoader )enum.nextElement();
+ ClassFile[] classes = fl.getClasses();
+ log( "Processing " + classes.length + " classes in " + fl.getFile() );
+ // process all classes
+ for( int i = 0; i < classes.length; i++ )
+ {
+ classFiles.put( classes[i].getFullName(), classes[i] );
+ }
+ }
+
+ // Load the JProbe coverage XML report
+ DocumentBuilder dbuilder = newBuilder();
+ InputSource is = new InputSource( new FileInputStream( file ) );
+ if( jprobeHome != null )
+ {
+ File dtdDir = new File( jprobeHome, "dtd" );
+ is.setSystemId( "file:///" + dtdDir.getAbsolutePath() + "/" );
+ }
+ report = dbuilder.parse( is );
+ report.normalize();
+
+ // create maps for faster node access (also filters out unwanted nodes)
+ createNodeMaps();
+
+ // Make sure each class from the reference path ends up in the report
+ Enumeration classes = classFiles.elements();
+ while( classes.hasMoreElements() )
+ {
+ ClassFile cf = ( ClassFile )classes.nextElement();
+ serializeClass( cf );
+ }
+ // update the document with the stats
+ update();
+ return report;
+ }
+
+ public void log( String message )
+ {
+ if( task == null )
+ {
+ //System.out.println(message);
+ }
+ else
+ {
+ task.log( message, Project.MSG_DEBUG );
+ }
+ }
+
+ protected Element[] getClasses( Element pkg )
+ {
+ Vector v = new Vector();
+ NodeList children = pkg.getChildNodes();
+ int len = children.getLength();
+ for( int i = 0; i < len; i++ )
+ {
+ Node child = children.item( i );
+ if( child.getNodeType() == Node.ELEMENT_NODE )
+ {
+ Element elem = ( Element )child;
+ if( "class".equals( elem.getNodeName() ) )
+ {
+ v.addElement( elem );
+ }
+ }
+ }
+ Element[] elems = new Element[v.size()];
+ v.copyInto( elems );
+ return elems;
+ }
+
+ protected Element getCovDataChild( Element parent )
+ {
+ NodeList children = parent.getChildNodes();
+ int len = children.getLength();
+ for( int i = 0; i < len; i++ )
+ {
+ Node child = children.item( i );
+ if( child.getNodeType() == Node.ELEMENT_NODE )
+ {
+ Element elem = ( Element )child;
+ if( "cov.data".equals( elem.getNodeName() ) )
+ {
+ return elem;
+ }
+ }
+ }
+ throw new NoSuchElementException( "Could not find 'cov.data' element in parent '" + parent.getNodeName() + "'" );
+ }
+
+ protected Vector getFilteredMethods( ClassFile classFile )
+ {
+ MethodInfo[] methodlist = classFile.getMethods();
+ Vector methods = new Vector( methodlist.length );
+ for( int i = 0; i < methodlist.length; i++ )
+ {
+ MethodInfo method = methodlist[i];
+ String signature = getMethodSignature( classFile, method );
+ if( filters.accept( signature ) )
+ {
+ methods.addElement( method );
+ log( "keeping " + signature );
+ }
+ else
+ {
+// log("discarding " + signature);
+ }
+ }
+ return methods;
+ }
+
+ /**
+ * JProbe does not put the java.lang prefix for classes in this package, so
+ * used this nice method so that I have the same signature for methods
+ *
+ * @param method Description of Parameter
+ * @return The MethodSignature value
+ */
+ protected String getMethodSignature( MethodInfo method )
+ {
+ StringBuffer buf = new StringBuffer( method.getName() );
+ buf.append( "(" );
+ String[] params = method.getParametersType();
+ for( int i = 0; i < params.length; i++ )
+ {
+ String type = params[i];
+ int pos = type.lastIndexOf( '.' );
+ if( pos != -1 )
+ {
+ String pkg = type.substring( 0, pos );
+ if( "java.lang".equals( pkg ) )
+ {
+ params[i] = type.substring( pos + 1 );
+ }
+ }
+ buf.append( params[i] );
+ if( i != params.length - 1 )
+ {
+ buf.append( ", " );
+ }
+ }
+ buf.append( ")" );
+ return buf.toString();
+ }
+
+ /**
+ * Convert to a CovReport-like signature ie,
+ * <classname>.<method>()
+ *
+ * @param clazz Description of Parameter
+ * @param method Description of Parameter
+ * @return The MethodSignature value
+ */
+ protected String getMethodSignature( ClassFile clazz, MethodInfo method )
+ {
+ StringBuffer buf = new StringBuffer( clazz.getFullName() );
+ buf.append( "." );
+ buf.append( method.getName() );
+ buf.append( "()" );
+ return buf.toString();
+ }
+
+ protected Hashtable getMethods( Element clazz )
+ {
+ Hashtable map = new Hashtable();
+ NodeList children = clazz.getChildNodes();
+ int len = children.getLength();
+ for( int i = 0; i < len; i++ )
+ {
+ Node child = children.item( i );
+ if( child.getNodeType() == Node.ELEMENT_NODE )
+ {
+ Element elem = ( Element )child;
+ if( "method".equals( elem.getNodeName() ) )
+ {
+ String name = elem.getAttribute( "name" );
+ map.put( name, elem );
+ }
+ }
+ }
+ return map;
+ }
+
+ protected Element[] getPackages( Element snapshot )
+ {
+ Vector v = new Vector();
+ NodeList children = snapshot.getChildNodes();
+ int len = children.getLength();
+ for( int i = 0; i < len; i++ )
+ {
+ Node child = children.item( i );
+ if( child.getNodeType() == Node.ELEMENT_NODE )
+ {
+ Element elem = ( Element )child;
+ if( "package".equals( elem.getNodeName() ) )
+ {
+ v.addElement( elem );
+ }
+ }
+ }
+ Element[] elems = new Element[v.size()];
+ v.copyInto( elems );
+ return elems;
+ }
+
+ /**
+ * create an empty class element with its default cov.data (0)
+ *
+ * @param classFile Description of Parameter
+ * @return Description of the Returned Value
+ */
+ protected Element createClassElement( ClassFile classFile )
+ {
+ // create the class element
+ Element classElem = report.createElement( "class" );
+ classElem.setAttribute( "name", classFile.getName() );
+ // source file possibly does not exist in the bytecode
+ if( null != classFile.getSourceFile() )
+ {
+ classElem.setAttribute( "source", classFile.getSourceFile() );
+ }
+ // create the cov.data elem
+ Element classData = report.createElement( "cov.data" );
+ classElem.appendChild( classData );
+ // create the class cov.data element
+ classData.setAttribute( "calls", "0" );
+ classData.setAttribute( "hit_methods", "0" );
+ classData.setAttribute( "total_methods", "0" );
+ classData.setAttribute( "hit_lines", "0" );
+ classData.setAttribute( "total_lines", "0" );
+ return classElem;
+ }
+
+ /**
+ * create an empty method element with its cov.data values
+ *
+ * @param method Description of Parameter
+ * @return Description of the Returned Value
+ */
+ protected Element createMethodElement( MethodInfo method )
+ {
+ String methodsig = getMethodSignature( method );
+ Element methodElem = report.createElement( "method" );
+ methodElem.setAttribute( "name", methodsig );
+ // create the method cov.data element
+ Element methodData = report.createElement( "cov.data" );
+ methodElem.appendChild( methodData );
+ methodData.setAttribute( "calls", "0" );
+ methodData.setAttribute( "hit_lines", "0" );
+ methodData.setAttribute( "total_lines", String.valueOf( method.getNumberOfLines() ) );
+ return methodElem;
+ }
+
+
+ /**
+ * create node maps so that we can access node faster by their name
+ */
+ protected void createNodeMaps()
+ {
+ pkgMap = new Hashtable();
+ classMap = new Hashtable();
+ // create a map index of all packages by their name
+ // @todo can be done faster by direct access.
+ NodeList packages = report.getElementsByTagName( "package" );
+ final int pkglen = packages.getLength();
+ log( "Indexing " + pkglen + " packages" );
+ for( int i = pkglen - 1; i > -1; i-- )
+ {
+ Element pkg = ( Element )packages.item( i );
+ String pkgname = pkg.getAttribute( "name" );
+
+ int nbclasses = 0;
+ // create a map index of all classes by their fully
+ // qualified name.
+ NodeList classes = pkg.getElementsByTagName( "class" );
+ final int classlen = classes.getLength();
+ log( "Indexing " + classlen + " classes in package " + pkgname );
+ for( int j = classlen - 1; j > -1; j-- )
+ {
+ Element clazz = ( Element )classes.item( j );
+ String classname = clazz.getAttribute( "name" );
+ if( pkgname != null && pkgname.length() != 0 )
+ {
+ classname = pkgname + "." + classname;
+ }
+
+ int nbmethods = 0;
+ NodeList methods = clazz.getElementsByTagName( "method" );
+ final int methodlen = methods.getLength();
+ for( int k = methodlen - 1; k > -1; k-- )
+ {
+ Element meth = ( Element )methods.item( k );
+ StringBuffer methodname = new StringBuffer( meth.getAttribute( "name" ) );
+ methodname.delete( methodname.toString().indexOf( "(" ), methodname.toString().length() );
+ String signature = classname + "." + methodname + "()";
+ if( filters.accept( signature ) )
+ {
+ log( "kept method:" + signature );
+ nbmethods++;
+ }
+ else
+ {
+ clazz.removeChild( meth );
+ }
+ }
+ // if we don't keep any method, we don't keep the class
+ if( nbmethods != 0 && classFiles.containsKey( classname ) )
+ {
+ log( "Adding class '" + classname + "'" );
+ classMap.put( classname, clazz );
+ nbclasses++;
+ }
+ else
+ {
+ pkg.removeChild( clazz );
+ }
+ }
+ if( nbclasses != 0 )
+ {
+ log( "Adding package '" + pkgname + "'" );
+ pkgMap.put( pkgname, pkg );
+ }
+ else
+ {
+ pkg.getParentNode().removeChild( pkg );
+ }
+ }
+ log( "Indexed " + classMap.size() + " classes in " + pkgMap.size() + " packages" );
+ }
+
+ /**
+ * create an empty package element with its default cov.data (0)
+ *
+ * @param pkgname Description of Parameter
+ * @return Description of the Returned Value
+ */
+ protected Element createPackageElement( String pkgname )
+ {
+ Element pkgElem = report.createElement( "package" );
+ pkgElem.setAttribute( "name", pkgname );
+ // create the package cov.data element / default
+ // must be updated at the end of the whole process
+ Element pkgData = report.createElement( "cov.data" );
+ pkgElem.appendChild( pkgData );
+ pkgData.setAttribute( "calls", "0" );
+ pkgData.setAttribute( "hit_methods", "0" );
+ pkgData.setAttribute( "total_methods", "0" );
+ pkgData.setAttribute( "hit_lines", "0" );
+ pkgData.setAttribute( "total_lines", "0" );
+ return pkgElem;
+ }
+
+ /**
+ * Do additional work on an element to remove abstract methods that are
+ * reported by JProbe 3.0
+ *
+ * @param classFile Description of Parameter
+ * @param classNode Description of Parameter
+ */
+ protected void removeAbstractMethods( ClassFile classFile, Element classNode )
+ {
+ MethodInfo[] methods = classFile.getMethods();
+ Hashtable methodNodeList = getMethods( classNode );
+ // assert xmlMethods.size() == methods.length()
+ final int size = methods.length;
+ for( int i = 0; i < size; i++ )
+ {
+ MethodInfo method = methods[i];
+ String methodSig = getMethodSignature( method );
+ Element methodNode = ( Element )methodNodeList.get( methodSig );
+ if( methodNode != null &&
+ Utils.isAbstract( method.getAccessFlags() ) )
+ {
+ log( "\tRemoving abstract method " + methodSig );
+ classNode.removeChild( methodNode );
+ }
+ }
+ }
+
+ /**
+ * serialize a classfile into XML
+ *
+ * @param classFile Description of Parameter
+ */
+ protected void serializeClass( ClassFile classFile )
+ {
+ // the class already is reported so ignore it
+ String fullclassname = classFile.getFullName();
+ log( "Looking for '" + fullclassname + "'" );
+ Element clazz = ( Element )classMap.get( fullclassname );
+
+ // ignore classes that are already reported, all the information is
+ // already there.
+ if( clazz != null )
+ {
+ log( "Ignoring " + fullclassname );
+ removeAbstractMethods( classFile, clazz );
+ return;
+ }
+
+ // ignore interfaces files, there is no code in there to cover.
+ if( Utils.isInterface( classFile.getAccess() ) )
+ {
+ return;
+ }
+
+ Vector methods = getFilteredMethods( classFile );
+ // no need to process, there are no methods to add for this class.
+ if( methods.size() == 0 )
+ {
+ return;
+ }
+
+ String pkgname = classFile.getPackage();
+ // System.out.println("Looking for package " + pkgname);
+ Element pkgElem = ( Element )pkgMap.get( pkgname );
+ if( pkgElem == null )
+ {
+ pkgElem = createPackageElement( pkgname );
+ report.getDocumentElement().appendChild( pkgElem );
+ pkgMap.put( pkgname, pkgElem );// add the pkg to the map
+ }
+ // this is a brand new class, so we have to create a new node
+
+ // create the class element
+ Element classElem = createClassElement( classFile );
+ pkgElem.appendChild( classElem );
+
+ int total_lines = 0;
+ int total_methods = 0;
+ for( int i = 0; i < methods.size(); i++ )
+ {
+ // create the method element
+ MethodInfo method = ( MethodInfo )methods.elementAt( i );
+ if( Utils.isAbstract( method.getAccessFlags() ) )
+ {
+ continue;// no need to report abstract methods
+ }
+ Element methodElem = createMethodElement( method );
+ classElem.appendChild( methodElem );
+ total_lines += method.getNumberOfLines();
+ total_methods++;
+ }
+ // create the class cov.data element
+ Element classData = getCovDataChild( classElem );
+ classData.setAttribute( "total_methods", String.valueOf( total_methods ) );
+ classData.setAttribute( "total_lines", String.valueOf( total_lines ) );
+
+ // add itself to the node map
+ classMap.put( fullclassname, classElem );
+ }
+
+
+ /**
+ * update the count of the XML, that is accumulate the stats on methods,
+ * classes and package so that the numbers are valid according to the info
+ * that was appended to the XML.
+ */
+ protected void update()
+ {
+ int calls = 0;
+ int hit_methods = 0;
+ int total_methods = 0;
+ int hit_lines = 0;
+ int total_lines = 0;
+
+ // use the map for access, all nodes should be there
+ Enumeration enum = pkgMap.elements();
+ while( enum.hasMoreElements() )
+ {
+ Element pkgElem = ( Element )enum.nextElement();
+ String pkgname = pkgElem.getAttribute( "name" );
+ Element[] classes = getClasses( pkgElem );
+ int pkg_calls = 0;
+ int pkg_hit_methods = 0;
+ int pkg_total_methods = 0;
+ int pkg_hit_lines = 0;
+ int pkg_total_lines = 0;
+ //System.out.println("Processing package '" + pkgname + "': " + classes.length + " classes");
+ for( int j = 0; j < classes.length; j++ )
+ {
+ Element clazz = classes[j];
+ String classname = clazz.getAttribute( "name" );
+ if( pkgname != null && pkgname.length() != 0 )
+ {
+ classname = pkgname + "." + classname;
+ }
+ // there's only cov.data as a child so bet on it
+ Element covdata = getCovDataChild( clazz );
+ try
+ {
+ pkg_calls += Integer.parseInt( covdata.getAttribute( "calls" ) );
+ pkg_hit_methods += Integer.parseInt( covdata.getAttribute( "hit_methods" ) );
+ pkg_total_methods += Integer.parseInt( covdata.getAttribute( "total_methods" ) );
+ pkg_hit_lines += Integer.parseInt( covdata.getAttribute( "hit_lines" ) );
+ pkg_total_lines += Integer.parseInt( covdata.getAttribute( "total_lines" ) );
+ }
+ catch( NumberFormatException e )
+ {
+ log( "Error parsing '" + classname + "' (" + j + "/" + classes.length + ") in package '" + pkgname + "'" );
+ throw e;
+ }
+ }
+ Element covdata = getCovDataChild( pkgElem );
+ covdata.setAttribute( "calls", String.valueOf( pkg_calls ) );
+ covdata.setAttribute( "hit_methods", String.valueOf( pkg_hit_methods ) );
+ covdata.setAttribute( "total_methods", String.valueOf( pkg_total_methods ) );
+ covdata.setAttribute( "hit_lines", String.valueOf( pkg_hit_lines ) );
+ covdata.setAttribute( "total_lines", String.valueOf( pkg_total_lines ) );
+ calls += pkg_calls;
+ hit_methods += pkg_hit_methods;
+ total_methods += pkg_total_methods;
+ hit_lines += pkg_hit_lines;
+ total_lines += pkg_total_lines;
+ }
+ Element covdata = getCovDataChild( report.getDocumentElement() );
+ covdata.setAttribute( "calls", String.valueOf( calls ) );
+ covdata.setAttribute( "hit_methods", String.valueOf( hit_methods ) );
+ covdata.setAttribute( "total_methods", String.valueOf( total_methods ) );
+ covdata.setAttribute( "hit_lines", String.valueOf( hit_lines ) );
+ covdata.setAttribute( "total_lines", String.valueOf( total_lines ) );
+ }
+
+}
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassFile.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassFile.java
new file mode 100644
index 000000000..25bf53a4d
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassFile.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.sitraka.bytecode;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+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.Utf8CPInfo;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.attributes.AttributeInfo;
+
+
+/**
+ * Object representing a class. Information are kept to the strict minimum for
+ * JProbe reports so that not too many objects are created for a class,
+ * otherwise the JVM can quickly run out of memory when analyzing a great deal
+ * of classes and keeping them in memory for global analysis.
+ *
+ * @author Stephane Bailliez
+ */
+public final class ClassFile
+{
+
+ private int access_flags;
+
+ private String fullname;
+
+ private MethodInfo[] methods;
+
+ private String sourceFile;
+
+ public ClassFile( InputStream is )
+ throws IOException
+ {
+ DataInputStream dis = new DataInputStream( is );
+ ConstantPool constantPool = new ConstantPool();
+
+ int magic = dis.readInt();// 0xCAFEBABE
+ int minor = dis.readShort();
+ int major = dis.readShort();
+
+ constantPool.read( dis );
+ constantPool.resolve();
+
+ // class information
+ access_flags = dis.readShort();
+ int this_class = dis.readShort();
+ fullname = ( ( ClassCPInfo )constantPool.getEntry( this_class ) ).getClassName().replace( '/', '.' );
+ int super_class = dis.readShort();
+
+ // skip interfaces...
+ int count = dis.readShort();
+ dis.skipBytes( count * 2 );// short
+
+ // skip fields...
+ int numFields = dis.readShort();
+ for( int i = 0; i < numFields; i++ )
+ {
+ // 3 short: access flags, name index, descriptor index
+ dis.skip( 2 * 3 );
+ // attribute list...
+ int attributes_count = dis.readUnsignedShort();
+ for( int j = 0; j < attributes_count; j++ )
+ {
+ dis.skipBytes( 2 );// skip attr_id (short)
+ int len = dis.readInt();
+ dis.skipBytes( len );
+ }
+ }
+
+ // read methods
+ int method_count = dis.readShort();
+ methods = new MethodInfo[method_count];
+ for( int i = 0; i < method_count; i++ )
+ {
+ methods[i] = new MethodInfo();
+ methods[i].read( constantPool, dis );
+ }
+
+ // get interesting attributes.
+ int attributes_count = dis.readUnsignedShort();
+ for( int j = 0; j < attributes_count; j++ )
+ {
+ int attr_id = dis.readShort();
+ int len = dis.readInt();
+ String attr_name = Utils.getUTF8Value( constantPool, attr_id );
+ if( AttributeInfo.SOURCE_FILE.equals( attr_name ) )
+ {
+ int name_index = dis.readShort();
+ sourceFile = ( ( Utf8CPInfo )constantPool.getEntry( name_index ) ).getValue();
+ }
+ else
+ {
+ dis.skipBytes( len );
+ }
+ }
+ }
+
+ public int getAccess()
+ {
+ return access_flags;
+ }
+
+ public String getFullName()
+ {
+ return fullname;
+ }
+
+ public MethodInfo[] getMethods()
+ {
+ return methods;
+ }
+
+ public String getName()
+ {
+ String name = getFullName();
+ int pos = name.lastIndexOf( '.' );
+ if( pos == -1 )
+ {
+ return "";
+ }
+ return name.substring( pos + 1 );
+ }
+
+ public String getPackage()
+ {
+ String name = getFullName();
+ int pos = name.lastIndexOf( '.' );
+ if( pos == -1 )
+ {
+ return "";
+ }
+ return name.substring( 0, pos );
+ }
+
+ public String getSourceFile()
+ {
+ return sourceFile;
+ }
+
+}
+
+
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassPathLoader.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassPathLoader.java
new file mode 100644
index 000000000..26f19526c
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassPathLoader.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka.bytecode;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Core of the bytecode analyzer. It loads classes from a given classpath.
+ *
+ * @author Stephane Bailliez
+ */
+public class ClassPathLoader
+{
+
+ public final static FileLoader NULL_LOADER = new NullLoader();
+
+ /**
+ * the list of files to look for
+ */
+ protected File[] files;
+
+ /**
+ * create a new instance with a given classpath. It must be urls separated
+ * by the platform specific path separator.
+ *
+ * @param classPath the classpath to load all the classes from.
+ */
+ public ClassPathLoader( String classPath )
+ {
+ StringTokenizer st = new StringTokenizer( classPath, File.pathSeparator );
+ Vector entries = new Vector();
+ while( st.hasMoreTokens() )
+ {
+ File file = new File( st.nextToken() );
+ entries.addElement( file );
+ }
+ files = new File[entries.size()];
+ entries.copyInto( files );
+ }
+
+ /**
+ * create a new instance with a given set of urls.
+ *
+ * @param entries valid file urls (either .jar, .zip or directory)
+ */
+ public ClassPathLoader( String[] entries )
+ {
+ files = new File[entries.length];
+ for( int i = 0; i < entries.length; i++ )
+ {
+ files[i] = new File( entries[i] );
+ }
+ }
+
+ /**
+ * create a new instance with a given set of urls
+ *
+ * @param entries file urls to look for classes (.jar, .zip or directory)
+ */
+ public ClassPathLoader( File[] entries )
+ {
+ files = entries;
+ }
+
+ /**
+ * useful methods to read the whole input stream in memory so that it can be
+ * accessed faster. Processing rt.jar and tools.jar from JDK 1.3.1 brings
+ * time from 50s to 7s.
+ *
+ * @param is Description of Parameter
+ * @return The CachedStream value
+ * @exception IOException Description of Exception
+ */
+ public static InputStream getCachedStream( InputStream is )
+ throws IOException
+ {
+ is = new BufferedInputStream( is );
+ byte[] buffer = new byte[8192];
+ ByteArrayOutputStream baos = new ByteArrayOutputStream( 2048 );
+ int n;
+ baos.reset();
+ while( ( n = is.read( buffer, 0, buffer.length ) ) != -1 )
+ {
+ baos.write( buffer, 0, n );
+ }
+ is.close();
+ return new ByteArrayInputStream( baos.toByteArray() );
+ }
+
+ /**
+ * return the whole set of classes in the classpath. Note that this method
+ * can be very resource demanding since it must load all bytecode from all
+ * classes in all resources in the classpath at a time. To process it in a
+ * less resource demanding way, it is maybe better to use the loaders()
+ * that will return loader one by one.
+ *
+ * @return the hashtable containing ALL classes that are found in the given
+ * classpath. Note that the first entry of a given classname will
+ * shadow classes with the same name (as a classloader does)
+ * @exception IOException Description of Exception
+ */
+ public Hashtable getClasses()
+ throws IOException
+ {
+ Hashtable map = new Hashtable();
+ Enumeration enum = loaders();
+ while( enum.hasMoreElements() )
+ {
+ FileLoader loader = ( FileLoader )enum.nextElement();
+ System.out.println( "Processing " + loader.getFile() );
+ long t0 = System.currentTimeMillis();
+ ClassFile[] classes = loader.getClasses();
+ long dt = System.currentTimeMillis() - t0;
+ System.out.println( "" + classes.length + " classes loaded in " + dt + "ms" );
+ for( int j = 0; j < classes.length; j++ )
+ {
+ String name = classes[j].getFullName();
+ // do not allow duplicates entries to preserve 'classpath' behavior
+ // first class in wins
+ if( !map.containsKey( name ) )
+ {
+ map.put( name, classes[j] );
+ }
+ }
+ }
+ return map;
+ }
+
+ /**
+ * @return the set of FileLoader loaders matching the given
+ * classpath.
+ */
+ public Enumeration loaders()
+ {
+ return new LoaderEnumeration();
+ }
+
+ /**
+ * the interface to implement to look up for specific resources
+ *
+ * @author RT
+ */
+ public interface FileLoader
+ {
+ /**
+ * the file url that is looked for .class files
+ *
+ * @return The File value
+ */
+ public File getFile();
+
+ /**
+ * return the set of classes found in the file
+ *
+ * @return The Classes value
+ * @exception IOException Description of Exception
+ */
+ public ClassFile[] getClasses()
+ throws IOException;
+ }
+
+ /**
+ * the loader enumeration that will return loaders
+ *
+ * @author RT
+ */
+ protected class LoaderEnumeration implements Enumeration
+ {
+ protected int index = 0;
+
+ public boolean hasMoreElements()
+ {
+ return index < files.length;
+ }
+
+ public Object nextElement()
+ {
+ if( index >= files.length )
+ {
+ throw new NoSuchElementException();
+ }
+ File file = files[index++];
+ if( !file.exists() )
+ {
+ return new NullLoader( file );
+ }
+ if( file.isDirectory() )
+ {
+ // it's a directory
+ return new DirectoryLoader( file );
+ }
+ else if( file.getName().endsWith( ".zip" ) || file.getName().endsWith( ".jar" ) )
+ {
+ // it's a jar/zip file
+ return new JarLoader( file );
+ }
+ return new NullLoader( file );
+ }
+ }
+}
+
+/**
+ * a null loader to return when the file is not valid
+ *
+ * @author RT
+ */
+class NullLoader implements ClassPathLoader.FileLoader
+{
+
+ private File file;
+
+ NullLoader()
+ {
+ this( null );
+ }
+
+ NullLoader( File file )
+ {
+ this.file = file;
+ }
+
+ public ClassFile[] getClasses()
+ throws IOException
+ {
+ return new ClassFile[0];
+ }
+
+ public File getFile()
+ {
+ return file;
+ }
+}
+
+/**
+ * jar loader specified in looking for classes in jar and zip
+ *
+ * @author RT
+ * @todo read the jar manifest in case there is a Class-Path entry.
+ */
+class JarLoader implements ClassPathLoader.FileLoader
+{
+
+ private File file;
+
+ JarLoader( File file )
+ {
+ this.file = file;
+ }
+
+ public ClassFile[] getClasses()
+ throws IOException
+ {
+ ZipFile zipFile = new ZipFile( file );
+ Vector v = new Vector();
+ Enumeration entries = zipFile.entries();
+ while( entries.hasMoreElements() )
+ {
+ ZipEntry entry = ( ZipEntry )entries.nextElement();
+ if( entry.getName().endsWith( ".class" ) )
+ {
+ InputStream is = ClassPathLoader.getCachedStream( zipFile.getInputStream( entry ) );
+ ClassFile classFile = new ClassFile( is );
+ is.close();
+ v.addElement( classFile );
+ }
+ }
+ ClassFile[] classes = new ClassFile[v.size()];
+ v.copyInto( classes );
+ return classes;
+ }
+
+ public File getFile()
+ {
+ return file;
+ }
+}
+
+/**
+ * directory loader that will look all classes recursively
+ *
+ * @author RT
+ * @todo should discard classes which package name does not match the directory
+ * ?
+ */
+class DirectoryLoader implements ClassPathLoader.FileLoader
+{
+
+ private File directory;
+
+ DirectoryLoader( File dir )
+ {
+ directory = dir;
+ }
+
+ /**
+ * List files that obeys to a specific filter recursively from a given base
+ * directory.
+ *
+ * @param directory the directory where to list the files from.
+ * @param filter the file filter to apply
+ * @param recurse tells whether or not the listing is recursive.
+ * @return the list of File objects that applies to the given
+ * filter.
+ */
+ public static Vector listFiles( File directory, FilenameFilter filter, boolean recurse )
+ {
+ if( !directory.isDirectory() )
+ {
+ throw new IllegalArgumentException( directory + " is not a directory" );
+ }
+ Vector list = new Vector();
+ listFilesTo( list, directory, filter, recurse );
+ return list;
+ }
+
+ /**
+ * List and add files to a given list. As a convenience it sends back the
+ * instance of the list given as a parameter.
+ *
+ * @param list the list of files where the filtered files should be added
+ * @param directory the directory where to list the files from.
+ * @param filter the file filter to apply
+ * @param recurse tells whether or not the listing is recursive.
+ * @return the list instance that was passed as the list argument.
+ */
+ private static Vector listFilesTo( Vector list, File directory, FilenameFilter filter, boolean recurse )
+ {
+ String[] files = directory.list( filter );
+ for( int i = 0; i < files.length; i++ )
+ {
+ list.addElement( new File( directory, files[i] ) );
+ }
+ files = null;// we don't need it anymore
+ if( recurse )
+ {
+ String[] subdirs = directory.list( new DirectoryFilter() );
+ for( int i = 0; i < subdirs.length; i++ )
+ {
+ listFilesTo( list, new File( directory, subdirs[i] ), filter, recurse );
+ }
+ }
+ return list;
+ }
+
+ public ClassFile[] getClasses()
+ throws IOException
+ {
+ Vector v = new Vector();
+ Vector files = listFiles( directory, new ClassFilter(), true );
+ for( int i = 0; i < files.size(); i++ )
+ {
+ File file = ( File )files.elementAt( i );
+ InputStream is = null;
+ try
+ {
+ is = ClassPathLoader.getCachedStream( new FileInputStream( file ) );
+ ClassFile classFile = new ClassFile( is );
+ is.close();
+ is = null;
+ v.addElement( classFile );
+ }
+ finally
+ {
+ if( is != null )
+ {
+ try
+ {
+ is.close();
+ }
+ catch( IOException ignored )
+ {}
+ }
+ }
+ }
+ ClassFile[] classes = new ClassFile[v.size()];
+ v.copyInto( classes );
+ return classes;
+ }
+
+ public File getFile()
+ {
+ return directory;
+ }
+
+}
+
+/**
+ * Convenient filter that accepts only directory File
+ *
+ * @author RT
+ */
+class DirectoryFilter implements FilenameFilter
+{
+
+ public boolean accept( File directory, String name )
+ {
+ File pathname = new File( directory, name );
+ return pathname.isDirectory();
+ }
+}
+
+/**
+ * convenient filter to accept only .class files
+ *
+ * @author RT
+ */
+class ClassFilter implements FilenameFilter
+{
+
+ public boolean accept( File dir, String name )
+ {
+ return name.endsWith( ".class" );
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/MethodInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/MethodInfo.java
new file mode 100644
index 000000000..5d335ae3b
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/MethodInfo.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.sitraka.bytecode;
+import java.io.DataInputStream;
+import java.io.IOException;
+import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPool;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.attributes.AttributeInfo;
+
+/**
+ * Method info structure.
+ *
+ * @author Stephane Bailliez
+ * @todo give a more appropriate name to methods.
+ */
+public final class MethodInfo
+{
+ private int loc = -1;
+ private int access_flags;
+ private String descriptor;
+ private String name;
+
+ public MethodInfo() { }
+
+ public String getAccess()
+ {
+ return Utils.getMethodAccess( access_flags );
+ }
+
+ public int getAccessFlags()
+ {
+ return access_flags;
+ }
+
+ public String getDescriptor()
+ {
+ return descriptor;
+ }
+
+ public String getFullSignature()
+ {
+ return getReturnType() + " " + getShortSignature();
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public int getNumberOfLines()
+ {
+ return loc;
+ }
+
+ public String[] getParametersType()
+ {
+ return Utils.getMethodParams( getDescriptor() );
+ }
+
+ public String getReturnType()
+ {
+ return Utils.getMethodReturnType( getDescriptor() );
+ }
+
+ public String getShortSignature()
+ {
+ StringBuffer buf = new StringBuffer( getName() );
+ buf.append( "(" );
+ String[] params = getParametersType();
+ for( int i = 0; i < params.length; i++ )
+ {
+ buf.append( params[i] );
+ if( i != params.length - 1 )
+ {
+ buf.append( ", " );
+ }
+ }
+ buf.append( ")" );
+ return buf.toString();
+ }
+
+ public void read( ConstantPool constantPool, DataInputStream dis )
+ throws IOException
+ {
+ access_flags = dis.readShort();
+
+ int name_index = dis.readShort();
+ name = Utils.getUTF8Value( constantPool, name_index );
+
+ int descriptor_index = dis.readShort();
+ descriptor = Utils.getUTF8Value( constantPool, descriptor_index );
+
+ int attributes_count = dis.readUnsignedShort();
+ for( int i = 0; i < attributes_count; i++ )
+ {
+ int attr_id = dis.readShort();
+ String attr_name = Utils.getUTF8Value( constantPool, attr_id );
+ int len = dis.readInt();
+ if( AttributeInfo.CODE.equals( attr_name ) )
+ {
+ readCode( constantPool, dis );
+ }
+ else
+ {
+ dis.skipBytes( len );
+ }
+ }
+
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append( "Method: " ).append( getAccess() ).append( " " );
+ sb.append( getFullSignature() );
+ return sb.toString();
+ }
+
+ protected void readCode( ConstantPool constantPool, DataInputStream dis )
+ throws IOException
+ {
+ // skip max_stack (short), max_local (short)
+ dis.skipBytes( 2 * 2 );
+
+ // skip bytecode...
+ int bytecode_len = dis.readInt();
+ dis.skip( bytecode_len );
+
+ // skip exceptions... 1 exception = 4 short.
+ int exception_count = dis.readShort();
+ dis.skipBytes( exception_count * 4 * 2 );
+
+ // read attributes...
+ int attributes_count = dis.readUnsignedShort();
+ for( int i = 0; i < attributes_count; i++ )
+ {
+ int attr_id = dis.readShort();
+ String attr_name = Utils.getUTF8Value( constantPool, attr_id );
+ int len = dis.readInt();
+ if( AttributeInfo.LINE_NUMBER_TABLE.equals( attr_name ) )
+ {
+ // we're only interested in lines of code...
+ loc = dis.readShort();
+ // skip the table which is 2*loc*short
+ dis.skip( loc * 2 * 2 );
+ }
+ else
+ {
+ dis.skipBytes( len );
+ }
+ }
+ }
+}
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/Utils.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/Utils.java
new file mode 100644
index 000000000..74af7a06e
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/Utils.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka.bytecode;
+import java.util.Vector;
+import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPool;
+import org.apache.tools.ant.taskdefs.optional.depend.constantpool.Utf8CPInfo;
+
+/**
+ * Utilities mostly to manipulate methods and access flags.
+ *
+ * @author Stephane Bailliez
+ */
+public class Utils
+{
+ /**
+ * public access flag
+ */
+ public final static short ACC_PUBLIC = 1;
+ /**
+ * private access flag
+ */
+ public final static short ACC_PRIVATE = 2;
+ /**
+ * protected access flag
+ */
+ public final static short ACC_PROTECTED = 4;
+ /**
+ * static access flag
+ */
+ public final static short ACC_STATIC = 8;
+ /**
+ * final access flag
+ */
+ public final static short ACC_FINAL = 16;
+ /**
+ * super access flag
+ */
+ public final static short ACC_SUPER = 32;
+ /**
+ * synchronized access flag
+ */
+ public final static short ACC_SYNCHRONIZED = 32;
+ /**
+ * volatile access flag
+ */
+ public final static short ACC_VOLATILE = 64;
+ /**
+ * transient access flag
+ */
+ public final static short ACC_TRANSIENT = 128;
+ /**
+ * native access flag
+ */
+ public final static short ACC_NATIVE = 256;
+ /**
+ * interface access flag
+ */
+ public final static short ACC_INTERFACE = 512;
+ /**
+ * abstract access flag
+ */
+ public final static short ACC_ABSTRACT = 1024;
+ /**
+ * strict access flag
+ */
+ public final static short ACC_STRICT = 2048;
+
+ /**
+ * private constructor
+ */
+ private Utils() { }
+
+ /**
+ * return the class access flag as java modifiers
+ *
+ * @param access_flags access flags
+ * @return the access flags as modifier strings
+ */
+ public static String getClassAccess( int access_flags )
+ {
+ StringBuffer sb = new StringBuffer();
+ if( isPublic( access_flags ) )
+ {
+ sb.append( "public " );
+ }
+ else if( isProtected( access_flags ) )
+ {
+ sb.append( "protected " );
+ }
+ else if( isPrivate( access_flags ) )
+ {
+ sb.append( "private " );
+ }
+ if( isFinal( access_flags ) )
+ {
+ sb.append( "final " );
+ }
+ if( isSuper( access_flags ) )
+ {
+ sb.append( "/*super*/ " );
+ }
+ if( isInterface( access_flags ) )
+ {
+ sb.append( "interface " );
+ }
+ if( isAbstract( access_flags ) )
+ {
+ sb.append( "abstract " );
+ }
+ if( isClass( access_flags ) )
+ {
+ sb.append( "class " );
+ }
+ return sb.toString().trim();
+ }
+
+ /**
+ * return the field access flag as java modifiers
+ *
+ * @param access_flags access flags
+ * @return the access flags as modifier strings
+ */
+ public static String getFieldAccess( int access_flags )
+ {
+ StringBuffer sb = new StringBuffer();
+ if( isPublic( access_flags ) )
+ {
+ sb.append( "public " );
+ }
+ else if( isPrivate( access_flags ) )
+ {
+ sb.append( "private " );
+ }
+ else if( isProtected( access_flags ) )
+ {
+ sb.append( "protected " );
+ }
+ if( isFinal( access_flags ) )
+ {
+ sb.append( "final " );
+ }
+ if( isStatic( access_flags ) )
+ {
+ sb.append( "static " );
+ }
+ if( isVolatile( access_flags ) )
+ {
+ sb.append( "volatile " );
+ }
+ if( isTransient( access_flags ) )
+ {
+ sb.append( "transient " );
+ }
+ return sb.toString().trim();
+ }
+
+ /**
+ * return the method access flag as java modifiers
+ *
+ * @param access_flags access flags
+ * @return the access flags as modifier strings
+ */
+ public static String getMethodAccess( int access_flags )
+ {
+ StringBuffer sb = new StringBuffer();
+ if( isPublic( access_flags ) )
+ {
+ sb.append( "public " );
+ }
+ else if( isPrivate( access_flags ) )
+ {
+ sb.append( "private " );
+ }
+ else if( isProtected( access_flags ) )
+ {
+ sb.append( "protected " );
+ }
+ if( isFinal( access_flags ) )
+ {
+ sb.append( "final " );
+ }
+ if( isStatic( access_flags ) )
+ {
+ sb.append( "static " );
+ }
+ if( isSynchronized( access_flags ) )
+ {
+ sb.append( "synchronized " );
+ }
+ if( isNative( access_flags ) )
+ {
+ sb.append( "native " );
+ }
+ if( isAbstract( access_flags ) )
+ {
+ sb.append( "abstract " );
+ }
+ return sb.toString().trim();
+ }
+
+ /**
+ * parse all parameters from a descritor into fields of java name.
+ *
+ * @param descriptor of a method.
+ * @return the parameter list of a given method descriptor. Each string
+ * represent a java object with its fully qualified classname or the
+ * primitive name such as int, long, ...
+ */
+ public static String[] getMethodParams( String descriptor )
+ {
+ int i = 0;
+ if( descriptor.charAt( i ) != '(' )
+ {
+ throw new IllegalArgumentException( "Method descriptor should start with a '('" );
+ }
+ Vector params = new Vector();
+ StringBuffer param = new StringBuffer();
+ i++;
+ while( ( i = descriptor2java( descriptor, i, param ) ) < descriptor.length() )
+ {
+ params.add( param.toString() );
+ param.setLength( 0 );// reset
+ if( descriptor.charAt( i ) == ')' )
+ {
+ i++;
+ break;
+ }
+ }
+ String[] array = new String[params.size()];
+ params.copyInto( array );
+ return array;
+ }
+
+ /**
+ * return the object type of a return type.
+ *
+ * @param descriptor
+ * @return get the return type objet of a given descriptor
+ */
+ public static String getMethodReturnType( String descriptor )
+ {
+ int pos = descriptor.indexOf( ')' );
+ StringBuffer rettype = new StringBuffer();
+ descriptor2java( descriptor, pos + 1, rettype );
+ return rettype.toString();
+ }
+
+ /**
+ * return an UTF8 value from the pool located a a specific index.
+ *
+ * @param pool the constant pool to look at
+ * @param index index of the UTF8 value in the constant pool
+ * @return the value of the string if it exists
+ * @throws ClassCastException if the index is not an UTF8 constant.
+ */
+ public static String getUTF8Value( ConstantPool pool, int index )
+ {
+ return ( ( Utf8CPInfo )pool.getEntry( index ) ).getValue();
+ }
+
+ /**
+ * check for abstract access
+ *
+ * @param access_flags access flags
+ * @return The Abstract value
+ */
+ public static boolean isAbstract( int access_flags )
+ {
+ return ( access_flags & ACC_ABSTRACT ) != 0;
+ }
+
+ /**
+ * check for class access
+ *
+ * @param access_flags access flags
+ * @return The Class value
+ */
+ public static boolean isClass( int access_flags )
+ {
+ return !isInterface( access_flags );
+ }
+
+ /**
+ * chck for final flag
+ *
+ * @param access_flags access flags
+ * @return The Final value
+ */
+ public static boolean isFinal( int access_flags )
+ {
+ return ( access_flags & ACC_FINAL ) != 0;
+ }
+
+ /**
+ * check for interface access
+ *
+ * @param access_flags access flags
+ * @return The Interface value
+ */
+ public static boolean isInterface( int access_flags )
+ {
+ return ( access_flags & ACC_INTERFACE ) != 0;
+ }
+
+ /**
+ * check for native access
+ *
+ * @param access_flags access flags
+ * @return The Native value
+ */
+ public static boolean isNative( int access_flags )
+ {
+ return ( access_flags & ACC_NATIVE ) != 0;
+ }
+
+ /**
+ * check for private access
+ *
+ * @param access_flags access flags
+ * @return The Private value
+ */
+ public static boolean isPrivate( int access_flags )
+ {
+ return ( access_flags & ACC_PRIVATE ) != 0;
+ }
+
+ /**
+ * check for protected flag
+ *
+ * @param access_flags access flags
+ * @return The Protected value
+ */
+ public static boolean isProtected( int access_flags )
+ {
+ return ( access_flags & ACC_PROTECTED ) != 0;
+ }
+
+ /**
+ * check for public access
+ *
+ * @param access_flags access flags
+ * @return The Public value
+ */
+ public static boolean isPublic( int access_flags )
+ {
+ return ( access_flags & ACC_PUBLIC ) != 0;
+ }
+
+ /**
+ * check for a static access
+ *
+ * @param access_flags access flags
+ * @return The Static value
+ */
+ public static boolean isStatic( int access_flags )
+ {
+ return ( access_flags & ACC_STATIC ) != 0;
+ }
+
+ /**
+ * check for strict access
+ *
+ * @param access_flags access flags
+ * @return The Strict value
+ */
+ public static boolean isStrict( int access_flags )
+ {
+ return ( access_flags & ACC_STRICT ) != 0;
+ }
+
+ /**
+ * check for super flag
+ *
+ * @param access_flags access flag
+ * @return The Super value
+ */
+ public static boolean isSuper( int access_flags )
+ {
+ return ( access_flags & ACC_SUPER ) != 0;
+ }
+
+ /**
+ * check for synchronized flag
+ *
+ * @param access_flags access flags
+ * @return The Synchronized value
+ */
+ public static boolean isSynchronized( int access_flags )
+ {
+ return ( access_flags & ACC_SYNCHRONIZED ) != 0;
+ }
+
+ /**
+ * check for transient flag
+ *
+ * @param access_flags access flags
+ * @return The Transient value
+ */
+ public static boolean isTransient( int access_flags )
+ {
+ return ( access_flags & ACC_TRANSIENT ) != 0;
+ }
+
+ /**
+ * check for volatile flag
+ *
+ * @param access_flags access flags
+ * @return The Volatile value
+ */
+ public static boolean isVolatile( int access_flags )
+ {
+ return ( access_flags & ACC_VOLATILE ) != 0;
+ }
+
+ /**
+ * Parse a single descriptor symbol and returns it java equivalent.
+ *
+ * @param descriptor the descriptor symbol.
+ * @param i the index to look at the symbol in the descriptor string
+ * @param sb the stringbuffer to return the java equivalent of the symbol
+ * @return the index after the descriptor symbol
+ */
+ public static int descriptor2java( String descriptor, int i, StringBuffer sb )
+ {
+ // get the dimension
+ StringBuffer dim = new StringBuffer();
+ for( ; descriptor.charAt( i ) == '['; i++ )
+ {
+ dim.append( "[]" );
+ }
+ // now get the type
+ switch ( descriptor.charAt( i ) )
+ {
+ case 'B':
+ sb.append( "byte" );
+ break;
+ case 'C':
+ sb.append( "char" );
+ break;
+ case 'D':
+ sb.append( "double" );
+ break;
+ case 'F':
+ sb.append( "float" );
+ break;
+ case 'I':
+ sb.append( "int" );
+ break;
+ case 'J':
+ sb.append( "long" );
+ break;
+ case 'S':
+ sb.append( "short" );
+ break;
+ case 'Z':
+ sb.append( "boolean" );
+ break;
+ case 'V':
+ sb.append( "void" );
+ break;
+ case 'L':
+ // it is a class
+ int pos = descriptor.indexOf( ';', i + 1 );
+ String classname = descriptor.substring( i + 1, pos ).replace( '/', '.' );
+ sb.append( classname );
+ i = pos;
+ break;
+ default:
+ //@todo, yeah this happens because I got things like:
+ // ()Ljava/lang/Object; and it will return and ) will be here
+ // think about it.
+
+ //ooooops should never happen
+ //throw new IllegalArgumentException("Invalid descriptor symbol: '" + i + "' in '" + descriptor + "'");
+ }
+ sb.append( dim.toString() );
+ return ++i;
+ }
+}
+
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/attributes/AttributeInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/attributes/AttributeInfo.java
new file mode 100644
index 000000000..3631b9748
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/attributes/AttributeInfo.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.taskdefs.optional.sitraka.bytecode.attributes;
+
+/**
+ * Attribute info structure that provides base methods
+ *
+ * @author Stephane Bailliez
+ */
+public interface AttributeInfo
+{
+
+ public final static String SOURCE_FILE = "SourceFile";
+
+ public final static String CONSTANT_VALUE = "ConstantValue";
+
+ public final static String CODE = "Code";
+
+ public final static String EXCEPTIONS = "Exceptions";
+
+ public final static String LINE_NUMBER_TABLE = "LineNumberTable";
+
+ public final static String LOCAL_VARIABLE_TABLE = "LocalVariableTable";
+
+ public final static String INNER_CLASSES = "InnerClasses";
+
+ public final static String SOURCE_DIR = "SourceDir";
+
+ public final static String SYNTHETIC = "Synthetic";
+
+ public final static String DEPRECATED = "Deprecated";
+
+ public final static String UNKNOWN = "Unknown";
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sound/AntSoundPlayer.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sound/AntSoundPlayer.java
new file mode 100644
index 000000000..64e1b8e88
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sound/AntSoundPlayer.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sound;
+import java.io.File;
+import java.io.IOException;
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Clip;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.Line;
+import javax.sound.sampled.LineEvent;// imports for all the sound classes required
+// note: comes with jmf or jdk1.3 +
+// these can be obtained from http://java.sun.com/products/java-media/sound/
+import javax.sound.sampled.LineListener;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.UnsupportedAudioFileException;
+import org.apache.tools.ant.BuildEvent;// ant includes
+import org.apache.tools.ant.BuildListener;
+import org.apache.tools.ant.Project;
+
+
+/**
+ * This class is designed to be used by any AntTask that requires audio output.
+ * It implements the BuildListener interface to listen for BuildEvents and could
+ * be easily extended to provide audio output upon any specific build events
+ * occuring. I have only tested this with .WAV and .AIFF sound file formats.
+ * Both seem to work fine.
+ *
+ * @author Nick Pellow
+ * @version $Revision$, $Date$
+ */
+
+public class AntSoundPlayer implements LineListener, BuildListener
+{
+
+ private File fileSuccess = null;
+ private int loopsSuccess = 0;
+ private Long durationSuccess = null;
+
+ private File fileFail = null;
+ private int loopsFail = 0;
+ private Long durationFail = null;
+
+ public AntSoundPlayer() { }
+
+
+ /**
+ * @param fileFail The feature to be added to the BuildFailedSound attribute
+ * @param loopsFail The feature to be added to the BuildFailedSound
+ * attribute
+ * @param durationFail The feature to be added to the BuildFailedSound
+ * attribute
+ */
+ public void addBuildFailedSound( File fileFail, int loopsFail, Long durationFail )
+ {
+ this.fileFail = fileFail;
+ this.loopsFail = loopsFail;
+ this.durationFail = durationFail;
+ }
+
+ /**
+ * @param loops the number of times the file should be played when the build
+ * is successful
+ * @param duration the number of milliseconds the file should be played when
+ * the build is successful
+ * @param file The feature to be added to the BuildSuccessfulSound attribute
+ */
+ public void addBuildSuccessfulSound( File file, int loops, Long duration )
+ {
+ this.fileSuccess = file;
+ this.loopsSuccess = loops;
+ this.durationSuccess = duration;
+ }
+
+ /**
+ * 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 )
+ {
+ if( event.getException() == null && fileSuccess != null )
+ {
+ // build successfull!
+ play( event.getProject(), fileSuccess, loopsSuccess, durationSuccess );
+ }
+ else if( event.getException() != null && fileFail != null )
+ {
+ play( event.getProject(), fileFail, loopsFail, durationFail );
+ }
+ }
+
+
+ /**
+ * Fired before any targets are started.
+ *
+ * @param event Description of Parameter
+ */
+ public void buildStarted( BuildEvent event ) { }
+
+ /**
+ * Fired whenever a message is logged.
+ *
+ * @param event Description of Parameter
+ * @see BuildEvent#getMessage()
+ * @see BuildEvent#getPriority()
+ */
+ public void messageLogged( 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()
+ */
+ public void targetFinished( BuildEvent event ) { }
+
+ /**
+ * Fired when a target is started.
+ *
+ * @param event Description of Parameter
+ * @see BuildEvent#getTarget()
+ */
+ public void targetStarted( 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()
+ */
+ public void taskFinished( BuildEvent event ) { }
+
+ /**
+ * Fired when a task is started.
+ *
+ * @param event Description of Parameter
+ * @see BuildEvent#getTask()
+ */
+ public void taskStarted( BuildEvent event ) { }
+
+ /**
+ * This is implemented to listen for any line events and closes the clip if
+ * required.
+ *
+ * @param event Description of Parameter
+ */
+ public void update( LineEvent event )
+ {
+ if( event.getType().equals( LineEvent.Type.STOP ) )
+ {
+ Line line = event.getLine();
+ line.close();
+ }
+ else if( event.getType().equals( LineEvent.Type.CLOSE ) )
+ {
+ /*
+ * There is a bug in JavaSound 0.90 (jdk1.3beta).
+ * It prevents correct termination of the VM.
+ * So we have to exit ourselves.
+ */
+ //System.exit(0);
+ }
+ }
+
+ /**
+ * Plays the file for duration milliseconds or loops.
+ *
+ * @param project Description of Parameter
+ * @param file Description of Parameter
+ * @param loops Description of Parameter
+ * @param duration Description of Parameter
+ */
+ private void play( Project project, File file, int loops, Long duration )
+ {
+
+ Clip audioClip = null;
+
+ AudioInputStream audioInputStream = null;
+
+ try
+ {
+ audioInputStream = AudioSystem.getAudioInputStream( file );
+ }
+ catch( UnsupportedAudioFileException uafe )
+ {
+ project.log( "Audio format is not yet supported: " + uafe.getMessage() );
+ }
+ catch( IOException ioe )
+ {
+ ioe.printStackTrace();
+ }
+
+ if( audioInputStream != null )
+ {
+ AudioFormat format = audioInputStream.getFormat();
+ DataLine.Info info = new DataLine.Info( Clip.class, format,
+ AudioSystem.NOT_SPECIFIED );
+ try
+ {
+ audioClip = ( Clip )AudioSystem.getLine( info );
+ audioClip.addLineListener( this );
+ audioClip.open( audioInputStream );
+ }
+ catch( LineUnavailableException e )
+ {
+ project.log( "The sound device is currently unavailable" );
+ return;
+ }
+ catch( IOException e )
+ {
+ e.printStackTrace();
+ }
+
+ if( duration != null )
+ {
+ playClip( audioClip, duration.longValue() );
+ }
+ else
+ {
+ playClip( audioClip, loops );
+ }
+ audioClip.drain();
+ audioClip.close();
+ }
+ else
+ {
+ project.log( "Can't get data from file " + file.getName() );
+ }
+ }
+
+ private void playClip( Clip clip, int loops )
+ {
+
+ clip.loop( loops );
+ while( clip.isRunning() )
+ {
+ }
+ }
+
+ private void playClip( Clip clip, long duration )
+ {
+
+ long currentTime = System.currentTimeMillis();
+ clip.loop( Clip.LOOP_CONTINUOUSLY );
+ try
+ {
+ Thread.sleep( duration );
+ }
+ catch( InterruptedException e )
+ {
+ }
+ }
+}
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sound/SoundTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sound/SoundTask.java
new file mode 100644
index 000000000..8854c4c0d
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sound/SoundTask.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.sound;
+import java.io.File;
+import java.util.Random;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+
+/**
+ * This is an example of an AntTask that makes of use of the AntSoundPlayer.
+ * There are three attributes to be set: source: the location of
+ * the audio file to be played duration: play the sound file
+ * continuously until "duration" milliseconds has expired loops:
+ * the number of times the sound file should be played until stopped I have only
+ * tested this with .WAV and .AIFF sound file formats. Both seem to work fine.
+ * plans for the future: - use the midi api to define sounds (or drum beat etc)
+ * in xml and have Ant play them back
+ *
+ * @author Nick Pellow
+ * @version $Revision$, $Date$
+ */
+
+public class SoundTask extends Task
+{
+
+ private BuildAlert success = null;
+ private BuildAlert fail = null;
+
+ public SoundTask() { }
+
+ public BuildAlert createFail()
+ {
+ fail = new BuildAlert();
+ return fail;
+ }
+
+ public BuildAlert createSuccess()
+ {
+ success = new BuildAlert();
+ return success;
+ }
+
+ public void execute()
+ {
+
+ AntSoundPlayer soundPlayer = new AntSoundPlayer();
+
+ if( success == null )
+ {
+ log( "No nested success element found.", Project.MSG_WARN );
+ }
+ else
+ {
+ soundPlayer.addBuildSuccessfulSound( success.getSource(),
+ success.getLoops(), success.getDuration() );
+ }
+
+ if( fail == null )
+ {
+ log( "No nested failure element found.", Project.MSG_WARN );
+ }
+ else
+ {
+ soundPlayer.addBuildFailedSound( fail.getSource(),
+ fail.getLoops(), fail.getDuration() );
+ }
+
+ getProject().addBuildListener( soundPlayer );
+
+ }
+
+ public void init() { }
+
+ /**
+ * A class to be extended by any BuildAlert's that require the output of
+ * sound.
+ *
+ * @author RT
+ */
+ public class BuildAlert
+ {
+ private File source = null;
+ private int loops = 0;
+ private Long duration = null;
+
+ /**
+ * Sets the duration in milliseconds the file should be played.
+ *
+ * @param duration The new Duration value
+ */
+ public void setDuration( Long duration )
+ {
+ this.duration = duration;
+ }
+
+ /**
+ * Sets the number of times the source file should be played.
+ *
+ * @param loops the number of loops to play the source file
+ */
+ public void setLoops( int loops )
+ {
+ this.loops = loops;
+ }
+
+ /**
+ * Sets the location of the file to get the audio.
+ *
+ * @param source the name of a sound-file directory or of the audio file
+ */
+ public void setSource( File source )
+ {
+ this.source = source;
+ }
+
+ /**
+ * Gets the duration in milliseconds the file should be played.
+ *
+ * @return The Duration value
+ */
+ public Long getDuration()
+ {
+ return this.duration;
+ }
+
+ /**
+ * Sets the number of times the source file should be played.
+ *
+ * @return the number of loops to play the source file
+ */
+ public int getLoops()
+ {
+ return this.loops;
+ }
+
+ /**
+ * Gets the location of the file to get the audio.
+ *
+ * @return The Source value
+ */
+ public File getSource()
+ {
+ File nofile = null;
+ // Check if source is a directory
+ if( source.exists() )
+ {
+ if( source.isDirectory() )
+ {
+ // get the list of files in the dir
+ String[] entries = source.list();
+ Vector files = new Vector();
+ for( int i = 0; i < entries.length; i++ )
+ {
+ File f = new File( source, entries[i] );
+ if( f.isFile() )
+ {
+ files.addElement( f );
+ }
+ }
+ if( files.size() < 1 )
+ {
+ throw new BuildException( "No files found in directory " + source );
+ }
+ int numfiles = files.size();
+ // get a random number between 0 and the number of files
+ Random rn = new Random();
+ int x = rn.nextInt( numfiles );
+ // set the source to the file at that location
+ this.source = ( File )files.elementAt( x );
+ }
+ }
+ else
+ {
+ log( source + ": invalid path.", Project.MSG_WARN );
+ this.source = nofile;
+ }
+ return this.source;
+ }
+ }
+}
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSS.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSS.java
new file mode 100644
index 000000000..2bd290da5
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSS.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.optional.vss;
+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 java.io.IOException;
+
+/**
+ * A base class for creating tasks for executing commands on Visual SourceSafe.
+ *
+ *
+ * The class extends the 'exec' task as it operates by executing the ss.exe
+ * program supplied with SourceSafe. By default the task expects ss.exe to be in
+ * the path, you can override this be specifying the ssdir attribute.
+ *
+ * This class provides set and get methods for 'login' and 'vsspath' attributes.
+ * It also contains constants for the flags that can be passed to SS.
+ *
+ * @author Craig Cottingham
+ * @author Andrew Everitt
+ */
+public abstract class MSVSS extends Task
+{
+
+ /**
+ * Constant for the thing to execute
+ */
+ private final static String SS_EXE = "ss";
+ /**
+ */
+ public final static String PROJECT_PREFIX = "$";
+
+ /**
+ * The 'Get' command
+ */
+ public final static String COMMAND_GET = "Get";
+ /**
+ * The 'Checkout' command
+ */
+ public final static String COMMAND_CHECKOUT = "Checkout";
+ /**
+ * The 'Checkin' command
+ */
+ public final static String COMMAND_CHECKIN = "Checkin";
+ /**
+ * The 'Label' command
+ */
+ public final static String COMMAND_LABEL = "Label";
+ /**
+ * The 'History' command
+ */
+ public final static String COMMAND_HISTORY = "History";
+
+ /**
+ */
+ public final static String FLAG_LOGIN = "-Y";
+ /**
+ */
+ public final static String FLAG_OVERRIDE_WORKING_DIR = "-GL";
+ /**
+ */
+ public final static String FLAG_AUTORESPONSE_DEF = "-I-";
+ /**
+ */
+ public final static String FLAG_AUTORESPONSE_YES = "-I-Y";
+ /**
+ */
+ public final static String FLAG_AUTORESPONSE_NO = "-I-N";
+ /**
+ */
+ public final static String FLAG_RECURSION = "-R";
+ /**
+ */
+ public final static String FLAG_VERSION = "-V";
+ /**
+ */
+ public final static String FLAG_VERSION_DATE = "-Vd";
+ /**
+ */
+ public final static String FLAG_VERSION_LABEL = "-VL";
+ /**
+ */
+ public final static String FLAG_WRITABLE = "-W";
+ /**
+ */
+ public final static String VALUE_NO = "-N";
+ /**
+ */
+ public final static String VALUE_YES = "-Y";
+ /**
+ */
+ public final static String FLAG_QUIET = "-O-";
+
+ private String m_SSDir = "";
+ private String m_vssLogin = null;
+ private String m_vssPath = null;
+ private String m_serverPath = null;
+
+ /**
+ * Set the login to use when accessing vss.
+ *
+ * Should be formatted as username,password
+ *
+ * @param login the login string to use
+ */
+ public final void setLogin( String login )
+ {
+ m_vssLogin = login;
+ }
+
+ /**
+ * Set the path to the location of the ss.ini
+ *
+ * @param serverPath
+ */
+ public final void setServerpath( String serverPath )
+ {
+ m_serverPath = serverPath;
+ }
+
+ /**
+ * Set the directory where ss.exe is located
+ *
+ * @param dir the directory containing ss.exe
+ */
+ public final void setSsdir( String dir )
+ {
+ m_SSDir = project.translatePath( dir );
+ }
+
+ /**
+ * Set the path to the item in vss to operate on
+ *
+ * Ant can't cope with a '$' sign in an attribute so we have to add it here.
+ * Also we strip off any 'vss://' prefix which is an XMS special and should
+ * probably be removed!
+ *
+ * @param vssPath
+ */
+ public final void setVsspath( String vssPath )
+ {
+ if( vssPath.startsWith( "vss://" ) )
+ {
+ m_vssPath = PROJECT_PREFIX + vssPath.substring( 5 );
+ }
+ else
+ {
+ m_vssPath = PROJECT_PREFIX + vssPath;
+ }
+ }
+
+ /**
+ * Builds and returns the command string to execute ss.exe
+ *
+ * @return The SSCommand value
+ */
+ public final String getSSCommand()
+ {
+ String toReturn = m_SSDir;
+ if( !toReturn.equals( "" ) && !toReturn.endsWith( "\\" ) )
+ {
+ toReturn += "\\";
+ }
+ toReturn += SS_EXE;
+
+ return toReturn;
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getLoginCommand( Commandline cmd )
+ {
+ if( m_vssLogin == null )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_LOGIN + m_vssLogin );
+ }
+ }
+
+ /**
+ * @return m_vssPath
+ */
+ public String getVsspath()
+ {
+ return m_vssPath;
+ }
+
+ protected int run( Commandline cmd )
+ {
+ try
+ {
+ Execute exe = new Execute( new LogStreamHandler( this,
+ Project.MSG_INFO,
+ Project.MSG_WARN ) );
+
+ // If location of ss.ini is specified we need to set the
+ // environment-variable SSDIR to this value
+ if( m_serverPath != null )
+ {
+ String[] env = exe.getEnvironment();
+ if( env == null )
+ {
+ env = new String[0];
+ }
+ String[] newEnv = new String[env.length + 1];
+ for( int i = 0; i < env.length; i++ )
+ {
+ newEnv[i] = env[i];
+ }
+ newEnv[env.length] = "SSDIR=" + m_serverPath;
+
+ exe.setEnvironment( newEnv );
+ }
+
+ exe.setAntRun( project );
+ exe.setWorkingDirectory( project.getBaseDir() );
+ exe.setCommandline( cmd.getCommandline() );
+ return exe.execute();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( e );
+ }
+ }
+}
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKIN.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKIN.java
new file mode 100644
index 000000000..8e936b6d4
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKIN.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.vss;
+import java.io.File;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Task to perform CheckIn commands to Microsoft Visual Source Safe.
+ *
+ * @author Martin Poeschl
+ */
+public class MSVSSCHECKIN extends MSVSS
+{
+
+ private String m_LocalPath = null;
+ private boolean m_Recursive = false;
+ private boolean m_Writable = false;
+ private String m_AutoResponse = null;
+ private String m_Comment = "-";
+
+ /**
+ * Set behaviour, used in get command to make files that are 'got' writable
+ *
+ * @param argWritable The new Writable value
+ */
+ public final void setWritable( boolean argWritable )
+ {
+ m_Writable = argWritable;
+ }
+
+ public void setAutoresponse( String response )
+ {
+ if( response.equals( "" ) || response.equals( "null" ) )
+ {
+ m_AutoResponse = null;
+ }
+ else
+ {
+ m_AutoResponse = response;
+ }
+ }
+
+ /**
+ * Set the comment to apply in SourceSafe
+ *
+ * If this is null or empty, it will be replaced with "-" which is what
+ * SourceSafe uses for an empty comment.
+ *
+ * @param comment The new Comment value
+ */
+ public void setComment( String comment )
+ {
+ if( comment.equals( "" ) || comment.equals( "null" ) )
+ {
+ m_Comment = "-";
+ }
+ else
+ {
+ m_Comment = comment;
+ }
+ }
+
+ /**
+ * Set the local path.
+ *
+ * @param localPath The new Localpath value
+ */
+ public void setLocalpath( Path localPath )
+ {
+ m_LocalPath = localPath.toString();
+ }
+
+ /**
+ * Set behaviour recursive or non-recursive
+ *
+ * @param recursive The new Recursive value
+ */
+ public void setRecursive( boolean recursive )
+ {
+ m_Recursive = recursive;
+ }
+
+ /**
+ * Checks the value set for the autoResponse. if it equals "Y" then we
+ * return -I-Y if it equals "N" then we return -I-N otherwise we return -I
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getAutoresponse( Commandline cmd )
+ {
+
+ if( m_AutoResponse == null )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "Y" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_YES );
+
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "N" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_NO );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }// end of else
+
+ }
+
+ /**
+ * Gets the comment to be applied.
+ *
+ * @return the comment to be applied.
+ */
+ public String getComment()
+ {
+ return m_Comment;
+ }
+
+ /**
+ * Builds and returns the -GL flag command if required
+ *
+ * The localpath is created if it didn't exist
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getLocalpathCommand( Commandline cmd )
+ {
+ if( m_LocalPath == null )
+ {
+ return;
+ }
+ else
+ {
+ // make sure m_LocalDir exists, create it if it doesn't
+ File dir = project.resolveFile( m_LocalPath );
+ if( !dir.exists() )
+ {
+ boolean done = dir.mkdirs();
+ if( done == false )
+ {
+ String msg = "Directory " + m_LocalPath + " creation was not " +
+ "succesful for an unknown reason";
+ throw new BuildException( msg, location );
+ }
+ project.log( "Created dir: " + dir.getAbsolutePath() );
+ }
+
+ cmd.createArgument().setValue( FLAG_OVERRIDE_WORKING_DIR + m_LocalPath );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getRecursiveCommand( Commandline cmd )
+ {
+ if( !m_Recursive )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_RECURSION );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getWritableCommand( Commandline cmd )
+ {
+ if( !m_Writable )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_WRITABLE );
+ }
+ }
+
+ /**
+ * Executes the task.
+ *
+ * Builds a command line to execute ss 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();
+ int result = 0;
+
+ // first off, make sure that we've got a command and a vssdir ...
+ if( getVsspath() == null )
+ {
+ String msg = "vsspath attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+
+ // now look for illegal combinations of things ...
+
+ // build the command line from what we got the format is
+ // ss Checkin VSS items [-H] [-C] [-I-] [-N] [-O] [-R] [-W] [-Y] [-?]
+ // as specified in the SS.EXE help
+ commandLine.setExecutable( getSSCommand() );
+ commandLine.createArgument().setValue( COMMAND_CHECKIN );
+
+ // VSS items
+ commandLine.createArgument().setValue( getVsspath() );
+ // -GL
+ getLocalpathCommand( commandLine );
+ // -I- or -I-Y or -I-N
+ getAutoresponse( commandLine );
+ // -R
+ getRecursiveCommand( commandLine );
+ // -W
+ getWritableCommand( commandLine );
+ // -Y
+ getLoginCommand( commandLine );
+ // -C
+ commandLine.createArgument().setValue( "-C" + getComment() );
+
+ result = run( commandLine );
+ if( result != 0 )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKOUT.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKOUT.java
new file mode 100644
index 000000000..356bf1565
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKOUT.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.vss;
+import java.io.File;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Task to perform CheckOut commands to Microsoft Visual Source Safe.
+ *
+ * @author Martin Poeschl
+ */
+public class MSVSSCHECKOUT extends MSVSS
+{
+
+ private String m_LocalPath = null;
+ private boolean m_Recursive = false;
+ private String m_Version = null;
+ private String m_Date = null;
+ private String m_Label = null;
+ private String m_AutoResponse = null;
+
+ public void setAutoresponse( String response )
+ {
+ if( response.equals( "" ) || response.equals( "null" ) )
+ {
+ m_AutoResponse = null;
+ }
+ else
+ {
+ m_AutoResponse = response;
+ }
+ }
+
+ /**
+ * Set the stored date string
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g. date="${date}" when
+ * date has not been defined to ant!
+ *
+ * @param date The new Date value
+ */
+ public void setDate( String date )
+ {
+ if( date.equals( "" ) || date.equals( "null" ) )
+ {
+ m_Date = null;
+ }
+ else
+ {
+ m_Date = date;
+ }
+ }
+
+ /**
+ * Set the labeled version to operate on in SourceSafe
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * label="${label_server}" when label_server has not been defined to ant!
+ *
+ * @param label The new Label value
+ */
+ public void setLabel( String label )
+ {
+ if( label.equals( "" ) || label.equals( "null" ) )
+ {
+ m_Label = null;
+ }
+ else
+ {
+ m_Label = label;
+ }
+ }
+
+ /**
+ * Set the local path.
+ *
+ * @param localPath The new Localpath value
+ */
+ public void setLocalpath( Path localPath )
+ {
+ m_LocalPath = localPath.toString();
+ }
+
+ /**
+ * Set behaviour recursive or non-recursive
+ *
+ * @param recursive The new Recursive value
+ */
+ public void setRecursive( boolean recursive )
+ {
+ m_Recursive = recursive;
+ }
+
+ /**
+ * Set the stored version string
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * version="${ver_server}" when ver_server has not been defined to ant!
+ *
+ * @param version The new Version value
+ */
+ public void setVersion( String version )
+ {
+ if( version.equals( "" ) || version.equals( "null" ) )
+ {
+ m_Version = null;
+ }
+ else
+ {
+ m_Version = version;
+ }
+ }
+
+ /**
+ * Checks the value set for the autoResponse. if it equals "Y" then we
+ * return -I-Y if it equals "N" then we return -I-N otherwise we return -I
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getAutoresponse( Commandline cmd )
+ {
+
+ if( m_AutoResponse == null )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "Y" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_YES );
+
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "N" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_NO );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }// end of else
+
+ }
+
+ /**
+ * Builds and returns the -GL flag command if required
+ *
+ * The localpath is created if it didn't exist
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getLocalpathCommand( Commandline cmd )
+ {
+ if( m_LocalPath == null )
+ {
+ return;
+ }
+ else
+ {
+ // make sure m_LocalDir exists, create it if it doesn't
+ File dir = project.resolveFile( m_LocalPath );
+ if( !dir.exists() )
+ {
+ boolean done = dir.mkdirs();
+ if( done == false )
+ {
+ String msg = "Directory " + m_LocalPath + " creation was not " +
+ "succesful for an unknown reason";
+ throw new BuildException( msg, location );
+ }
+ project.log( "Created dir: " + dir.getAbsolutePath() );
+ }
+
+ cmd.createArgument().setValue( FLAG_OVERRIDE_WORKING_DIR + m_LocalPath );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getRecursiveCommand( Commandline cmd )
+ {
+ if( !m_Recursive )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_RECURSION );
+ }
+ }
+
+ /**
+ * Simple order of priority. Returns the first specified of version, date,
+ * label If none of these was specified returns ""
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getVersionCommand( Commandline cmd )
+ {
+
+ if( m_Version != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION + m_Version );
+ }
+ else if( m_Date != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + m_Date );
+ }
+ else if( m_Label != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_LABEL + m_Label );
+ }
+ }
+
+ /**
+ * Executes the task.
+ *
+ * Builds a command line to execute ss 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();
+ int result = 0;
+
+ // first off, make sure that we've got a command and a vssdir ...
+ if( getVsspath() == null )
+ {
+ String msg = "vsspath attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+
+ // now look for illegal combinations of things ...
+
+ // build the command line from what we got the format is
+ // ss Checkout VSS items [-G] [-C] [-H] [-I-] [-N] [-O] [-R] [-V] [-Y] [-?]
+ // as specified in the SS.EXE help
+ commandLine.setExecutable( getSSCommand() );
+ commandLine.createArgument().setValue( COMMAND_CHECKOUT );
+
+ // VSS items
+ commandLine.createArgument().setValue( getVsspath() );
+ // -GL
+ getLocalpathCommand( commandLine );
+ // -I- or -I-Y or -I-N
+ getAutoresponse( commandLine );
+ // -R
+ getRecursiveCommand( commandLine );
+ // -V
+ getVersionCommand( commandLine );
+ // -Y
+ getLoginCommand( commandLine );
+
+ result = run( commandLine );
+ if( result != 0 )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+ }
+
+}
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSGET.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSGET.java
new file mode 100644
index 000000000..17bcc222c
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSGET.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.vss;
+import java.io.File;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Task to perform GET commands to Microsoft Visual Source Safe.
+ *
+ * The following attributes are interpretted:
+ *
+ *
+ *
+ *
+ *
+ * Attribute
+ *
+ *
+ *
+ * Values
+ *
+ *
+ *
+ * Required
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * login
+ *
+ *
+ *
+ * username,password
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * vsspath
+ *
+ *
+ *
+ * SourceSafe path
+ *
+ *
+ *
+ * Yes
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * localpath
+ *
+ *
+ *
+ * Override the working directory and get to the specified path
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * writable
+ *
+ *
+ *
+ * true or false
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * recursive
+ *
+ *
+ *
+ * true or false
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * version
+ *
+ *
+ *
+ * a version number to get
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * date
+ *
+ *
+ *
+ * a date stamp to get at
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * label
+ *
+ *
+ *
+ * a label to get for
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * quiet
+ *
+ *
+ *
+ * suppress output (off by default)
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * autoresponse
+ *
+ *
+ *
+ * What to respond with (sets the -I option). By default, -I- is used;
+ * values of Y or N will be appended to this.
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Note that only one of version, date or label should be specified
+ *
+ * @author Craig Cottingham
+ * @author Andrew Everitt
+ */
+public class MSVSSGET extends MSVSS
+{
+
+ private String m_LocalPath = null;
+ private boolean m_Recursive = false;
+ private boolean m_Writable = false;
+ private String m_Version = null;
+ private String m_Date = null;
+ private String m_Label = null;
+ private String m_AutoResponse = null;
+ private boolean m_Quiet = false;
+
+ /**
+ * Sets/clears quiet mode
+ *
+ * @param quiet The new Quiet value
+ */
+ public final void setQuiet( boolean quiet )
+ {
+ this.m_Quiet = quiet;
+ }
+
+ /**
+ * Set behaviour, used in get command to make files that are 'got' writable
+ *
+ * @param argWritable The new Writable value
+ */
+ public final void setWritable( boolean argWritable )
+ {
+ m_Writable = argWritable;
+ }
+
+ public void setAutoresponse( String response )
+ {
+ if( response.equals( "" ) || response.equals( "null" ) )
+ {
+ m_AutoResponse = null;
+ }
+ else
+ {
+ m_AutoResponse = response;
+ }
+ }
+
+ /**
+ * Set the stored date string
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g. date="${date}" when
+ * date has not been defined to ant!
+ *
+ * @param date The new Date value
+ */
+ public void setDate( String date )
+ {
+ if( date.equals( "" ) || date.equals( "null" ) )
+ {
+ m_Date = null;
+ }
+ else
+ {
+ m_Date = date;
+ }
+ }
+
+ /**
+ * Set the labeled version to operate on in SourceSafe
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * label="${label_server}" when label_server has not been defined to ant!
+ *
+ * @param label The new Label value
+ */
+ public void setLabel( String label )
+ {
+ if( label.equals( "" ) || label.equals( "null" ) )
+ {
+ m_Label = null;
+ }
+ else
+ {
+ m_Label = label;
+ }
+ }
+
+ /**
+ * Set the local path.
+ *
+ * @param localPath The new Localpath value
+ */
+ public void setLocalpath( Path localPath )
+ {
+ m_LocalPath = localPath.toString();
+ }
+
+ /**
+ * Set behaviour recursive or non-recursive
+ *
+ * @param recursive The new Recursive value
+ */
+ public void setRecursive( boolean recursive )
+ {
+ m_Recursive = recursive;
+ }
+
+ /**
+ * Set the stored version string
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * version="${ver_server}" when ver_server has not been defined to ant!
+ *
+ * @param version The new Version value
+ */
+ public void setVersion( String version )
+ {
+ if( version.equals( "" ) || version.equals( "null" ) )
+ {
+ m_Version = null;
+ }
+ else
+ {
+ m_Version = version;
+ }
+ }
+
+ /**
+ * Checks the value set for the autoResponse. if it equals "Y" then we
+ * return -I-Y if it equals "N" then we return -I-N otherwise we return -I
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getAutoresponse( Commandline cmd )
+ {
+
+ if( m_AutoResponse == null )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "Y" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_YES );
+
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "N" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_NO );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }// end of else
+
+ }
+
+ /**
+ * Builds and returns the -GL flag command if required
+ *
+ * The localpath is created if it didn't exist
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getLocalpathCommand( Commandline cmd )
+ {
+ if( m_LocalPath == null )
+ {
+ return;
+ }
+ else
+ {
+ // make sure m_LocalDir exists, create it if it doesn't
+ File dir = project.resolveFile( m_LocalPath );
+ if( !dir.exists() )
+ {
+ boolean done = dir.mkdirs();
+ if( done == false )
+ {
+ String msg = "Directory " + m_LocalPath + " creation was not " +
+ "successful for an unknown reason";
+ throw new BuildException( msg, location );
+ }
+ project.log( "Created dir: " + dir.getAbsolutePath() );
+ }
+
+ cmd.createArgument().setValue( FLAG_OVERRIDE_WORKING_DIR + m_LocalPath );
+ }
+ }
+
+ public void getQuietCommand( Commandline cmd )
+ {
+ if( m_Quiet )
+ {
+ cmd.createArgument().setValue( FLAG_QUIET );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getRecursiveCommand( Commandline cmd )
+ {
+ if( !m_Recursive )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_RECURSION );
+ }
+ }
+
+ /**
+ * Simple order of priority. Returns the first specified of version, date,
+ * label If none of these was specified returns ""
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getVersionCommand( Commandline cmd )
+ {
+
+ if( m_Version != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION + m_Version );
+ }
+ else if( m_Date != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + m_Date );
+ }
+ else if( m_Label != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_LABEL + m_Label );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getWritableCommand( Commandline cmd )
+ {
+ if( !m_Writable )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_WRITABLE );
+ }
+ }
+
+ /**
+ * Executes the task.
+ *
+ * Builds a command line to execute ss 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();
+ int result = 0;
+
+ // first off, make sure that we've got a command and a vssdir ...
+ if( getVsspath() == null )
+ {
+ String msg = "vsspath attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+
+ // now look for illegal combinations of things ...
+
+ // build the command line from what we got the format is
+ // ss Get VSS items [-G] [-H] [-I-] [-N] [-O] [-R] [-V] [-W] [-Y] [-?]
+ // as specified in the SS.EXE help
+ commandLine.setExecutable( getSSCommand() );
+ commandLine.createArgument().setValue( COMMAND_GET );
+
+ // VSS items
+ commandLine.createArgument().setValue( getVsspath() );
+ // -GL
+ getLocalpathCommand( commandLine );
+ // -I- or -I-Y or -I-N
+ getAutoresponse( commandLine );
+ // -O-
+ getQuietCommand( commandLine );
+ // -R
+ getRecursiveCommand( commandLine );
+ // -V
+ getVersionCommand( commandLine );
+ // -W
+ getWritableCommand( commandLine );
+ // -Y
+ getLoginCommand( commandLine );
+
+ result = run( commandLine );
+ if( result != 0 )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+ }
+
+}
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSHISTORY.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSHISTORY.java
new file mode 100644
index 000000000..583e2249a
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSHISTORY.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.ant.taskdefs.optional.vss;
+import java.io.File;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+
+/**
+ * Task to perform HISTORY commands to Microsoft Visual Source Safe.
+ *
+ * @author Balazs Fejes 2
+ * @author Glenn_Twiggs@bmc.com
+ */
+
+public class MSVSSHISTORY extends MSVSS
+{
+
+ public final static String VALUE_FROMDATE = "~d";
+ public final static String VALUE_FROMLABEL = "~L";
+
+ public final static String FLAG_OUTPUT = "-O";
+ public final static String FLAG_USER = "-U";
+
+ private String m_FromDate = null;
+ private String m_ToDate = null;
+ private DateFormat m_DateFormat =
+ DateFormat.getDateInstance( DateFormat.SHORT );
+
+ private String m_FromLabel = null;
+ private String m_ToLabel = null;
+ private String m_OutputFileName = null;
+ private String m_User = null;
+ private int m_NumDays = Integer.MIN_VALUE;
+ private String m_Style = "";
+ private boolean m_Recursive = false;
+
+ /**
+ * Set the Start Date for the Comparison of two versions in SourceSafe
+ * History
+ *
+ * @param dateFormat The new DateFormat value
+ */
+ public void setDateFormat( String dateFormat )
+ {
+ if( !( dateFormat.equals( "" ) || dateFormat == null ) )
+ {
+ m_DateFormat = new SimpleDateFormat( dateFormat );
+ }
+ }
+
+ /**
+ * Set the Start Date for the Comparison of two versions in SourceSafe
+ * History
+ *
+ * @param fromDate The new FromDate value
+ */
+ public void setFromDate( String fromDate )
+ {
+ if( fromDate.equals( "" ) || fromDate == null )
+ {
+ m_FromDate = null;
+ }
+ else
+ {
+ m_FromDate = fromDate;
+ }
+ }
+
+ /**
+ * Set the Start Label
+ *
+ * @param fromLabel The new FromLabel value
+ */
+ public void setFromLabel( String fromLabel )
+ {
+ if( fromLabel.equals( "" ) || fromLabel == null )
+ {
+ m_FromLabel = null;
+ }
+ else
+ {
+ m_FromLabel = fromLabel;
+ }
+ }
+
+ /**
+ * Set the number of days to go back for Comparison
+ *
+ * The default value is 2 days.
+ *
+ * @param numd The new Numdays value
+ */
+ public void setNumdays( int numd )
+ {
+ m_NumDays = numd;
+ }
+
+ /**
+ * Set the output file name for SourceSafe output
+ *
+ * @param outfile The new Output value
+ */
+ public void setOutput( File outfile )
+ {
+ if( outfile == null )
+ {
+ m_OutputFileName = null;
+ }
+ else
+ {
+ m_OutputFileName = outfile.getAbsolutePath();
+ }
+ }
+
+ /**
+ * Set behaviour recursive or non-recursive
+ *
+ * @param recursive The new Recursive value
+ */
+ public void setRecursive( boolean recursive )
+ {
+ m_Recursive = recursive;
+ }
+
+ /**
+ * Specify the detail of output
+ *
+ * @param attr The new Style value
+ */
+ public void setStyle( BriefCodediffNofile attr )
+ {
+ String option = attr.getValue();
+ if( option.equals( "brief" ) )
+ {
+ m_Style = "-B";
+ }
+ else if( option.equals( "codediff" ) )
+ {
+ m_Style = "-D";
+ }
+ else if( option.equals( "default" ) )
+ {
+ m_Style = "";
+ }
+ else if( option.equals( "nofile" ) )
+ {
+ m_Style = "-F-";
+ }
+ else
+ {
+ throw new BuildException( "Style " + attr + " unknown." );
+ }
+ }
+
+ /**
+ * Set the End Date for the Comparison of two versions in SourceSafe History
+ *
+ * @param toDate The new ToDate value
+ */
+ public void setToDate( String toDate )
+ {
+ if( toDate.equals( "" ) || toDate == null )
+ {
+ m_ToDate = null;
+ }
+ else
+ {
+ m_ToDate = toDate;
+ }
+ }
+
+ /**
+ * Set the End Label
+ *
+ * @param toLabel The new ToLabel value
+ */
+ public void setToLabel( String toLabel )
+ {
+ if( toLabel.equals( "" ) || toLabel == null )
+ {
+ m_ToLabel = null;
+ }
+ else
+ {
+ m_ToLabel = toLabel;
+ }
+ }
+
+ /**
+ * Set the Username of the user whose changes we would like to see.
+ *
+ * @param user The new User value
+ */
+ public void setUser( String user )
+ {
+ m_User = user;
+ }
+
+ /**
+ * Executes the task.
+ *
+ * Builds a command line to execute ss 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();
+ int result = 0;
+
+ // first off, make sure that we've got a command and a vssdir and a label ...
+ if( getVsspath() == null )
+ {
+ String msg = "vsspath attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+
+ // now look for illegal combinations of things ...
+
+ // build the command line from what we got the format is
+ // ss History elements [-H] [-L] [-N] [-O] [-V] [-Y] [-#] [-?]
+ // as specified in the SS.EXE help
+ commandLine.setExecutable( getSSCommand() );
+ commandLine.createArgument().setValue( COMMAND_HISTORY );
+
+ // VSS items
+ commandLine.createArgument().setValue( getVsspath() );
+
+ // -I-
+ commandLine.createArgument().setValue( "-I-" );// ignore all errors
+
+ // -V
+ // Label an existing file or project version
+ getVersionDateCommand( commandLine );
+ getVersionLabelCommand( commandLine );
+
+ // -R
+ if( m_Recursive )
+ {
+ commandLine.createArgument().setValue( FLAG_RECURSION );
+ }
+
+ // -B / -D / -F-
+ if( m_Style.length() > 0 )
+ {
+ commandLine.createArgument().setValue( m_Style );
+ }
+
+ // -Y
+ getLoginCommand( commandLine );
+
+ // -O
+ getOutputCommand( commandLine );
+
+ System.out.println( "***: " + commandLine );
+
+ result = run( commandLine );
+ if( result != 0 )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+
+ }
+
+ /**
+ * Builds the version date command.
+ *
+ * @param cmd the commandline the command is to be added to
+ */
+ private void getOutputCommand( Commandline cmd )
+ {
+ if( m_OutputFileName != null )
+ {
+ cmd.createArgument().setValue( FLAG_OUTPUT + m_OutputFileName );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ private void getRecursiveCommand( Commandline cmd )
+ {
+ if( !m_Recursive )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_RECURSION );
+ }
+ }
+
+ /**
+ * Builds the User command.
+ *
+ * @param cmd the commandline the command is to be added to
+ */
+ private void getUserCommand( Commandline cmd )
+ {
+ if( m_User != null )
+ {
+ cmd.createArgument().setValue( FLAG_USER + m_User );
+ }
+ }
+
+ /**
+ * Builds the version date command.
+ *
+ * @param cmd the commandline the command is to be added to
+ * @exception BuildException Description of Exception
+ */
+ private void getVersionDateCommand( Commandline cmd )
+ throws BuildException
+ {
+ if( m_FromDate == null && m_ToDate == null && m_NumDays == Integer.MIN_VALUE )
+ {
+ return;
+ }
+
+ if( m_FromDate != null && m_ToDate != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + m_ToDate + VALUE_FROMDATE + m_FromDate );
+ }
+ else if( m_ToDate != null && m_NumDays != Integer.MIN_VALUE )
+ {
+ String startDate = null;
+ try
+ {
+ startDate = calcDate( m_ToDate, m_NumDays );
+ }
+ catch( ParseException ex )
+ {
+ String msg = "Error parsing date: " + m_ToDate;
+ throw new BuildException( msg, location );
+ }
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + m_ToDate + VALUE_FROMDATE + startDate );
+ }
+ else if( m_FromDate != null && m_NumDays != Integer.MIN_VALUE )
+ {
+ String endDate = null;
+ try
+ {
+ endDate = calcDate( m_FromDate, m_NumDays );
+ }
+ catch( ParseException ex )
+ {
+ String msg = "Error parsing date: " + m_FromDate;
+ throw new BuildException( msg, location );
+ }
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + endDate + VALUE_FROMDATE + m_FromDate );
+ }
+ else
+ {
+ if( m_FromDate != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION + VALUE_FROMDATE + m_FromDate );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + m_ToDate );
+ }
+ }
+ }
+
+ /**
+ * Builds the version date command.
+ *
+ * @param cmd the commandline the command is to be added to
+ * @exception BuildException Description of Exception
+ */
+ private void getVersionLabelCommand( Commandline cmd )
+ throws BuildException
+ {
+ if( m_FromLabel == null && m_ToLabel == null )
+ {
+ return;
+ }
+
+ if( m_FromLabel != null && m_ToLabel != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_LABEL + m_ToLabel + VALUE_FROMLABEL + m_FromLabel );
+ }
+ else if( m_FromLabel != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION + VALUE_FROMLABEL + m_FromLabel );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_LABEL + m_ToLabel );
+ }
+ }
+
+ /**
+ * Calculates the start date for version comparison.
+ *
+ * Calculates the date numDay days earlier than startdate.
+ *
+ * @param fromDate Description of Parameter
+ * @param numDays Description of Parameter
+ * @return Description of the Returned Value
+ * @exception ParseException Description of Exception
+ */
+ private String calcDate( String fromDate, int numDays )
+ throws ParseException
+ {
+ String toDate = null;
+ Date currdate = new Date();
+ Calendar calend = new GregorianCalendar();
+ currdate = m_DateFormat.parse( fromDate );
+ calend.setTime( currdate );
+ calend.add( Calendar.DATE, numDays );
+ toDate = m_DateFormat.format( calend.getTime() );
+ return toDate;
+ }
+
+ public static class BriefCodediffNofile extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"brief", "codediff", "nofile", "default"};
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSLABEL.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSLABEL.java
new file mode 100644
index 000000000..d113a2f03
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSLABEL.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.vss;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Commandline;
+
+/**
+ * Task to perform LABEL commands to Microsoft Visual Source Safe.
+ *
+ * The following attributes are interpreted:
+ *
+ *
+ *
+ *
+ *
+ * Attribute
+ *
+ *
+ *
+ * Values
+ *
+ *
+ *
+ * Required
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * login
+ *
+ *
+ *
+ * username,password
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * vsspath
+ *
+ *
+ *
+ * SourceSafe path
+ *
+ *
+ *
+ * Yes
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ssdir
+ *
+ *
+ *
+ * directory where ss.exe resides. By default the task
+ * expects it to be in the PATH.
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * label
+ *
+ *
+ *
+ * A label to apply to the hierarchy
+ *
+ *
+ *
+ * Yes
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * version
+ *
+ *
+ *
+ * An existing file or project version to label
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * autoresponse
+ *
+ *
+ *
+ * What to respond with (sets the -I option). By default, -I- is used;
+ * values of Y or N will be appended to this.
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * comment
+ *
+ *
+ *
+ * The comment to use for this label. Empty or '-' for no comment.
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @author Phillip Wells
+ */
+public class MSVSSLABEL extends MSVSS
+{
+
+ public final static String FLAG_LABEL = "-L";
+ private String m_AutoResponse = null;
+ private String m_Label = null;
+ private String m_Version = null;
+ private String m_Comment = "-";
+
+ public void setAutoresponse( String response )
+ {
+ if( response.equals( "" ) || response.equals( "null" ) )
+ {
+ m_AutoResponse = null;
+ }
+ else
+ {
+ m_AutoResponse = response;
+ }
+ }
+
+ /**
+ * Set the comment to apply in SourceSafe
+ *
+ * If this is null or empty, it will be replaced with "-" which is what
+ * SourceSafe uses for an empty comment.
+ *
+ * @param comment The new Comment value
+ */
+ public void setComment( String comment )
+ {
+ if( comment.equals( "" ) || comment.equals( "null" ) )
+ {
+ m_Comment = "-";
+ }
+ else
+ {
+ m_Comment = comment;
+ }
+ }
+
+ /**
+ * Set the label to apply in SourceSafe
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * label="${label_server}" when label_server has not been defined to ant!
+ *
+ * @param label The new Label value
+ */
+ public void setLabel( String label )
+ {
+ if( label.equals( "" ) || label.equals( "null" ) )
+ {
+ m_Label = null;
+ }
+ else
+ {
+ m_Label = label;
+ }
+ }
+
+ /**
+ * Set the stored version string
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * version="${ver_server}" when ver_server has not been defined to ant!
+ *
+ * @param version The new Version value
+ */
+ public void setVersion( String version )
+ {
+ if( version.equals( "" ) || version.equals( "null" ) )
+ {
+ m_Version = null;
+ }
+ else
+ {
+ m_Version = version;
+ }
+ }
+
+ /**
+ * Checks the value set for the autoResponse. if it equals "Y" then we
+ * return -I-Y if it equals "N" then we return -I-N otherwise we return -I
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getAutoresponse( Commandline cmd )
+ {
+
+ if( m_AutoResponse == null )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "Y" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_YES );
+
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "N" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_NO );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }// end of else
+
+ }
+
+ /**
+ * Gets the comment to be applied.
+ *
+ * @return the comment to be applied.
+ */
+ public String getComment()
+ {
+ return m_Comment;
+ }
+
+ /**
+ * Gets the label to be applied.
+ *
+ * @return the label to be applied.
+ */
+ public String getLabel()
+ {
+ return m_Label;
+ }
+
+ /**
+ * Builds the label command.
+ *
+ * @param cmd the commandline the command is to be added to
+ */
+ public void getLabelCommand( Commandline cmd )
+ {
+ if( m_Label != null )
+ {
+ cmd.createArgument().setValue( FLAG_LABEL + m_Label );
+ }
+ }
+
+ /**
+ * Builds the version command.
+ *
+ * @param cmd the commandline the command is to be added to
+ */
+ public void getVersionCommand( Commandline cmd )
+ {
+ if( m_Version != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION + m_Version );
+ }
+ }
+
+ /**
+ * Executes the task.
+ *
+ * Builds a command line to execute ss 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();
+ int result = 0;
+
+ // first off, make sure that we've got a command and a vssdir and a label ...
+ if( getVsspath() == null )
+ {
+ String msg = "vsspath attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+ if( getLabel() == null )
+ {
+ String msg = "label attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+
+ // now look for illegal combinations of things ...
+
+ // build the command line from what we got the format is
+ // ss Label VSS items [-C] [-H] [-I-] [-Llabel] [-N] [-O] [-V] [-Y] [-?]
+ // as specified in the SS.EXE help
+ commandLine.setExecutable( getSSCommand() );
+ commandLine.createArgument().setValue( COMMAND_LABEL );
+
+ // VSS items
+ commandLine.createArgument().setValue( getVsspath() );
+
+ // -C
+ commandLine.createArgument().setValue( "-C" + getComment() );
+
+ // -I- or -I-Y or -I-N
+ getAutoresponse( commandLine );
+
+ // -L
+ // Specify the new label on the command line (instead of being prompted)
+ getLabelCommand( commandLine );
+
+ // -V
+ // Label an existing file or project version
+ getVersionCommand( commandLine );
+
+ // -Y
+ getLoginCommand( commandLine );
+
+ result = run( commandLine );
+ if( result != 0 )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/DefaultRmicAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/DefaultRmicAdapter.java
new file mode 100644
index 000000000..934d8e199
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/DefaultRmicAdapter.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.taskdefs.rmic;
+import java.io.File;
+import java.util.Random;
+import java.util.Vector;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Rmic;
+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.FileNameMapper;
+
+/**
+ * This is the default implementation for the RmicAdapter interface. Currently,
+ * this is a cut-and-paste of the original rmic task and
+ * DefaultCopmpilerAdapter.
+ *
+ * @author duncan@x180.com
+ * @author ludovic.claude@websitewatchers.co.uk
+ * @author David Maclean david@cm.co.za
+ * @author Stefan Bodewig
+ * @author Takashi Okamoto
+ */
+public abstract class DefaultRmicAdapter implements RmicAdapter
+{
+
+ private final static Random rand = new Random();
+
+ private Rmic attributes;
+ private FileNameMapper mapper;
+
+ public DefaultRmicAdapter() { }
+
+ public void setRmic( Rmic attributes )
+ {
+ this.attributes = attributes;
+ mapper = new RmicFileNameMapper();
+ }
+
+ /**
+ * The CLASSPATH this rmic process will use.
+ *
+ * @return The Classpath value
+ */
+ public Path getClasspath()
+ {
+ return getCompileClasspath();
+ }
+
+ /**
+ * This implementation maps *.class to *getStubClassSuffix().class and - if
+ * stubversion is not 1.2 - to *getSkelClassSuffix().class.
+ *
+ * @return The Mapper value
+ */
+ public FileNameMapper getMapper()
+ {
+ return mapper;
+ }
+
+ public Rmic getRmic()
+ {
+ return attributes;
+ }
+
+ /**
+ * setup rmic argument for rmic.
+ *
+ * @return Description of the Returned Value
+ */
+ protected Commandline setupRmicCommand()
+ {
+ return setupRmicCommand( null );
+ }
+
+ /**
+ * setup rmic argument for rmic.
+ *
+ * @param options additional parameters needed by a specific implementation.
+ * @return Description of the Returned Value
+ */
+ protected Commandline setupRmicCommand( String[] options )
+ {
+ Commandline cmd = new Commandline();
+
+ if( options != null )
+ {
+ for( int i = 0; i < options.length; i++ )
+ {
+ cmd.createArgument().setValue( options[i] );
+ }
+ }
+
+ Path classpath = getCompileClasspath();
+
+ cmd.createArgument().setValue( "-d" );
+ cmd.createArgument().setFile( attributes.getBase() );
+
+ if( attributes.getExtdirs() != null )
+ {
+ if( Project.getJavaVersion().startsWith( "1.1" ) )
+ {
+ /*
+ * XXX - This doesn't mix very well with build.systemclasspath,
+ */
+ classpath.addExtdirs( attributes.getExtdirs() );
+ }
+ else
+ {
+ cmd.createArgument().setValue( "-extdirs" );
+ cmd.createArgument().setPath( attributes.getExtdirs() );
+ }
+ }
+
+ cmd.createArgument().setValue( "-classpath" );
+ cmd.createArgument().setPath( classpath );
+
+ String stubVersion = attributes.getStubVersion();
+ if( null != stubVersion )
+ {
+ if( "1.1".equals( stubVersion ) )
+ cmd.createArgument().setValue( "-v1.1" );
+ else if( "1.2".equals( stubVersion ) )
+ cmd.createArgument().setValue( "-v1.2" );
+ else
+ cmd.createArgument().setValue( "-vcompat" );
+ }
+
+ if( null != attributes.getSourceBase() )
+ {
+ cmd.createArgument().setValue( "-keepgenerated" );
+ }
+
+ if( attributes.getIiop() )
+ {
+ attributes.log( "IIOP has been turned on.", Project.MSG_INFO );
+ cmd.createArgument().setValue( "-iiop" );
+ if( attributes.getIiopopts() != null )
+ {
+ attributes.log( "IIOP Options: " + attributes.getIiopopts(),
+ Project.MSG_INFO );
+ cmd.createArgument().setValue( attributes.getIiopopts() );
+ }
+ }
+
+ if( attributes.getIdl() )
+ {
+ cmd.createArgument().setValue( "-idl" );
+ attributes.log( "IDL has been turned on.", Project.MSG_INFO );
+ if( attributes.getIdlopts() != null )
+ {
+ cmd.createArgument().setValue( attributes.getIdlopts() );
+ attributes.log( "IDL Options: " + attributes.getIdlopts(),
+ Project.MSG_INFO );
+ }
+ }
+
+ if( attributes.getDebug() )
+ {
+ cmd.createArgument().setValue( "-g" );
+ }
+
+ logAndAddFilesToCompile( cmd );
+ return cmd;
+ }
+
+ /**
+ * Builds the compilation classpath.
+ *
+ * @return The CompileClasspath value
+ */
+ protected Path getCompileClasspath()
+ {
+ // add dest dir to classpath so that previously compiled and
+ // untouched classes are on classpath
+ Path classpath = new Path( attributes.getProject() );
+ classpath.setLocation( attributes.getBase() );
+
+ // Combine the build classpath with the system classpath, in an
+ // order determined by the value of build.classpath
+
+ if( attributes.getClasspath() == null )
+ {
+ if( attributes.getIncludeantruntime() )
+ {
+ classpath.addExisting( Path.systemClasspath );
+ }
+ }
+ else
+ {
+ if( attributes.getIncludeantruntime() )
+ {
+ classpath.addExisting( attributes.getClasspath().concatSystemClasspath( "last" ) );
+ }
+ else
+ {
+ classpath.addExisting( attributes.getClasspath().concatSystemClasspath( "ignore" ) );
+ }
+ }
+
+ if( attributes.getIncludejavaruntime() )
+ {
+ classpath.addJavaRuntime();
+ }
+ return classpath;
+ }
+
+ protected String getSkelClassSuffix()
+ {
+ return "_Skel";
+ }
+
+ protected String getStubClassSuffix()
+ {
+ return "_Stub";
+ }
+
+ protected String getTieClassSuffix()
+ {
+ return "_Tie";
+ }
+
+ /**
+ * Logs the compilation parameters, adds the files to compile and logs the
+ * &qout;niceSourceList"
+ *
+ * @param cmd Description of Parameter
+ */
+ protected void logAndAddFilesToCompile( Commandline cmd )
+ {
+ Vector compileList = attributes.getCompileList();
+
+ attributes.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:" );
+
+ for( int i = 0; i < compileList.size(); i++ )
+ {
+ String arg = ( String )compileList.elementAt( i );
+ cmd.createArgument().setValue( arg );
+ niceSourceList.append( " " + arg );
+ }
+
+ attributes.log( niceSourceList.toString(), Project.MSG_VERBOSE );
+ }
+
+ /**
+ * Mapper that possibly returns two file names, *_Stub and *_Skel.
+ *
+ * @author RT
+ */
+ private class RmicFileNameMapper implements FileNameMapper
+ {
+
+ RmicFileNameMapper() { }
+
+ /**
+ * Empty implementation.
+ *
+ * @param s The new From value
+ */
+ public void setFrom( String s ) { }
+
+ /**
+ * Empty implementation.
+ *
+ * @param s The new To value
+ */
+ public void setTo( String s ) { }
+
+ public String[] mapFileName( String name )
+ {
+ if( name == null
+ || !name.endsWith( ".class" )
+ || name.endsWith( getStubClassSuffix() + ".class" )
+ || name.endsWith( getSkelClassSuffix() + ".class" )
+ || name.endsWith( getTieClassSuffix() + ".class" ) )
+ {
+ // Not a .class file or the one we'd generate
+ return null;
+ }
+
+ String base = name.substring( 0, name.indexOf( ".class" ) );
+ String classname = base.replace( File.separatorChar, '.' );
+ if( attributes.getVerify() &&
+ !attributes.isValidRmiRemote( classname ) )
+ {
+ return null;
+ }
+
+ /*
+ * fallback in case we have trouble loading the class or
+ * don't know how to handle it (there is no easy way to
+ * know what IDL mode would generate.
+ *
+ * This is supposed to make Ant always recompile the
+ * class, as a file of that name should not exist.
+ */
+ String[] target = new String[]{name + ".tmp." + rand.nextLong()};
+
+ if( !attributes.getIiop() && !attributes.getIdl() )
+ {
+ // JRMP with simple naming convention
+ if( "1.2".equals( attributes.getStubVersion() ) )
+ {
+ target = new String[]{
+ base + getStubClassSuffix() + ".class"
+ };
+ }
+ else
+ {
+ target = new String[]{
+ base + getStubClassSuffix() + ".class",
+ base + getSkelClassSuffix() + ".class",
+ };
+ }
+ }
+ else if( !attributes.getIdl() )
+ {
+ int lastSlash = base.lastIndexOf( File.separatorChar );
+
+ String dirname = "";
+ /*
+ * I know, this is not necessary, but I prefer it explicit (SB)
+ */
+ int index = -1;
+ if( lastSlash == -1 )
+ {
+ // no package
+ index = 0;
+ }
+ else
+ {
+ index = lastSlash + 1;
+ dirname = base.substring( 0, index );
+ }
+
+ String filename = base.substring( index );
+
+ try
+ {
+ Class c = attributes.getLoader().loadClass( classname );
+
+ if( c.isInterface() )
+ {
+ // only stub, no tie
+ target = new String[]{
+ dirname + "_" + filename + getStubClassSuffix()
+ + ".class"
+ };
+ }
+ else
+ {
+ /*
+ * stub is derived from implementation,
+ * tie from interface name.
+ */
+ Class interf = attributes.getRemoteInterface( c );
+ String iName = interf.getName();
+ String iDir = "";
+ int iIndex = -1;
+ int lastDot = iName.lastIndexOf( "." );
+ if( lastDot == -1 )
+ {
+ // no package
+ iIndex = 0;
+ }
+ else
+ {
+ iIndex = lastDot + 1;
+ iDir = iName.substring( 0, iIndex );
+ iDir = iDir.replace( '.', File.separatorChar );
+ }
+
+ target = new String[]{
+ dirname + "_" + filename + getTieClassSuffix()
+ + ".class",
+ iDir + "_" + iName.substring( iIndex )
+ + getStubClassSuffix() + ".class"
+ };
+ }
+ }
+ catch( ClassNotFoundException e )
+ {
+ attributes.log( "Unable to verify class " + classname
+ + ". It could not be found.",
+ Project.MSG_WARN );
+ }
+ catch( NoClassDefFoundError e )
+ {
+ attributes.log( "Unable to verify class " + classname
+ + ". It is not defined.", Project.MSG_WARN );
+ }
+ catch( Throwable t )
+ {
+ attributes.log( "Unable to verify class " + classname
+ + ". Loading caused Exception: "
+ + t.getMessage(), Project.MSG_WARN );
+ }
+ }
+ return target;
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/KaffeRmic.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/KaffeRmic.java
new file mode 100644
index 000000000..a7a76813e
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/KaffeRmic.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.taskdefs.rmic;
+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.types.Commandline;
+
+/**
+ * The implementation of the rmic for Kaffe
+ *
+ * @author Takashi Okamoto
+ */
+public class KaffeRmic extends DefaultRmicAdapter
+{
+
+ public boolean execute()
+ throws BuildException
+ {
+ getRmic().log( "Using Kaffe rmic", Project.MSG_VERBOSE );
+ Commandline cmd = setupRmicCommand();
+
+ try
+ {
+
+ Class c = Class.forName( "kaffe.rmi.rmic.RMIC" );
+ Constructor cons = c.getConstructor( new Class[]{String[].class} );
+ Object rmic = cons.newInstance( new Object[]{cmd.getArguments()} );
+ Method doRmic = c.getMethod( "run", null );
+ String str[] = cmd.getArguments();
+ Boolean ok = ( Boolean )doRmic.invoke( rmic, null );
+
+ return ok.booleanValue();
+ }
+ catch( ClassNotFoundException ex )
+ {
+ throw new BuildException( "Cannot use Kaffe rmic, as it is not available" +
+ " A common solution is to set the environment variable" +
+ " JAVA_HOME or CLASSPATH.", getRmic().getLocation() );
+ }
+ catch( Exception ex )
+ {
+ if( ex instanceof BuildException )
+ {
+ throw ( BuildException )ex;
+ }
+ else
+ {
+ throw new BuildException( "Error starting Kaffe rmic: ", ex, getRmic().getLocation() );
+ }
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/RmicAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/RmicAdapter.java
new file mode 100644
index 000000000..34491f984
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/RmicAdapter.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.rmic;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.taskdefs.Rmic;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.util.FileNameMapper;
+
+/**
+ * The interface that all rmic adapters must adher to.
+ *
+ * A rmic adapter is an adapter that interprets the rmic's parameters in
+ * preperation to be passed off to the compiler this adapter represents. As all
+ * the necessary values are stored in the Rmic task itself, the only thing all
+ * adapters need is the rmic task, the execute command and a parameterless
+ * constructor (for reflection).
+ *
+ * @author Takashi Okamoto
+ * @author Stefan Bodewig
+ */
+
+public interface RmicAdapter
+{
+
+ /**
+ * Sets the rmic attributes, which are stored in the Rmic task.
+ *
+ * @param attributes The new Rmic value
+ */
+ void setRmic( Rmic attributes );
+
+ /**
+ * Executes the task.
+ *
+ * @return has the compilation been successful
+ * @exception BuildException Description of Exception
+ */
+ boolean execute()
+ throws BuildException;
+
+ /**
+ * Maps source class files to the files generated by this rmic
+ * implementation.
+ *
+ * @return The Mapper value
+ */
+ FileNameMapper getMapper();
+
+ /**
+ * The CLASSPATH this rmic process will use.
+ *
+ * @return The Classpath value
+ */
+ Path getClasspath();
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/RmicAdapterFactory.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/RmicAdapterFactory.java
new file mode 100644
index 000000000..a8b21d048
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/RmicAdapterFactory.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.rmic;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+
+/**
+ * Creates the necessary rmic adapter, given basic criteria.
+ *
+ * @author Takashi Okamoto
+ * @author J D Glanville
+ */
+public class RmicAdapterFactory
+{
+
+ /**
+ * This is a singlton -- can't create instances!!
+ */
+ private RmicAdapterFactory() { }
+
+ /**
+ * Based on the parameter passed in, this method creates the necessary
+ * factory desired. The current mapping for rmic names are as follows:
+ *
+ * sun = SUN's rmic
+ * kaffe = Kaffe's rmic
+ * a fully quallified classname = the name of a rmic adapter
+ *
+ *
+ *
+ *
+ * @param rmicType either the name of the desired rmic, or the full
+ * classname of the rmic's adapter.
+ * @param task a task to log through.
+ * @return The Rmic value
+ * @throws BuildException if the rmic type could not be resolved into a rmic
+ * adapter.
+ */
+ public static RmicAdapter getRmic( String rmicType, Task task )
+ throws BuildException
+ {
+ if( rmicType == null )
+ {
+ /*
+ * When not specified rmicType, search SUN's rmic and
+ * Kaffe's rmic.
+ */
+ try
+ {
+ Class.forName( "sun.rmi.rmic.Main" );
+ rmicType = "sun";
+ }
+ catch( ClassNotFoundException cnfe )
+ {
+ try
+ {
+ Class.forName( "kaffe.rmi.rmic.RMIC" );
+ Class.forName( "kaffe.tools.compiler.Compiler" );
+ rmicType = "kaffe";
+ }
+ catch( ClassNotFoundException cnfk )
+ {
+ throw new BuildException( "Couldn\'t guess rmic implementation" );
+ }
+ }
+ }
+
+ if( rmicType.equalsIgnoreCase( "sun" ) )
+ {
+ return new SunRmic();
+ }
+ else if( rmicType.equalsIgnoreCase( "kaffe" ) )
+ {
+ return new KaffeRmic();
+ }
+ else if( rmicType.equalsIgnoreCase( "weblogic" ) )
+ {
+ return new WLRmic();
+ }
+ return resolveClassName( rmicType );
+ }
+
+ /**
+ * Tries to resolve the given classname into a rmic 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 RmicAdapter.
+ */
+ private static RmicAdapter resolveClassName( String className )
+ throws BuildException
+ {
+ try
+ {
+ Class c = Class.forName( className );
+ Object o = c.newInstance();
+ return ( RmicAdapter )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 rmic 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/rmic/SunRmic.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/SunRmic.java
new file mode 100644
index 000000000..6b375df25
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/SunRmic.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.rmic;
+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 rmic for SUN's JDK.
+ *
+ * @author Takashi Okamoto
+ */
+public class SunRmic extends DefaultRmicAdapter
+{
+
+ public boolean execute()
+ throws BuildException
+ {
+ getRmic().log( "Using SUN rmic compiler", Project.MSG_VERBOSE );
+ Commandline cmd = setupRmicCommand();
+
+ // Create an instance of the rmic, redirecting output to
+ // the project log
+ LogOutputStream logstr = new LogOutputStream( getRmic(), Project.MSG_WARN );
+
+ try
+ {
+ Class c = Class.forName( "sun.rmi.rmic.Main" );
+ Constructor cons = c.getConstructor( new Class[]
+ {OutputStream.class, String.class} );
+ Object rmic = cons.newInstance( new Object[]{logstr, "rmic"} );
+
+ Method doRmic = c.getMethod( "compile",
+ new Class[]{String[].class} );
+ Boolean ok = ( Boolean )doRmic.invoke( rmic,
+ ( new Object[]{cmd.getArguments()} ) );
+ return ok.booleanValue();
+ }
+ catch( ClassNotFoundException ex )
+ {
+ throw new BuildException( "Cannot use SUN rmic, as it is not available" +
+ " A common solution is to set the environment variable" +
+ " JAVA_HOME or CLASSPATH.", getRmic().getLocation() );
+ }
+ catch( Exception ex )
+ {
+ if( ex instanceof BuildException )
+ {
+ throw ( BuildException )ex;
+ }
+ else
+ {
+ throw new BuildException( "Error starting SUN rmic: ", ex, getRmic().getLocation() );
+ }
+ }
+ finally
+ {
+ try
+ {
+ logstr.close();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( e );
+ }
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/WLRmic.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/WLRmic.java
new file mode 100644
index 000000000..67fc6fcf4
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/WLRmic.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.taskdefs.rmic;
+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 rmic for WebLogic
+ *
+ * @author Takashi Okamoto
+ */
+public class WLRmic extends DefaultRmicAdapter
+{
+
+ /**
+ * Get the suffix for the rmic skeleton classes
+ *
+ * @return The SkelClassSuffix value
+ */
+ public String getSkelClassSuffix()
+ {
+ return "_WLSkel";
+ }
+
+ /**
+ * Get the suffix for the rmic stub classes
+ *
+ * @return The StubClassSuffix value
+ */
+ public String getStubClassSuffix()
+ {
+ return "_WLStub";
+ }
+
+ public boolean execute()
+ throws BuildException
+ {
+ getRmic().log( "Using WebLogic rmic", Project.MSG_VERBOSE );
+ Commandline cmd = setupRmicCommand( new String[]{"-noexit"} );
+
+ try
+ {
+ // Create an instance of the rmic
+ Class c = Class.forName( "weblogic.rmic" );
+ Method doRmic = c.getMethod( "main",
+ new Class[]{String[].class} );
+ doRmic.invoke( null, new Object[]{cmd.getArguments()} );
+ return true;
+ }
+ catch( ClassNotFoundException ex )
+ {
+ throw new BuildException( "Cannot use WebLogic rmic, as it is not available" +
+ " A common solution is to set the environment variable" +
+ " CLASSPATH.", getRmic().getLocation() );
+ }
+ catch( Exception ex )
+ {
+ if( ex instanceof BuildException )
+ {
+ throw ( BuildException )ex;
+ }
+ else
+ {
+ throw new BuildException( "Error starting WebLogic rmic: ", ex, getRmic().getLocation() );
+ }
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/Commandline.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Commandline.java
new file mode 100644
index 000000000..b9b221d84
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Commandline.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.File;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+
+
+/**
+ * Commandline objects help handling command lines specifying processes to
+ * execute. The class can be used to define a command line as nested elements or
+ * as a helper to define a command line by an application.
+ *
+ *
+ * <someelement>
+ * <acommandline executable="/executable/to/run">
+ * <argument value="argument 1" />
+ * <argument line="argument_1 argument_2 argument_3"
+ * />
+ * <argument value="argument 4" />
+ * </acommandline>
+ * </someelement>
+ * The element someelement must provide a method createAcommandline
+ * which returns an instance of this class.
+ *
+ * @author thomas.haas@softwired-inc.com
+ * @author Stefan Bodewig
+ */
+public class Commandline implements Cloneable
+{
+
+ private Vector arguments = new Vector();
+ private String executable = null;
+
+ public Commandline( String to_process )
+ {
+ super();
+ String[] tmp = translateCommandline( to_process );
+ if( tmp != null && tmp.length > 0 )
+ {
+ setExecutable( tmp[0] );
+ for( int i = 1; i < tmp.length; i++ )
+ {
+ createArgument().setValue( tmp[i] );
+ }
+ }
+ }
+
+ public Commandline()
+ {
+ super();
+ }
+
+ /**
+ * Put quotes around the given String if necessary.
+ *
+ * If the argument doesn't include spaces or quotes, return it as is. If it
+ * contains double quotes, use single quotes - else surround the argument by
+ * double quotes.
+ *
+ * @param argument Description of Parameter
+ * @return Description of the Returned Value
+ */
+ public static String quoteArgument( String argument )
+ {
+ if( argument.indexOf( "\"" ) > -1 )
+ {
+ if( argument.indexOf( "\'" ) > -1 )
+ {
+ throw new BuildException( "Can\'t handle single and double quotes in same argument" );
+ }
+ else
+ {
+ return '\'' + argument + '\'';
+ }
+ }
+ else if( argument.indexOf( "\'" ) > -1 || argument.indexOf( " " ) > -1 )
+ {
+ return '\"' + argument + '\"';
+ }
+ else
+ {
+ return argument;
+ }
+ }
+
+ public static String toString( String[] line )
+ {
+ // empty path return empty string
+ if( line == null || line.length == 0 )
+ return "";
+
+ // path containing one or more elements
+ final StringBuffer result = new StringBuffer();
+ for( int i = 0; i < line.length; i++ )
+ {
+ if( i > 0 )
+ {
+ result.append( ' ' );
+ }
+ result.append( quoteArgument( line[i] ) );
+ }
+ return result.toString();
+ }
+
+ public static String[] translateCommandline( String to_process )
+ {
+ if( to_process == null || to_process.length() == 0 )
+ {
+ return new String[0];
+ }
+
+ // parse with a simple finite state machine
+
+ final int normal = 0;
+ final int inQuote = 1;
+ final int inDoubleQuote = 2;
+ int state = normal;
+ StringTokenizer tok = new StringTokenizer( to_process, "\"\' ", true );
+ Vector v = new Vector();
+ StringBuffer current = new StringBuffer();
+
+ while( tok.hasMoreTokens() )
+ {
+ String nextTok = tok.nextToken();
+ switch ( state )
+ {
+ case inQuote:
+ if( "\'".equals( nextTok ) )
+ {
+ state = normal;
+ }
+ else
+ {
+ current.append( nextTok );
+ }
+ break;
+ case inDoubleQuote:
+ if( "\"".equals( nextTok ) )
+ {
+ state = normal;
+ }
+ else
+ {
+ current.append( nextTok );
+ }
+ break;
+ default:
+ if( "\'".equals( nextTok ) )
+ {
+ state = inQuote;
+ }
+ else if( "\"".equals( nextTok ) )
+ {
+ state = inDoubleQuote;
+ }
+ else if( " ".equals( nextTok ) )
+ {
+ if( current.length() != 0 )
+ {
+ v.addElement( current.toString() );
+ current.setLength( 0 );
+ }
+ }
+ else
+ {
+ current.append( nextTok );
+ }
+ break;
+ }
+ }
+
+ if( current.length() != 0 )
+ {
+ v.addElement( current.toString() );
+ }
+
+ if( state == inQuote || state == inDoubleQuote )
+ {
+ throw new BuildException( "unbalanced quotes in " + to_process );
+ }
+
+ String[] args = new String[v.size()];
+ v.copyInto( args );
+ return args;
+ }
+
+
+ /**
+ * Sets the executable to run.
+ *
+ * @param executable The new Executable value
+ */
+ public void setExecutable( String executable )
+ {
+ if( executable == null || executable.length() == 0 )
+ return;
+ this.executable = executable.replace( '/', File.separatorChar )
+ .replace( '\\', File.separatorChar );
+ }
+
+
+ /**
+ * Returns all arguments defined by addLine, addValue
+ * or the argument object.
+ *
+ * @return The Arguments value
+ */
+ public String[] getArguments()
+ {
+ Vector result = new Vector( arguments.size() * 2 );
+ for( int i = 0; i < arguments.size(); i++ )
+ {
+ Argument arg = ( Argument )arguments.elementAt( i );
+ String[] s = arg.getParts();
+ for( int j = 0; j < s.length; j++ )
+ {
+ result.addElement( s[j] );
+ }
+ }
+
+ String[] res = new String[result.size()];
+ result.copyInto( res );
+ return res;
+ }
+
+ /**
+ * Returns the executable and all defined arguments.
+ *
+ * @return The Commandline value
+ */
+ public String[] getCommandline()
+ {
+ final String[] args = getArguments();
+ if( executable == null )
+ return args;
+ final String[] result = new String[args.length + 1];
+ result[0] = executable;
+ System.arraycopy( args, 0, result, 1, args.length );
+ return result;
+ }
+
+
+ public String getExecutable()
+ {
+ return executable;
+ }
+
+
+ public void addArguments( String[] line )
+ {
+ for( int i = 0; i < line.length; i++ )
+ {
+ createArgument().setValue( line[i] );
+ }
+ }
+
+ /**
+ * Clear out the whole command line.
+ */
+ public void clear()
+ {
+ executable = null;
+ arguments.removeAllElements();
+ }
+
+ /**
+ * Clear out the arguments but leave the executable in place for another
+ * operation.
+ */
+ public void clearArgs()
+ {
+ arguments.removeAllElements();
+ }
+
+ public Object clone()
+ {
+ Commandline c = new Commandline();
+ c.setExecutable( executable );
+ c.addArguments( getArguments() );
+ return c;
+ }
+
+ /**
+ * Creates an argument object. Each commandline object has at most one
+ * instance of the argument class.
+ *
+ * @return the argument object.
+ */
+ public Argument createArgument()
+ {
+ Argument argument = new Argument();
+ arguments.addElement( argument );
+ return argument;
+ }
+
+ /**
+ * Return a marker.
+ *
+ * This marker can be used to locate a position on the commandline - to
+ * insert something for example - when all parameters have been set.
+ *
+ * @return Description of the Returned Value
+ */
+ public Marker createMarker()
+ {
+ return new Marker( arguments.size() );
+ }
+
+ public int size()
+ {
+ return getCommandline().length;
+ }
+
+
+ public String toString()
+ {
+ return toString( getCommandline() );
+ }
+
+ /**
+ * Used for nested xml command line definitions.
+ *
+ * @author RT
+ */
+ public static class Argument
+ {
+
+ private String[] parts;
+
+ /**
+ * Sets a single commandline argument to the absolute filename of the
+ * given file.
+ *
+ * @param value a single commandline argument.
+ */
+ public void setFile( File value )
+ {
+ parts = new String[]{value.getAbsolutePath()};
+ }
+
+ /**
+ * Line to split into several commandline arguments.
+ *
+ * @param line line to split into several commandline arguments
+ */
+ public void setLine( String line )
+ {
+ parts = translateCommandline( line );
+ }
+
+ /**
+ * Sets a single commandline argument and treats it like a PATH -
+ * ensures the right separator for the local platform is used.
+ *
+ * @param value a single commandline argument.
+ */
+ public void setPath( Path value )
+ {
+ parts = new String[]{value.toString()};
+ }
+
+ /**
+ * Sets a single commandline argument.
+ *
+ * @param value a single commandline argument.
+ */
+ public void setValue( String value )
+ {
+ parts = new String[]{value};
+ }
+
+ /**
+ * Returns the parts this Argument consists of.
+ *
+ * @return The Parts value
+ */
+ public String[] getParts()
+ {
+ return parts;
+ }
+ }
+
+ /**
+ * Class to keep track of the position of an Argument.
+ *
+ * @author RT
+ */
+ // This class is there to support the srcfile and targetfile
+ // elements of <execon> and <transform> - don't know
+ // whether there might be additional use cases.
--SB
+ public class Marker
+ {
+ private int realPos = -1;
+
+ private int position;
+
+ Marker( int position )
+ {
+ this.position = position;
+ }
+
+ /**
+ * Return the number of arguments that preceeded this marker.
+ *
+ * The name of the executable - if set - is counted as the very first
+ * argument.
+ *
+ * @return The Position value
+ */
+ public int getPosition()
+ {
+ if( realPos == -1 )
+ {
+ realPos = ( executable == null ? 0 : 1 );
+ for( int i = 0; i < position; i++ )
+ {
+ Argument arg = ( Argument )arguments.elementAt( i );
+ realPos += arg.getParts().length;
+ }
+ }
+ return realPos;
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/CommandlineJava.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/CommandlineJava.java
new file mode 100644
index 000000000..489edcdc0
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/CommandlineJava.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.myrmidon.framework.Os;
+
+/**
+ * A representation of a Java command line that is nothing more than a composite
+ * of 2 Commandline . 1 for the vm/options and 1 for the
+ * classname/arguments. It provides specific methods for a java command line.
+ *
+ * @author thomas.haas@softwired-inc.com
+ * @author Stephane Bailliez
+ */
+public class CommandlineJava implements Cloneable
+{
+
+ private Commandline vmCommand = new Commandline();
+ private Commandline javaCommand = new Commandline();
+ private SysProperties sysProperties = new SysProperties();
+ private Path classpath = null;
+ private String maxMemory = null;
+
+ /**
+ * Indicate whether it will execute a jar file or not, in this case the
+ * first vm option must be a -jar and the 'executable' is a jar file.
+ */
+ private boolean executeJar = false;
+ private String vmVersion;
+
+ public CommandlineJava()
+ {
+ setVm( getJavaExecutableName() );
+ setVmversion( Project.getJavaVersion() );
+ }
+
+ /**
+ * set the classname to execute
+ *
+ * @param classname the fully qualified classname.
+ */
+ public void setClassname( String classname )
+ {
+ javaCommand.setExecutable( classname );
+ executeJar = false;
+ }
+
+ /**
+ * set a jar file to execute via the -jar option.
+ *
+ * @param jarpathname The new Jar value
+ */
+ public void setJar( String jarpathname )
+ {
+ javaCommand.setExecutable( jarpathname );
+ executeJar = true;
+ }
+
+ /**
+ * -mx or -Xmx depending on VM version
+ *
+ * @param max The new Maxmemory value
+ */
+ public void setMaxmemory( String max )
+ {
+ this.maxMemory = max;
+ }
+
+ public void setSystemProperties()
+ throws BuildException
+ {
+ sysProperties.setSystem();
+ }
+
+ public void setVm( String vm )
+ {
+ vmCommand.setExecutable( vm );
+ }
+
+ public void setVmversion( String value )
+ {
+ vmVersion = value;
+ }
+
+ /**
+ * @return the name of the class to run or null if there is no
+ * class.
+ * @see #getJar()
+ */
+ public String getClassname()
+ {
+ if( !executeJar )
+ {
+ return javaCommand.getExecutable();
+ }
+ return null;
+ }
+
+ public Path getClasspath()
+ {
+ return classpath;
+ }
+
+ /**
+ * get the command line to run a java vm.
+ *
+ * @return the list of all arguments necessary to run the vm.
+ */
+ public String[] getCommandline()
+ {
+ String[] result = new String[size()];
+ int pos = 0;
+ String[] vmArgs = getActualVMCommand().getCommandline();
+ // first argument is the java.exe path...
+ result[pos++] = vmArgs[0];
+
+ // -jar must be the first option in the command line.
+ if( executeJar )
+ {
+ result[pos++] = "-jar";
+ }
+ // next follows the vm options
+ System.arraycopy( vmArgs, 1, result, pos, vmArgs.length - 1 );
+ pos += vmArgs.length - 1;
+ // properties are part of the vm options...
+ if( sysProperties.size() > 0 )
+ {
+ System.arraycopy( sysProperties.getVariables(), 0,
+ result, pos, sysProperties.size() );
+ pos += sysProperties.size();
+ }
+ // classpath is a vm option too..
+ Path fullClasspath = classpath != null ? classpath.concatSystemClasspath( "ignore" ) : null;
+ if( fullClasspath != null && fullClasspath.toString().trim().length() > 0 )
+ {
+ result[pos++] = "-classpath";
+ result[pos++] = fullClasspath.toString();
+ }
+ // this is the classname to run as well as its arguments.
+ // in case of 'executeJar', the executable is a jar file.
+ System.arraycopy( javaCommand.getCommandline(), 0,
+ result, pos, javaCommand.size() );
+ return result;
+ }
+
+ /**
+ * @return the pathname of the jar file to run via -jar option or null
+ * if there is no jar to run.
+ * @see #getClassname()
+ */
+ public String getJar()
+ {
+ if( executeJar )
+ {
+ return javaCommand.getExecutable();
+ }
+ return null;
+ }
+
+ public Commandline getJavaCommand()
+ {
+ return javaCommand;
+ }
+
+ public SysProperties getSystemProperties()
+ {
+ return sysProperties;
+ }
+
+ public Commandline getVmCommand()
+ {
+ return getActualVMCommand();
+ }
+
+ public String getVmversion()
+ {
+ return vmVersion;
+ }
+
+ public void addSysproperty( Environment.Variable sysp )
+ {
+ sysProperties.addVariable( sysp );
+ }
+
+ /**
+ * Clear out the java arguments.
+ */
+ public void clearJavaArgs()
+ {
+ javaCommand.clearArgs();
+ }
+
+ public Object clone()
+ {
+ CommandlineJava c = new CommandlineJava();
+ c.vmCommand = ( Commandline )vmCommand.clone();
+ c.javaCommand = ( Commandline )javaCommand.clone();
+ c.sysProperties = ( SysProperties )sysProperties.clone();
+ c.maxMemory = maxMemory;
+ if( classpath != null )
+ {
+ c.classpath = ( Path )classpath.clone();
+ }
+ c.vmVersion = vmVersion;
+ c.executeJar = executeJar;
+ return c;
+ }
+
+ public Commandline.Argument createArgument()
+ {
+ return javaCommand.createArgument();
+ }
+
+ public Path createClasspath( Project p )
+ {
+ if( classpath == null )
+ {
+ classpath = new Path( p );
+ }
+ return classpath;
+ }
+
+ public Commandline.Argument createVmArgument()
+ {
+ return vmCommand.createArgument();
+ }
+
+ public void restoreSystemProperties()
+ throws BuildException
+ {
+ sysProperties.restoreSystem();
+ }
+
+ /**
+ * The size of the java command line.
+ *
+ * @return the total number of arguments in the java command line.
+ * @see #getCommandline()
+ */
+ public int size()
+ {
+ int size = getActualVMCommand().size() + javaCommand.size() + sysProperties.size();
+ // classpath is "-classpath " -> 2 args
+ Path fullClasspath = classpath != null ? classpath.concatSystemClasspath( "ignore" ) : null;
+ if( fullClasspath != null && fullClasspath.toString().trim().length() > 0 )
+ {
+ size += 2;
+ }
+ // jar execution requires an additional -jar option
+ if( executeJar )
+ {
+ size++;
+ }
+ return size;
+ }
+
+
+ public String toString()
+ {
+ return Commandline.toString( getCommandline() );
+ }
+
+ private Commandline getActualVMCommand()
+ {
+ Commandline actualVMCommand = ( Commandline )vmCommand.clone();
+ if( maxMemory != null )
+ {
+ if( vmVersion.startsWith( "1.1" ) )
+ {
+ actualVMCommand.createArgument().setValue( "-mx" + maxMemory );
+ }
+ else
+ {
+ actualVMCommand.createArgument().setValue( "-Xmx" + maxMemory );
+ }
+ }
+ return actualVMCommand;
+ }
+
+ private String getJavaExecutableName()
+ {
+ // 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/java" + extension );
+
+ if( jExecutable.exists() && !Os.isFamily( "netware" ) )
+ {
+ // NetWare may have a "java" in that directory, but 99% of
+ // the time, you don't want to execute it -- Jeff Tulley
+ //
+ return jExecutable.getAbsolutePath();
+ }
+ else
+ {
+ return "java";
+ }
+ }
+
+ /**
+ * Specialized Environment class for System properties
+ *
+ * @author RT
+ */
+ public static class SysProperties extends Environment implements Cloneable
+ {
+ Properties sys = null;
+
+ public void setSystem()
+ throws BuildException
+ {
+ try
+ {
+ Properties p = new Properties( sys = System.getProperties() );
+
+ for( Enumeration e = variables.elements(); e.hasMoreElements(); )
+ {
+ Environment.Variable v = ( Environment.Variable )e.nextElement();
+ p.put( v.getKey(), v.getValue() );
+ }
+ System.setProperties( p );
+ }
+ catch( SecurityException e )
+ {
+ throw new BuildException( "Cannot modify system properties", e );
+ }
+ }
+
+ public String[] getVariables()
+ throws BuildException
+ {
+ String props[] = super.getVariables();
+
+ if( props == null )
+ return null;
+
+ for( int i = 0; i < props.length; i++ )
+ {
+ props[i] = "-D" + props[i];
+ }
+ return props;
+ }
+
+ public Object clone()
+ {
+ try
+ {
+ SysProperties c = ( SysProperties )super.clone();
+ c.variables = ( Vector )variables.clone();
+ return c;
+ }
+ catch( CloneNotSupportedException e )
+ {
+ return null;
+ }
+ }
+
+ public void restoreSystem()
+ throws BuildException
+ {
+ if( sys == null )
+ throw new BuildException( "Unbalanced nesting of SysProperties" );
+
+ try
+ {
+ System.setProperties( sys );
+ sys = null;
+ }
+ catch( SecurityException e )
+ {
+ throw new BuildException( "Cannot modify system properties", e );
+ }
+ }
+
+ public int size()
+ {
+ return variables.size();
+ }
+
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/DataType.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/DataType.java
new file mode 100644
index 000000000..6bc7085f0
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/DataType.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.util.Stack;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.ProjectComponent;
+
+/**
+ * Base class for those classes that can appear inside the build file as stand
+ * alone data types.
+ *
+ * This class handles the common description attribute and provides a default
+ * implementation for reference handling and checking for circular references
+ * that is appropriate for types that can not be nested inside elements of the
+ * same type (i.e. <patternset> but not <path>).
+ *
+ * @author Stefan Bodewig
+ */
+public abstract class DataType extends ProjectComponent
+{
+ /**
+ * The descriptin the user has set.
+ */
+ protected String description = null;
+ /**
+ * Value to the refid attribute.
+ */
+ protected Reference ref = null;
+ /**
+ * Are we sure we don't hold circular references?
+ *
+ * Subclasses are responsible for setting this value to false if we'd need
+ * to investigate this condition (usually because a child element has been
+ * added that is a subclass of DataType).
+ */
+ protected boolean checked = true;
+
+ /**
+ * Sets a description of the current data type. It will be useful in
+ * commenting what we are doing.
+ *
+ * @param desc The new Description value
+ */
+ public void setDescription( String desc )
+ {
+ description = desc;
+ }
+
+ /**
+ * Set the value of the refid attribute.
+ *
+ * Subclasses may need to check whether any other attributes have been set
+ * as well or child elements have been created and thus override this
+ * method. if they do the must call super.setRefid.
+ *
+ * @param ref The new Refid value
+ */
+ public void setRefid( Reference ref )
+ {
+ this.ref = ref;
+ checked = false;
+ }
+
+ /**
+ * Return the description for the current data type.
+ *
+ * @return The Description value
+ */
+ public String getDescription()
+ {
+ return description;
+ }
+
+ /**
+ * Has the refid attribute of this element been set?
+ *
+ * @return The Reference value
+ */
+ public boolean isReference()
+ {
+ return ref != null;
+ }
+
+ /**
+ * Performs the check for circular references and returns the referenced
+ * object.
+ *
+ * @param requiredClass Description of Parameter
+ * @param dataTypeName Description of Parameter
+ * @return The CheckedRef value
+ */
+ protected Object getCheckedRef( Class requiredClass, String dataTypeName )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, getProject() );
+ }
+
+ Object o = ref.getReferencedObject( getProject() );
+ if( !( requiredClass.isAssignableFrom( o.getClass() ) ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a " + dataTypeName;
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return o;
+ }
+ }
+
+ /**
+ * Creates an exception that indicates the user has generated a loop of data
+ * types referencing each other.
+ *
+ * @return Description of the Returned Value
+ */
+ protected BuildException circularReference()
+ {
+ return new BuildException( "This data type contains a circular reference." );
+ }
+
+ /**
+ * Check to see whether any DataType we hold references to is included in
+ * the Stack (which holds all DataType instances that directly or indirectly
+ * reference this instance, including this instance itself).
+ *
+ * If one is included, throw a BuildException created by {@link
+ * #circularReference circularReference}.
+ *
+ * This implementation is appropriate only for a DataType that cannot hold
+ * other DataTypes as children.
+ *
+ * The general contract of this method is that it shouldn't do anything if
+ * {@link #checked checked} is true and set it to true on exit.
+ *
+ *
+ * @param stk Description of Parameter
+ * @param p Description of Parameter
+ * @exception BuildException Description of Exception
+ */
+ protected void dieOnCircularReference( Stack stk, Project p )
+ throws BuildException
+ {
+
+ if( checked || !isReference() )
+ {
+ return;
+ }
+ Object o = ref.getReferencedObject( p );
+
+ if( o instanceof DataType )
+ {
+ if( stk.contains( o ) )
+ {
+ throw circularReference();
+ }
+ else
+ {
+ stk.push( o );
+ ( ( DataType )o ).dieOnCircularReference( stk, p );
+ stk.pop();
+ }
+ }
+ checked = true;
+ }
+
+ /**
+ * 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
+ */
+ protected BuildException noChildrenAllowed()
+ {
+ return new BuildException( "You must not specify nested elements when using refid" );
+ }
+
+ /**
+ * Creates an exception that indicates that refid has to be the only
+ * attribute if it is set.
+ *
+ * @return Description of the Returned Value
+ */
+ protected BuildException tooManyAttributes()
+ {
+ return new BuildException( "You must not specify more than one attribute" +
+ " when using refid" );
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/Description.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Description.java
new file mode 100644
index 000000000..861006aac
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Description.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.types;
+
+
+/**
+ * Description is used to provide a project-wide description element (that is, a
+ * description that applies to a buildfile as a whole). If present, the
+ * <description> element is printed out before the target descriptions.
+ * Description has no attributes, only text. There can only be one project
+ * description per project. A second description element will overwrite the
+ * first.
+ *
+ * @author Craeg Strong
+ * @version $Revision$ $Date$
+ */
+public class Description extends DataType
+{
+
+ /**
+ * Adds descriptive text to the project.
+ *
+ * @param text The feature to be added to the Text attribute
+ */
+ public void addText( String text )
+ {
+ String currentDescription = project.getDescription();
+ if( currentDescription == null )
+ {
+ project.setDescription( text );
+ }
+ else
+ {
+ project.setDescription( currentDescription + text );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/EnumeratedAttribute.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/EnumeratedAttribute.java
new file mode 100644
index 000000000..24bd08bd3
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/EnumeratedAttribute.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.types;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Helper class for attributes that can only take one of a fixed list of values.
+ *
+ *
+ * See {@link org.apache.tools.ant.taskdefs.FixCRLF FixCRLF} for an example.
+ *
+ * @author Stefan Bodewig
+ */
+public abstract class EnumeratedAttribute
+{
+
+ protected String value;
+
+ public EnumeratedAttribute() { }
+
+ /**
+ * Invoked by {@link org.apache.tools.ant.IntrospectionHelper
+ * IntrospectionHelper}.
+ *
+ * @param value The new Value value
+ * @exception BuildException Description of Exception
+ */
+ public final void setValue( String value )
+ throws BuildException
+ {
+ if( !containsValue( value ) )
+ {
+ throw new BuildException( value + " is not a legal value for this attribute" );
+ }
+ this.value = value;
+ }
+
+ /**
+ * Retrieves the value.
+ *
+ * @return The Value value
+ */
+ public final String getValue()
+ {
+ return value;
+ }
+
+ /**
+ * This is the only method a subclass needs to implement.
+ *
+ * @return an array holding all possible values of the enumeration.
+ */
+ public abstract String[] getValues();
+
+ /**
+ * Is this value included in the enumeration?
+ *
+ * @param value Description of Parameter
+ * @return Description of the Returned Value
+ */
+ public final boolean containsValue( String value )
+ {
+ String[] values = getValues();
+ if( values == null || value == null )
+ {
+ return false;
+ }
+
+ for( int i = 0; i < values.length; i++ )
+ {
+ if( value.equals( values[i] ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/Environment.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Environment.java
new file mode 100644
index 000000000..ca2cd0260
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Environment.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.types;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Wrapper for environment variables.
+ *
+ * @author Stefan Bodewig
+ */
+public class Environment
+{
+
+ protected Vector variables;
+
+ public Environment()
+ {
+ variables = new Vector();
+ }
+
+ public String[] getVariables()
+ throws BuildException
+ {
+ if( variables.size() == 0 )
+ {
+ return null;
+ }
+ String[] result = new String[variables.size()];
+ for( int i = 0; i < result.length; i++ )
+ {
+ result[i] = ( ( Variable )variables.elementAt( i ) ).getContent();
+ }
+ return result;
+ }
+
+ public void addVariable( Variable var )
+ {
+ variables.addElement( var );
+ }
+
+ public static class Variable
+ {
+ private String key, value;
+
+ public Variable()
+ {
+ super();
+ }
+
+ public void setFile( java.io.File file )
+ {
+ this.value = file.getAbsolutePath();
+ }
+
+ public void setKey( String key )
+ {
+ this.key = key;
+ }
+
+ public void setPath( Path path )
+ {
+ this.value = path.toString();
+ }
+
+ public void setValue( String value )
+ {
+ this.value = value;
+ }
+
+ public String getContent()
+ throws BuildException
+ {
+ if( key == null || value == null )
+ {
+ throw new BuildException( "key and value must be specified for environment variables." );
+ }
+ StringBuffer sb = new StringBuffer( key.trim() );
+ sb.append( "=" ).append( value.trim() );
+ return sb.toString();
+ }
+
+ public String getKey()
+ {
+ return this.key;
+ }
+
+ public String getValue()
+ {
+ return this.value;
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/FileList.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/FileList.java
new file mode 100644
index 000000000..1d369eb8a
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/FileList.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.File;
+import java.util.Stack;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * FileList represents an explicitly named list of files. FileLists are useful
+ * when you want to capture a list of files regardless of whether they currently
+ * exist. By contrast, FileSet operates as a filter, only returning the name of
+ * a matched file if it currently exists in the file system.
+ *
+ * @author Craeg Strong
+ * @version $Revision$ $Date$
+ */
+public class FileList extends DataType
+{
+
+ private Vector filenames = new Vector();
+ private File dir;
+
+ public FileList()
+ {
+ super();
+ }
+
+ protected FileList( FileList filelist )
+ {
+ this.dir = filelist.dir;
+ this.filenames = filelist.filenames;
+ setProject( filelist.getProject() );
+ }
+
+ public void setDir( File dir )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ this.dir = dir;
+ }
+
+ public void setFiles( String filenames )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( filenames != null && filenames.length() > 0 )
+ {
+ StringTokenizer tok = new StringTokenizer( filenames, ", \t\n\r\f", false );
+ while( tok.hasMoreTokens() )
+ {
+ this.filenames.addElement( tok.nextToken() );
+ }
+ }
+ }
+
+ /**
+ * Makes this instance in effect a reference to another FileList instance.
+ *
+ *
+ * You must not set another attribute or nest elements inside this element
+ * if you make it a reference.
+ *
+ * @param r The new Refid value
+ * @exception BuildException Description of Exception
+ */
+ public void setRefid( Reference r )
+ throws BuildException
+ {
+ if( ( dir != null ) || ( filenames.size() != 0 ) )
+ {
+ throw tooManyAttributes();
+ }
+ super.setRefid( r );
+ }
+
+ public File getDir( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getDir( p );
+ }
+ return dir;
+ }
+
+ /**
+ * Returns the list of files represented by this FileList.
+ *
+ * @param p Description of Parameter
+ * @return The Files value
+ */
+ public String[] getFiles( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getFiles( p );
+ }
+
+ if( dir == null )
+ {
+ throw new BuildException( "No directory specified for filelist." );
+ }
+
+ if( filenames.size() == 0 )
+ {
+ throw new BuildException( "No files specified for filelist." );
+ }
+
+ String result[] = new String[filenames.size()];
+ filenames.copyInto( result );
+ return result;
+ }
+
+ /**
+ * Performs the check for circular references and returns the referenced
+ * FileList.
+ *
+ * @param p Description of Parameter
+ * @return The Ref value
+ */
+ protected FileList getRef( Project p )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, p );
+ }
+
+ Object o = ref.getReferencedObject( p );
+ if( !( o instanceof FileList ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a filelist";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( FileList )o;
+ }
+ }
+
+}//-- FileList.java
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/FileSet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/FileSet.java
new file mode 100644
index 000000000..5961775ab
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/FileSet.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.File;
+import java.util.Stack;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.FileScanner;
+import org.apache.tools.ant.Project;
+
+/**
+ * Moved out of MatchingTask to make it a standalone object that could be
+ * referenced (by scripts for example).
+ *
+ * @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
+ * @author Magesh Umasankar
+ */
+public class FileSet extends DataType implements Cloneable
+{
+
+ private PatternSet defaultPatterns = new PatternSet();
+ private Vector additionalPatterns = new Vector();
+ private boolean useDefaultExcludes = true;
+ private boolean isCaseSensitive = true;
+
+ private File dir;
+
+ public FileSet()
+ {
+ super();
+ }
+
+ protected FileSet( FileSet fileset )
+ {
+ this.dir = fileset.dir;
+ this.defaultPatterns = fileset.defaultPatterns;
+ this.additionalPatterns = fileset.additionalPatterns;
+ this.useDefaultExcludes = fileset.useDefaultExcludes;
+ this.isCaseSensitive = fileset.isCaseSensitive;
+ setProject( getProject() );
+ }
+
+ /**
+ * Sets case sensitivity of the file system
+ *
+ * @param isCaseSensitive "true"|"on"|"yes" if file system is case
+ * sensitive, "false"|"off"|"no" when not.
+ */
+ public void setCaseSensitive( boolean isCaseSensitive )
+ {
+ this.isCaseSensitive = isCaseSensitive;
+ }
+
+ /**
+ * 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 )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ this.useDefaultExcludes = useDefaultExcludes;
+ }
+
+ public void setDir( File dir )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ 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 )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ defaultPatterns.setExcludes( excludes );
+ }
+
+ /**
+ * Sets the name of the file containing the includes patterns.
+ *
+ * @param excl The file to fetch the exclude patterns from.
+ * @exception BuildException Description of Exception
+ */
+ public void setExcludesfile( File excl )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ defaultPatterns.setExcludesfile( excl );
+ }
+
+ /**
+ * 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 )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ defaultPatterns.setIncludes( includes );
+ }
+
+ /**
+ * Sets the name of the file containing the includes patterns.
+ *
+ * @param incl The file to fetch the include patterns from.
+ * @exception BuildException Description of Exception
+ */
+ public void setIncludesfile( File incl )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ defaultPatterns.setIncludesfile( incl );
+ }
+
+
+ /**
+ * Makes this instance in effect a reference to another PatternSet instance.
+ *
+ *
+ * You must not set another attribute or nest elements inside this element
+ * if you make it a reference.
+ *
+ * @param r The new Refid value
+ * @exception BuildException Description of Exception
+ */
+ public void setRefid( Reference r )
+ throws BuildException
+ {
+ if( dir != null || defaultPatterns.hasPatterns() )
+ {
+ throw tooManyAttributes();
+ }
+ if( !additionalPatterns.isEmpty() )
+ {
+ throw noChildrenAllowed();
+ }
+ super.setRefid( r );
+ }
+
+ public void setupDirectoryScanner( FileScanner ds, Project p )
+ {
+ if( ds == null )
+ {
+ throw new IllegalArgumentException( "ds cannot be null" );
+ }
+
+ ds.setBasedir( dir );
+
+ for( int i = 0; i < additionalPatterns.size(); i++ )
+ {
+ Object o = additionalPatterns.elementAt( i );
+ defaultPatterns.append( ( PatternSet )o, p );
+ }
+
+ p.log( "FileSet: Setup file scanner in dir " + dir +
+ " with " + defaultPatterns, p.MSG_DEBUG );
+
+ ds.setIncludes( defaultPatterns.getIncludePatterns( p ) );
+ ds.setExcludes( defaultPatterns.getExcludePatterns( p ) );
+ if( useDefaultExcludes )
+ ds.addDefaultExcludes();
+ ds.setCaseSensitive( isCaseSensitive );
+ }
+
+ public File getDir( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getDir( p );
+ }
+ return dir;
+ }
+
+ /**
+ * Returns the directory scanner needed to access the files to process.
+ *
+ * @param p Description of Parameter
+ * @return The DirectoryScanner value
+ */
+ public DirectoryScanner getDirectoryScanner( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getDirectoryScanner( p );
+ }
+
+ if( dir == null )
+ {
+ throw new BuildException( "No directory specified for fileset." );
+ }
+
+ if( !dir.exists() )
+ {
+ throw new BuildException( dir.getAbsolutePath() + " not found." );
+ }
+ if( !dir.isDirectory() )
+ {
+ throw new BuildException( dir.getAbsolutePath() + " is not a directory." );
+ }
+
+ DirectoryScanner ds = new DirectoryScanner();
+ setupDirectoryScanner( ds, p );
+ ds.scan();
+ return ds;
+ }
+
+ /**
+ * Return a FileSet that has the same basedir and same patternsets as this
+ * one.
+ *
+ * @return Description of the Returned Value
+ */
+ public Object clone()
+ {
+ if( isReference() )
+ {
+ return new FileSet( getRef( getProject() ) );
+ }
+ else
+ {
+ return new FileSet( this );
+ }
+ }
+
+ /**
+ * add a name entry on the exclude list
+ *
+ * @return Description of the Returned Value
+ */
+ public PatternSet.NameEntry createExclude()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return defaultPatterns.createExclude();
+ }
+
+ /**
+ * add a name entry on the include files list
+ *
+ * @return Description of the Returned Value
+ */
+ public PatternSet.NameEntry createExcludesFile()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return defaultPatterns.createExcludesFile();
+ }
+
+ /**
+ * add a name entry on the include list
+ *
+ * @return Description of the Returned Value
+ */
+ public PatternSet.NameEntry createInclude()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return defaultPatterns.createInclude();
+ }
+
+ /**
+ * add a name entry on the include files list
+ *
+ * @return Description of the Returned Value
+ */
+ public PatternSet.NameEntry createIncludesFile()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return defaultPatterns.createIncludesFile();
+ }
+
+ public PatternSet createPatternSet()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ PatternSet patterns = new PatternSet();
+ additionalPatterns.addElement( patterns );
+ return patterns;
+ }
+
+ /**
+ * Performs the check for circular references and returns the referenced
+ * FileSet.
+ *
+ * @param p Description of Parameter
+ * @return The Ref value
+ */
+ protected FileSet getRef( Project p )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, p );
+ }
+
+ Object o = ref.getReferencedObject( p );
+ if( !( o instanceof FileSet ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a fileset";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( FileSet )o;
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/FilterSet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/FilterSet.java
new file mode 100644
index 000000000..927114f4a
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/FilterSet.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;// java io classes
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;// java util classes
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;// ant classes
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+
+/**
+ * A set of filters to be applied to something. A filter set may have begintoken
+ * and endtokens defined.
+ *
+ * @author Michael McCallum
+ * @created 14 March 2001
+ */
+public class FilterSet extends DataType implements Cloneable
+{
+
+ /**
+ * The default token start string
+ */
+ public final static String DEFAULT_TOKEN_START = "@";
+
+ /**
+ * The default token end string
+ */
+ public final static String DEFAULT_TOKEN_END = "@";
+
+ private String startOfToken = DEFAULT_TOKEN_START;
+ private String endOfToken = DEFAULT_TOKEN_END;
+
+ /**
+ * List of ordered filters and filter files.
+ */
+ private Vector filters = new Vector();
+
+ public FilterSet() { }
+
+ /**
+ * Create a Filterset from another filterset
+ *
+ * @param filterset the filterset upon which this filterset will be based.
+ */
+ protected FilterSet( FilterSet filterset )
+ {
+ super();
+ this.filters = ( Vector )filterset.getFilters().clone();
+ }
+
+ /**
+ * The string used to id the beginning of a token.
+ *
+ * @param startOfToken The new Begintoken value
+ */
+ public void setBeginToken( String startOfToken )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( startOfToken == null || "".equals( startOfToken ) )
+ {
+ throw new BuildException( "beginToken must not be empty" );
+ }
+ this.startOfToken = startOfToken;
+ }
+
+
+ /**
+ * The string used to id the end of a token.
+ *
+ * @param endOfToken The new Endtoken value
+ */
+ public void setEndToken( String endOfToken )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( endOfToken == null || "".equals( endOfToken ) )
+ {
+ throw new BuildException( "endToken must not be empty" );
+ }
+ this.endOfToken = endOfToken;
+ }
+
+ /**
+ * set the file containing the filters for this filterset.
+ *
+ * @param filtersFile sets the filter fil to read filters for this filter
+ * set from.
+ * @exception BuildException if there is a problem reading the filters
+ */
+ public void setFiltersfile( File filtersFile )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ readFiltersFromFile( filtersFile );
+ }
+
+ public String getBeginToken()
+ {
+ if( isReference() )
+ {
+ return getRef().getBeginToken();
+ }
+ return startOfToken;
+ }
+
+ public String getEndToken()
+ {
+ if( isReference() )
+ {
+ return getRef().getEndToken();
+ }
+ return endOfToken;
+ }
+
+ /**
+ * Gets the filter hash of the FilterSet.
+ *
+ * @return The hash of the tokens and values for quick lookup.
+ */
+ public Hashtable getFilterHash()
+ {
+ int filterSize = getFilters().size();
+ Hashtable filterHash = new Hashtable( filterSize );
+ for( Enumeration e = getFilters().elements(); e.hasMoreElements(); )
+ {
+ Filter filter = ( Filter )e.nextElement();
+ filterHash.put( filter.getToken(), filter.getValue() );
+ }
+ return filterHash;
+ }
+
+ /**
+ * Create a new filter
+ *
+ * @param filter The feature to be added to the Filter attribute
+ */
+ public void addFilter( Filter filter )
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ filters.addElement( filter );
+ }
+
+ /**
+ * Add a new filter made from the given token and value.
+ *
+ * @param token The token for the new filter.
+ * @param value The value for the new filter.
+ */
+ public void addFilter( String token, String value )
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ filters.addElement( new Filter( token, value ) );
+ }
+
+ /**
+ * Add a Filterset to this filter set
+ *
+ * @param filterSet the filterset to be added to this filterset
+ */
+ public void addFilterSet( FilterSet filterSet )
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ for( Enumeration e = filterSet.getFilters().elements(); e.hasMoreElements(); )
+ {
+ filters.addElement( ( Filter )e.nextElement() );
+ }
+ }
+
+ public Object clone()
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ return new FilterSet( getRef() );
+ }
+ else
+ {
+ return new FilterSet( this );
+ }
+ }
+
+ /**
+ * Create a new FiltersFile
+ *
+ * @return The filter that was created.
+ */
+ public FiltersFile createFiltersfile()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return new FiltersFile();
+ }
+
+ /**
+ * Test to see if this filter set it empty.
+ *
+ * @return Return true if there are filter in this set otherwise false.
+ */
+ public boolean hasFilters()
+ {
+ return getFilters().size() > 0;
+ }
+
+
+ /**
+ * Read the filters from the given file.
+ *
+ * @param filtersFile the file from which filters are read
+ * @exception BuildException Throw a build exception when unable to read the
+ * file.
+ */
+ public void readFiltersFromFile( File filtersFile )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ if( filtersFile.isFile() )
+ {
+ log( "Reading filters from " + filtersFile, Project.MSG_VERBOSE );
+ FileInputStream in = null;
+ try
+ {
+ Properties props = new Properties();
+ in = new FileInputStream( filtersFile );
+ props.load( in );
+
+ Enumeration enum = props.propertyNames();
+ Vector filters = getFilters();
+ while( enum.hasMoreElements() )
+ {
+ String strPropName = ( String )enum.nextElement();
+ String strValue = props.getProperty( strPropName );
+ filters.addElement( new Filter( strPropName, strValue ) );
+ }
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( "Could not read filters from file: " + filtersFile );
+ }
+ finally
+ {
+ if( in != null )
+ {
+ try
+ {
+ in.close();
+ }
+ catch( IOException ioex )
+ {
+ }
+ }
+ }
+ }
+ else
+ {
+ throw new BuildException( "Must specify a file not a directory in the filtersfile attribute:" + filtersFile );
+ }
+ }
+
+ /**
+ * Does replacement on the given string with token matching. This uses the
+ * defined begintoken and endtoken values which default to @ for both.
+ *
+ * @param line The line to process the tokens in.
+ * @return The string with the tokens replaced.
+ */
+ public String replaceTokens( String line )
+ {
+ String beginToken = getBeginToken();
+ String endToken = getEndToken();
+ int index = line.indexOf( beginToken );
+
+ if( index > -1 )
+ {
+ Hashtable tokens = getFilterHash();
+ try
+ {
+ StringBuffer b = new StringBuffer();
+ int i = 0;
+ String token = null;
+ String value = null;
+
+ do
+ {
+ int endIndex = line.indexOf( endToken, index + beginToken.length() + 1 );
+ if( endIndex == -1 )
+ {
+ break;
+ }
+ token = line.substring( index + beginToken.length(), endIndex );
+ b.append( line.substring( i, index ) );
+ if( tokens.containsKey( token ) )
+ {
+ value = ( String )tokens.get( token );
+ log( "Replacing: " + beginToken + token + endToken + " -> " + value, Project.MSG_VERBOSE );
+ b.append( value );
+ i = index + beginToken.length() + token.length() + endToken.length();
+ }
+ else
+ {
+ // just append beginToken and search further
+ b.append( beginToken );
+ i = index + beginToken.length();
+ }
+ }while ( ( index = line.indexOf( beginToken, i ) ) > -1 );
+
+ b.append( line.substring( i ) );
+ return b.toString();
+ }
+ catch( StringIndexOutOfBoundsException e )
+ {
+ return line;
+ }
+ }
+ else
+ {
+ return line;
+ }
+ }
+
+ protected Vector getFilters()
+ {
+ if( isReference() )
+ {
+ return getRef().getFilters();
+ }
+ return filters;
+ }
+
+ protected FilterSet getRef()
+ {
+ return ( FilterSet )getCheckedRef( FilterSet.class, "filterset" );
+ }
+
+ /**
+ * Individual filter component of filterset
+ *
+ * @author Michael McCallum
+ * @created 14 March 2001
+ */
+ public static class Filter
+ {
+ /**
+ * Token which will be replaced in the filter operation
+ */
+ String token;
+
+ /**
+ * The value which will replace the token in the filtering operation
+ */
+ String value;
+
+ /**
+ * Constructor for the Filter object
+ *
+ * @param token The token which will be replaced when filtering
+ * @param value The value which will replace the token when filtering
+ */
+ public Filter( String token, String value )
+ {
+ this.token = token;
+ this.value = value;
+ }
+
+ /**
+ * No argument conmstructor
+ */
+ public Filter() { }
+
+ /**
+ * Sets the Token attribute of the Filter object
+ *
+ * @param token The new Token value
+ */
+ public void setToken( String token )
+ {
+ this.token = token;
+ }
+
+ /**
+ * Sets the Value attribute of the Filter object
+ *
+ * @param value The new Value value
+ */
+ public void setValue( String value )
+ {
+ this.value = value;
+ }
+
+ /**
+ * Gets the Token attribute of the Filter object
+ *
+ * @return The Token value
+ */
+ public String getToken()
+ {
+ return token;
+ }
+
+ /**
+ * Gets the Value attribute of the Filter object
+ *
+ * @return The Value value
+ */
+ public String getValue()
+ {
+ return value;
+ }
+ }
+
+ /**
+ * The filtersfile nested element.
+ *
+ * @author Michael McCallum
+ * @created Thursday, April 19, 2001
+ */
+ public class FiltersFile
+ {
+
+ /**
+ * Constructor for the Filter object
+ */
+ public FiltersFile() { }
+
+ /**
+ * Sets the file from which filters will be read.
+ *
+ * @param file the file from which filters will be read.
+ */
+ public void setFile( File file )
+ {
+ readFiltersFromFile( file );
+ }
+ }
+
+}
+
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/FilterSetCollection.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/FilterSetCollection.java
new file mode 100644
index 000000000..9bc5162a9
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/FilterSetCollection.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.types;// java io classes
+// java util classes
+import java.util.Enumeration;
+import java.util.Vector;
+
+// ant classes
+
+
+
+/**
+ * A FilterSetCollection is a collection of filtersets each of which may have a
+ * different start/end token settings.
+ *
+ * @author Conor MacNeill
+ */
+public class FilterSetCollection
+{
+
+ private Vector filterSets = new Vector();
+
+ public FilterSetCollection() { }
+
+ public FilterSetCollection( FilterSet filterSet )
+ {
+ addFilterSet( filterSet );
+ }
+
+
+ public void addFilterSet( FilterSet filterSet )
+ {
+ filterSets.addElement( filterSet );
+ }
+
+ /**
+ * Test to see if this filter set it empty.
+ *
+ * @return Return true if there are filter in this set otherwise false.
+ */
+ public boolean hasFilters()
+ {
+ for( Enumeration e = filterSets.elements(); e.hasMoreElements(); )
+ {
+ FilterSet filterSet = ( FilterSet )e.nextElement();
+ if( filterSet.hasFilters() )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Does replacement on the given string with token matching. This uses the
+ * defined begintoken and endtoken values which default to @ for both.
+ *
+ * @param line The line to process the tokens in.
+ * @return The string with the tokens replaced.
+ */
+ public String replaceTokens( String line )
+ {
+ String replacedLine = line;
+ for( Enumeration e = filterSets.elements(); e.hasMoreElements(); )
+ {
+ FilterSet filterSet = ( FilterSet )e.nextElement();
+ replacedLine = filterSet.replaceTokens( replacedLine );
+ }
+ return replacedLine;
+ }
+}
+
+
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/Mapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Mapper.java
new file mode 100644
index 000000000..b0df6dc91
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Mapper.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.util.Properties;
+import java.util.Stack;
+import org.apache.tools.ant.AntClassLoader;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.util.FileNameMapper;
+
+/**
+ * Element to define a FileNameMapper.
+ *
+ * @author Stefan Bodewig
+ */
+public class Mapper extends DataType implements Cloneable
+{
+
+ protected MapperType type = null;
+
+ protected String classname = null;
+
+ protected Path classpath = null;
+
+ protected String from = null;
+
+ protected String to = null;
+
+ public Mapper( Project p )
+ {
+ setProject( p );
+ }
+
+ /**
+ * Set the class name of the FileNameMapper to use.
+ *
+ * @param classname The new Classname value
+ */
+ public void setClassname( String classname )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ this.classname = classname;
+ }
+
+ /**
+ * Set the classpath to load the FileNameMapper through (attribute).
+ *
+ * @param classpath The new Classpath value
+ */
+ public void setClasspath( Path classpath )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( this.classpath == null )
+ {
+ this.classpath = classpath;
+ }
+ else
+ {
+ this.classpath.append( classpath );
+ }
+ }
+
+ /**
+ * Set the classpath to load the FileNameMapper through via reference
+ * (attribute).
+ *
+ * @param r The new ClasspathRef value
+ */
+ public void setClasspathRef( Reference r )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ createClasspath().setRefid( r );
+ }
+
+ /**
+ * Set the argument to FileNameMapper.setFrom
+ *
+ * @param from The new From value
+ */
+ public void setFrom( String from )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ this.from = from;
+ }
+
+ /**
+ * Make this Mapper instance a reference to another Mapper.
+ *
+ * You must not set any other attribute if you make it a reference.
+ *
+ * @param r The new Refid value
+ * @exception BuildException Description of Exception
+ */
+ public void setRefid( Reference r )
+ throws BuildException
+ {
+ if( type != null || from != null || to != null )
+ {
+ throw tooManyAttributes();
+ }
+ super.setRefid( r );
+ }
+
+ /**
+ * Set the argument to FileNameMapper.setTo
+ *
+ * @param to The new To value
+ */
+ public void setTo( String to )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ this.to = to;
+ }
+
+ /**
+ * Set the type of FileNameMapper to use.
+ *
+ * @param type The new Type value
+ */
+ public void setType( MapperType type )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ this.type = type;
+ }
+
+ /**
+ * Returns a fully configured FileNameMapper implementation.
+ *
+ * @return The Implementation value
+ * @exception BuildException Description of Exception
+ */
+ public FileNameMapper getImplementation()
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ return getRef().getImplementation();
+ }
+
+ if( type == null && classname == null )
+ {
+ throw new BuildException( "one of the attributes type or classname is required" );
+ }
+
+ if( type != null && classname != null )
+ {
+ throw new BuildException( "must not specify both type and classname attribute" );
+ }
+
+ try
+ {
+ if( type != null )
+ {
+ classname = type.getImplementation();
+ }
+
+ Class c = null;
+ if( classpath == null )
+ {
+ c = Class.forName( classname );
+ }
+ else
+ {
+ AntClassLoader al = new AntClassLoader( getProject(),
+ classpath );
+ c = al.loadClass( classname );
+ AntClassLoader.initializeClass( c );
+ }
+
+ FileNameMapper m = ( FileNameMapper )c.newInstance();
+ m.setFrom( from );
+ m.setTo( to );
+ return m;
+ }
+ catch( BuildException be )
+ {
+ throw be;
+ }
+ catch( Throwable t )
+ {
+ throw new BuildException( t );
+ }
+ finally
+ {
+ if( type != null )
+ {
+ classname = null;
+ }
+ }
+ }
+
+ /**
+ * Set the classpath to load the FileNameMapper through (nested element).
+ *
+ * @return Description of the Returned Value
+ */
+ public Path createClasspath()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ if( this.classpath == null )
+ {
+ this.classpath = new Path( getProject() );
+ }
+ return this.classpath.createPath();
+ }
+
+ /**
+ * Performs the check for circular references and returns the referenced
+ * Mapper.
+ *
+ * @return The Ref value
+ */
+ protected Mapper getRef()
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, getProject() );
+ }
+
+ Object o = ref.getReferencedObject( getProject() );
+ if( !( o instanceof Mapper ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a mapper";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( Mapper )o;
+ }
+ }
+
+ /**
+ * Class as Argument to FileNameMapper.setType.
+ *
+ * @author RT
+ */
+ public static class MapperType extends EnumeratedAttribute
+ {
+ private Properties implementations;
+
+ public MapperType()
+ {
+ implementations = new Properties();
+ implementations.put( "identity",
+ "org.apache.tools.ant.util.IdentityMapper" );
+ implementations.put( "flatten",
+ "org.apache.tools.ant.util.FlatFileNameMapper" );
+ implementations.put( "glob",
+ "org.apache.tools.ant.util.GlobPatternMapper" );
+ implementations.put( "merge",
+ "org.apache.tools.ant.util.MergingMapper" );
+ implementations.put( "regexp",
+ "org.apache.tools.ant.util.RegexpPatternMapper" );
+ }
+
+ public String getImplementation()
+ {
+ return implementations.getProperty( getValue() );
+ }
+
+ public String[] getValues()
+ {
+ return new String[]{"identity", "flatten", "glob", "merge", "regexp"};
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/Path.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Path.java
new file mode 100644
index 000000000..729dd96ae
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Path.java
@@ -0,0 +1,706 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Stack;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.PathTokenizer;
+import org.apache.tools.ant.Project;
+
+
+/**
+ * This object represents a path as used by CLASSPATH or PATH environment
+ * variable.
+ *
+ *
+ * <sometask>
+ * <somepath>
+ * <pathelement location="/path/to/file.jar" />
+ *
+ * <pathelement
+ * path="/path/to/file2.jar:/path/to/class2;/path/to/class3" />
+ * <pathelement location="/path/to/file3.jar" />
+ *
+ * <pathelement location="/path/to/file4.jar" />
+ *
+ * </somepath>
+ * </sometask>
+ *
+ *
+ * The object implemention sometask must provide a method called
+ * createSomepath which returns an instance of Path.
+ * Nested path definitions are handled by the Path object and must be labeled
+ * pathelement.
+ *
+ * The path element takes a parameter path which will be parsed and
+ * split into single elements. It will usually be used to define a path from an
+ * environment variable.
+ *
+ * @author Thomas.Haas@softwired-inc.com
+ * @author Stefan Bodewig
+ */
+
+public class Path extends DataType implements Cloneable
+{
+
+ public static Path systemClasspath =
+ new Path( null, System.getProperty( "java.class.path" ) );
+
+ private Vector elements;
+
+ /**
+ * Invoked by IntrospectionHelper for setXXX(Path p) attribute
+ * setters.
+ *
+ * @param p Description of Parameter
+ * @param path Description of Parameter
+ */
+ public Path( Project p, String path )
+ {
+ this( p );
+ createPathElement().setPath( path );
+ }
+
+ public Path( Project project )
+ {
+ setProject( project );
+ elements = new Vector();
+ }
+
+ /**
+ * Returns its argument with all file separator characters replaced so that
+ * they match the local OS conventions.
+ *
+ * @param source Description of Parameter
+ * @return Description of the Returned Value
+ */
+ public static String translateFile( String source )
+ {
+ if( source == null )
+ return "";
+
+ final StringBuffer result = new StringBuffer( source );
+ for( int i = 0; i < result.length(); i++ )
+ {
+ translateFileSep( result, i );
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * Splits a PATH (with : or ; as separators) into its parts.
+ *
+ * @param project Description of Parameter
+ * @param source Description of Parameter
+ * @return Description of the Returned Value
+ */
+ public static String[] translatePath( Project project, String source )
+ {
+ final Vector result = new Vector();
+ if( source == null )
+ return new String[0];
+
+ PathTokenizer tok = new PathTokenizer( source );
+ StringBuffer element = new StringBuffer();
+ while( tok.hasMoreTokens() )
+ {
+ element.setLength( 0 );
+ String pathElement = tok.nextToken();
+ try
+ {
+ element.append( resolveFile( project, pathElement ) );
+ }
+ catch( BuildException e )
+ {
+ project.log( "Dropping path element " + pathElement + " as it is not valid relative to the project",
+ Project.MSG_VERBOSE );
+ }
+ for( int i = 0; i < element.length(); i++ )
+ {
+ translateFileSep( element, i );
+ }
+ result.addElement( element.toString() );
+ }
+ String[] res = new String[result.size()];
+ result.copyInto( res );
+ return res;
+ }
+
+ /**
+ * Translates all occurrences of / or \ to correct separator of the current
+ * platform and returns whether it had to do any replacements.
+ *
+ * @param buffer Description of Parameter
+ * @param pos Description of Parameter
+ * @return Description of the Returned Value
+ */
+ protected static boolean translateFileSep( StringBuffer buffer, int pos )
+ {
+ if( buffer.charAt( pos ) == '/' || buffer.charAt( pos ) == '\\' )
+ {
+ buffer.setCharAt( pos, File.separatorChar );
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds a String to the Vector if it isn't already included.
+ *
+ * @param v The feature to be added to the UnlessPresent attribute
+ * @param s The feature to be added to the UnlessPresent attribute
+ */
+ private static void addUnlessPresent( Vector v, String s )
+ {
+ if( v.indexOf( s ) == -1 )
+ {
+ v.addElement( s );
+ }
+ }
+
+ /**
+ * Resolve a filename with Project's help - if we know one that is.
+ *
+ * Assume the filename is absolute if project is null.
+ *
+ * @param project Description of Parameter
+ * @param relativeName Description of Parameter
+ * @return Description of the Returned Value
+ */
+ private static String resolveFile( Project project, String relativeName )
+ {
+ if( project != null )
+ {
+ File f = project.resolveFile( relativeName );
+ return f.getAbsolutePath();
+ }
+ return relativeName;
+ }
+
+ /**
+ * Adds a element definition to the path.
+ *
+ * @param location the location of the element to add (must not be null
+ * nor empty.
+ * @exception BuildException Description of Exception
+ */
+ public void setLocation( File location )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ createPathElement().setLocation( location );
+ }
+
+
+ /**
+ * Parses a path definition and creates single PathElements.
+ *
+ * @param path the path definition.
+ * @exception BuildException Description of Exception
+ */
+ public void setPath( String path )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ createPathElement().setPath( path );
+ }
+
+ /**
+ * Makes this instance in effect a reference to another Path instance.
+ *
+ * You must not set another attribute or nest elements inside this element
+ * if you make it a reference.
+ *
+ * @param r The new Refid value
+ * @exception BuildException Description of Exception
+ */
+ public void setRefid( Reference r )
+ throws BuildException
+ {
+ if( !elements.isEmpty() )
+ {
+ throw tooManyAttributes();
+ }
+ elements.addElement( r );
+ super.setRefid( r );
+ }
+
+ /**
+ * Adds the components on the given path which exist to this Path.
+ * Components that don't exist, aren't added.
+ *
+ * @param source - source path whose components are examined for existence
+ */
+ public void addExisting( Path source )
+ {
+ String[] list = source.list();
+ for( int i = 0; i < list.length; i++ )
+ {
+ File f = null;
+ if( getProject() != null )
+ {
+ f = getProject().resolveFile( list[i] );
+ }
+ else
+ {
+ f = new File( list[i] );
+ }
+
+ if( f.exists() )
+ {
+ setLocation( f );
+ }
+ }
+ }
+
+ /**
+ * Emulation of extdirs feature in java >= 1.2. This method adds all files
+ * in the given directories (but not in sub-directories!) to the classpath,
+ * so that you don't have to specify them all one by one.
+ *
+ * @param extdirs The feature to be added to the Extdirs attribute
+ */
+ public void addExtdirs( Path extdirs )
+ {
+ if( extdirs == null )
+ {
+ String extProp = System.getProperty( "java.ext.dirs" );
+ if( extProp != null )
+ {
+ extdirs = new Path( getProject(), extProp );
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ String[] dirs = extdirs.list();
+ for( int i = 0; i < dirs.length; i++ )
+ {
+ File dir = getProject().resolveFile( dirs[i] );
+ if( dir.exists() && dir.isDirectory() )
+ {
+ FileSet fs = new FileSet();
+ fs.setDir( dir );
+ fs.setIncludes( "*" );
+ addFileset( fs );
+ }
+ }
+ }
+
+ /**
+ * Adds a nested <fileset> element.
+ *
+ * @param fs The feature to be added to the Fileset attribute
+ * @exception BuildException Description of Exception
+ */
+ public void addFileset( FileSet fs )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ elements.addElement( fs );
+ checked = false;
+ }
+
+ /**
+ * Add the Java Runtime classes to this Path instance.
+ */
+ public void addJavaRuntime()
+ {
+ if( System.getProperty( "java.vendor" ).toLowerCase( Locale.US ).indexOf( "microsoft" ) >= 0 )
+ {
+ // Pull in *.zip from packages directory
+ FileSet msZipFiles = new FileSet();
+ msZipFiles.setDir( new File( System.getProperty( "java.home" ) + File.separator + "Packages" ) );
+ msZipFiles.setIncludes( "*.ZIP" );
+ addFileset( msZipFiles );
+ }
+ else if( "Kaffe".equals( System.getProperty( "java.vm.name" ) ) )
+ {
+ FileSet kaffeJarFiles = new FileSet();
+ kaffeJarFiles.setDir( new File( System.getProperty( "java.home" )
+ + File.separator + "share"
+ + File.separator + "kaffe" ) );
+
+ kaffeJarFiles.setIncludes( "*.jar" );
+ addFileset( kaffeJarFiles );
+ }
+ else if( Project.getJavaVersion() == Project.JAVA_1_1 )
+ {
+ addExisting( new Path( null,
+ System.getProperty( "java.home" )
+ + File.separator + "lib"
+ + File.separator
+ + "classes.zip" ) );
+ }
+ else
+ {
+ // JDK > 1.1 seems to set java.home to the JRE directory.
+ addExisting( new Path( null,
+ System.getProperty( "java.home" )
+ + File.separator + "lib"
+ + File.separator + "rt.jar" ) );
+ // Just keep the old version as well and let addExisting
+ // sort it out.
+ addExisting( new Path( null,
+ System.getProperty( "java.home" )
+ + File.separator + "jre"
+ + File.separator + "lib"
+ + File.separator + "rt.jar" ) );
+
+ // Added for MacOS X
+ addExisting( new Path( null,
+ System.getProperty( "java.home" )
+ + File.separator + ".."
+ + File.separator + "Classes"
+ + File.separator + "classes.jar" ) );
+ addExisting( new Path( null,
+ System.getProperty( "java.home" )
+ + File.separator + ".."
+ + File.separator + "Classes"
+ + File.separator + "ui.jar" ) );
+ }
+ }
+
+ /**
+ * Append the contents of the other Path instance to this.
+ *
+ * @param other Description of Parameter
+ */
+ public void append( Path other )
+ {
+ if( other == null )
+ return;
+ String[] l = other.list();
+ for( int i = 0; i < l.length; i++ )
+ {
+ if( elements.indexOf( l[i] ) == -1 )
+ {
+ elements.addElement( l[i] );
+ }
+ }
+ }
+
+ /**
+ * Return a Path that holds the same elements as this instance.
+ *
+ * @return Description of the Returned Value
+ */
+ public Object clone()
+ {
+ Path p = new Path( getProject() );
+ p.append( this );
+ return p;
+ }
+
+ /**
+ * Concatenates the system class path in the order specified by the
+ * ${build.sysclasspath} property - using "last" as default value.
+ *
+ * @return Description of the Returned Value
+ */
+ public Path concatSystemClasspath()
+ {
+ return concatSystemClasspath( "last" );
+ }
+
+ /**
+ * Concatenates the system class path in the order specified by the
+ * ${build.sysclasspath} property - using the supplied value if
+ * ${build.sysclasspath} has not been set.
+ *
+ * @param defValue Description of Parameter
+ * @return Description of the Returned Value
+ */
+ public Path concatSystemClasspath( String defValue )
+ {
+
+ Path result = new Path( getProject() );
+
+ String order = defValue;
+ if( getProject() != null )
+ {
+ String o = getProject().getProperty( "build.sysclasspath" );
+ if( o != null )
+ {
+ order = o;
+ }
+ }
+
+ if( order.equals( "only" ) )
+ {
+ // only: the developer knows what (s)he is doing
+ result.addExisting( Path.systemClasspath );
+
+ }
+ else if( order.equals( "first" ) )
+ {
+ // first: developer could use a little help
+ result.addExisting( Path.systemClasspath );
+ result.addExisting( this );
+
+ }
+ else if( order.equals( "ignore" ) )
+ {
+ // ignore: don't trust anyone
+ result.addExisting( this );
+
+ }
+ else
+ {
+ // last: don't trust the developer
+ if( !order.equals( "last" ) )
+ {
+ log( "invalid value for build.sysclasspath: " + order,
+ Project.MSG_WARN );
+ }
+
+ result.addExisting( this );
+ result.addExisting( Path.systemClasspath );
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates a nested <path> element.
+ *
+ * @return Description of the Returned Value
+ * @exception BuildException Description of Exception
+ */
+ public Path createPath()
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ Path p = new Path( getProject() );
+ elements.addElement( p );
+ checked = false;
+ return p;
+ }
+
+ /**
+ * Creates the nested <pathelement> element.
+ *
+ * @return Description of the Returned Value
+ * @exception BuildException Description of Exception
+ */
+ public PathElement createPathElement()
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ PathElement pe = new PathElement();
+ elements.addElement( pe );
+ return pe;
+ }
+
+ /**
+ * Returns all path elements defined by this and nested path objects.
+ *
+ * @return list of path elements.
+ */
+ public String[] list()
+ {
+ if( !checked )
+ {
+ // make sure we don't have a circular reference here
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, getProject() );
+ }
+
+ Vector result = new Vector( 2 * elements.size() );
+ for( int i = 0; i < elements.size(); i++ )
+ {
+ Object o = elements.elementAt( i );
+ if( o instanceof Reference )
+ {
+ Reference r = ( Reference )o;
+ o = r.getReferencedObject( getProject() );
+ // we only support references to paths right now
+ if( !( o instanceof Path ) )
+ {
+ String msg = r.getRefId() + " doesn\'t denote a path";
+ throw new BuildException( msg );
+ }
+ }
+
+ if( o instanceof String )
+ {
+ // obtained via append
+ addUnlessPresent( result, ( String )o );
+ }
+ else if( o instanceof PathElement )
+ {
+ String[] parts = ( ( PathElement )o ).getParts();
+ if( parts == null )
+ {
+ throw new BuildException( "You must either set location or path on " );
+ }
+ for( int j = 0; j < parts.length; j++ )
+ {
+ addUnlessPresent( result, parts[j] );
+ }
+ }
+ else if( o instanceof Path )
+ {
+ Path p = ( Path )o;
+ if( p.getProject() == null )
+ {
+ p.setProject( getProject() );
+ }
+ String[] parts = p.list();
+ for( int j = 0; j < parts.length; j++ )
+ {
+ addUnlessPresent( result, parts[j] );
+ }
+ }
+ else if( o instanceof FileSet )
+ {
+ FileSet fs = ( FileSet )o;
+ DirectoryScanner ds = fs.getDirectoryScanner( getProject() );
+ String[] s = ds.getIncludedFiles();
+ File dir = fs.getDir( getProject() );
+ for( int j = 0; j < s.length; j++ )
+ {
+ File f = new File( dir, s[j] );
+ String absolutePath = f.getAbsolutePath();
+ addUnlessPresent( result, translateFile( absolutePath ) );
+ }
+ }
+ }
+ String[] res = new String[result.size()];
+ result.copyInto( res );
+ return res;
+ }
+
+ /**
+ * How many parts does this Path instance consist of.
+ *
+ * @return Description of the Returned Value
+ */
+ public int size()
+ {
+ return list().length;
+ }
+
+
+ /**
+ * Returns a textual representation of the path, which can be used as
+ * CLASSPATH or PATH environment variable definition.
+ *
+ * @return a textual representation of the path.
+ */
+ public String toString()
+ {
+ final String[] list = list();
+
+ // empty path return empty string
+ if( list.length == 0 )
+ return "";
+
+ // path containing one or more elements
+ final StringBuffer result = new StringBuffer( list[0].toString() );
+ for( int i = 1; i < list.length; i++ )
+ {
+ result.append( File.pathSeparatorChar );
+ result.append( list[i] );
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * Overrides the version of DataType to recurse on all DataType child
+ * elements that may have been added.
+ *
+ * @param stk Description of Parameter
+ * @param p Description of Parameter
+ * @exception BuildException Description of Exception
+ */
+ protected void dieOnCircularReference( Stack stk, Project p )
+ throws BuildException
+ {
+
+ if( checked )
+ {
+ return;
+ }
+
+ Enumeration enum = elements.elements();
+ while( enum.hasMoreElements() )
+ {
+ Object o = enum.nextElement();
+ if( o instanceof Reference )
+ {
+ o = ( ( Reference )o ).getReferencedObject( p );
+ }
+
+ if( o instanceof DataType )
+ {
+ if( stk.contains( o ) )
+ {
+ throw circularReference();
+ }
+ else
+ {
+ stk.push( o );
+ ( ( DataType )o ).dieOnCircularReference( stk, p );
+ stk.pop();
+ }
+ }
+ }
+ checked = true;
+ }
+
+
+ /**
+ * Helper class, holds the nested <pathelement> values.
+ *
+ * @author RT
+ */
+ public class PathElement
+ {
+ private String[] parts;
+
+ public void setLocation( File loc )
+ {
+ parts = new String[]{translateFile( loc.getAbsolutePath() )};
+ }
+
+ public void setPath( String path )
+ {
+ parts = Path.translatePath( getProject(), path );
+ }
+
+ public String[] getParts()
+ {
+ return parts;
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/PatternSet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/PatternSet.java
new file mode 100644
index 000000000..b4200922f
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/PatternSet.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Stack;
+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.ProjectHelper;
+
+/**
+ * Named collection of include/exclude tags.
+ *
+ * Moved out of MatchingTask to make it a standalone object that could be
+ * referenced (by scripts for example).
+ *
+ * @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 class PatternSet extends DataType
+{
+ private Vector includeList = new Vector();
+ private Vector excludeList = new Vector();
+ private Vector includesFileList = new Vector();
+ private Vector excludesFileList = new Vector();
+
+ public PatternSet()
+ {
+ super();
+ }
+
+ /**
+ * 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 )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( excludes != null && excludes.length() > 0 )
+ {
+ StringTokenizer tok = new StringTokenizer( excludes, ", ", false );
+ while( tok.hasMoreTokens() )
+ {
+ createExclude().setName( tok.nextToken() );
+ }
+ }
+ }
+
+ /**
+ * Sets the name of the file containing the excludes patterns.
+ *
+ * @param excludesFile The file to fetch the exclude patterns from.
+ * @exception BuildException Description of Exception
+ */
+ public void setExcludesfile( File excludesFile )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ createExcludesFile().setName( excludesFile.getAbsolutePath() );
+ }
+
+ /**
+ * 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 )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( includes != null && includes.length() > 0 )
+ {
+ StringTokenizer tok = new StringTokenizer( includes, ", ", false );
+ while( tok.hasMoreTokens() )
+ {
+ createInclude().setName( tok.nextToken() );
+ }
+ }
+ }
+
+ /**
+ * Sets the name of the file containing the includes patterns.
+ *
+ * @param includesFile The file to fetch the include patterns from.
+ * @exception BuildException Description of Exception
+ */
+ public void setIncludesfile( File includesFile )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ createIncludesFile().setName( includesFile.getAbsolutePath() );
+ }
+
+ /**
+ * Makes this instance in effect a reference to another PatternSet instance.
+ *
+ *
+ * You must not set another attribute or nest elements inside this element
+ * if you make it a reference.
+ *
+ * @param r The new Refid value
+ * @exception BuildException Description of Exception
+ */
+ public void setRefid( Reference r )
+ throws BuildException
+ {
+ if( !includeList.isEmpty() || !excludeList.isEmpty() )
+ {
+ throw tooManyAttributes();
+ }
+ super.setRefid( r );
+ }
+
+ /**
+ * Returns the filtered include patterns.
+ *
+ * @param p Description of Parameter
+ * @return The ExcludePatterns value
+ */
+ public String[] getExcludePatterns( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getExcludePatterns( p );
+ }
+ else
+ {
+ readFiles( p );
+ return makeArray( excludeList, p );
+ }
+ }
+
+ /**
+ * Returns the filtered include patterns.
+ *
+ * @param p Description of Parameter
+ * @return The IncludePatterns value
+ */
+ public String[] getIncludePatterns( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getIncludePatterns( p );
+ }
+ else
+ {
+ readFiles( p );
+ return makeArray( includeList, p );
+ }
+ }
+
+ /**
+ * Adds the patterns of the other instance to this set.
+ *
+ * @param other Description of Parameter
+ * @param p Description of Parameter
+ */
+ public void append( PatternSet other, Project p )
+ {
+ if( isReference() )
+ {
+ throw new BuildException( "Cannot append to a reference" );
+ }
+
+ String[] incl = other.getIncludePatterns( p );
+ if( incl != null )
+ {
+ for( int i = 0; i < incl.length; i++ )
+ {
+ createInclude().setName( incl[i] );
+ }
+ }
+
+ String[] excl = other.getExcludePatterns( p );
+ if( excl != null )
+ {
+ for( int i = 0; i < excl.length; i++ )
+ {
+ createExclude().setName( excl[i] );
+ }
+ }
+ }
+
+ /**
+ * add a name entry on the exclude list
+ *
+ * @return Description of the Returned Value
+ */
+ public NameEntry createExclude()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return addPatternToList( excludeList );
+ }
+
+ /**
+ * add a name entry on the exclude files list
+ *
+ * @return Description of the Returned Value
+ */
+ public NameEntry createExcludesFile()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return addPatternToList( excludesFileList );
+ }
+
+ /**
+ * add a name entry on the include list
+ *
+ * @return Description of the Returned Value
+ */
+ public NameEntry createInclude()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return addPatternToList( includeList );
+ }
+
+ /**
+ * add a name entry on the include files list
+ *
+ * @return Description of the Returned Value
+ */
+ public NameEntry createIncludesFile()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return addPatternToList( includesFileList );
+ }
+
+ public String toString()
+ {
+ return "patternSet{ includes: " + includeList +
+ " excludes: " + excludeList + " }";
+ }
+
+ /**
+ * helper for FileSet.
+ *
+ * @return Description of the Returned Value
+ */
+ boolean hasPatterns()
+ {
+ return includesFileList.size() > 0 || excludesFileList.size() > 0
+ || includeList.size() > 0 || excludeList.size() > 0;
+ }
+
+ /**
+ * Performs the check for circular references and returns the referenced
+ * PatternSet.
+ *
+ * @param p Description of Parameter
+ * @return The Ref value
+ */
+ private PatternSet getRef( Project p )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, p );
+ }
+
+ Object o = ref.getReferencedObject( p );
+ if( !( o instanceof PatternSet ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a patternset";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( PatternSet )o;
+ }
+ }
+
+ /**
+ * add a name entry to the given list
+ *
+ * @param list The feature to be added to the PatternToList attribute
+ * @return Description of the Returned Value
+ */
+ private NameEntry addPatternToList( Vector list )
+ {
+ NameEntry result = new NameEntry();
+ list.addElement( result );
+ return result;
+ }
+
+ /**
+ * Convert a vector of NameEntry elements into an array of Strings.
+ *
+ * @param list Description of Parameter
+ * @param p Description of Parameter
+ * @return Description of the Returned Value
+ */
+ private String[] makeArray( Vector list, Project p )
+ {
+ if( list.size() == 0 )
+ return null;
+
+ Vector tmpNames = new Vector();
+ for( Enumeration e = list.elements(); e.hasMoreElements(); )
+ {
+ NameEntry ne = ( NameEntry )e.nextElement();
+ String pattern = ne.evalName( p );
+ if( pattern != null && pattern.length() > 0 )
+ {
+ tmpNames.addElement( pattern );
+ }
+ }
+
+ String result[] = new String[tmpNames.size()];
+ tmpNames.copyInto( result );
+ return result;
+ }
+
+ /**
+ * Read includesfile ot excludesfile if not already done so.
+ *
+ * @param p Description of Parameter
+ */
+ private void readFiles( Project p )
+ {
+ if( includesFileList.size() > 0 )
+ {
+ Enumeration e = includesFileList.elements();
+ while( e.hasMoreElements() )
+ {
+ NameEntry ne = ( NameEntry )e.nextElement();
+ String fileName = ne.evalName( p );
+ if( fileName != null )
+ {
+ File inclFile = p.resolveFile( fileName );
+ if( !inclFile.exists() )
+ throw new BuildException( "Includesfile "
+ + inclFile.getAbsolutePath()
+ + " not found." );
+ readPatterns( inclFile, includeList, p );
+ }
+ }
+ includesFileList.removeAllElements();
+ }
+
+ if( excludesFileList.size() > 0 )
+ {
+ Enumeration e = excludesFileList.elements();
+ while( e.hasMoreElements() )
+ {
+ NameEntry ne = ( NameEntry )e.nextElement();
+ String fileName = ne.evalName( p );
+ if( fileName != null )
+ {
+ File exclFile = p.resolveFile( fileName );
+ if( !exclFile.exists() )
+ throw new BuildException( "Excludesfile "
+ + exclFile.getAbsolutePath()
+ + " not found." );
+ readPatterns( exclFile, excludeList, p );
+ }
+ }
+ excludesFileList.removeAllElements();
+ }
+ }
+
+ /**
+ * Reads path matching patterns from a file and adds them to the includes or
+ * excludes list (as appropriate).
+ *
+ * @param patternfile Description of Parameter
+ * @param patternlist Description of Parameter
+ * @param p Description of Parameter
+ * @exception BuildException Description of Exception
+ */
+ private void readPatterns( File patternfile, Vector patternlist, Project p )
+ throws BuildException
+ {
+
+ BufferedReader patternReader = null;
+ try
+ {
+ // Get a FileReader
+ patternReader =
+ new BufferedReader( new FileReader( patternfile ) );
+
+ // Create one NameEntry in the appropriate pattern list for each
+ // line in the file.
+ String line = patternReader.readLine();
+ while( line != null )
+ {
+ if( line.length() > 0 )
+ {
+ line = p.replaceProperties( line );
+ addPatternToList( patternlist ).setName( line );
+ }
+ line = patternReader.readLine();
+ }
+ }
+ catch( IOException ioe )
+ {
+ String msg = "An error occured while reading from pattern file: "
+ + patternfile;
+ throw new BuildException( msg, ioe );
+ }
+ finally
+ {
+ if( null != patternReader )
+ {
+ try
+ {
+ patternReader.close();
+ }
+ catch( IOException ioe )
+ {
+ //Ignore exception
+ }
+ }
+ }
+ }
+
+ /**
+ * inner class to hold a name on list. "If" and "Unless" attributes may be
+ * used to invalidate the entry based on the existence of a property
+ * (typically set thru the use of the Available task).
+ *
+ * @author RT
+ */
+ public class NameEntry
+ {
+ private String ifCond;
+ private String name;
+ private String unlessCond;
+
+ public void setIf( String cond )
+ {
+ ifCond = cond;
+ }
+
+ public void setName( String name )
+ {
+ this.name = name;
+ }
+
+ public void setUnless( String cond )
+ {
+ unlessCond = cond;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public String evalName( Project p )
+ {
+ return valid( p ) ? name : null;
+ }
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer( name );
+ if( ( ifCond != null ) || ( unlessCond != null ) )
+ {
+ buf.append( ":" );
+ String connector = "";
+
+ if( ifCond != null )
+ {
+ buf.append( "if->" );
+ buf.append( ifCond );
+ connector = ";";
+ }
+ if( unlessCond != null )
+ {
+ buf.append( connector );
+ buf.append( "unless->" );
+ buf.append( unlessCond );
+ }
+ }
+
+ return buf.toString();
+ }
+
+ private boolean valid( Project p )
+ {
+ if( ifCond != null && p.getProperty( ifCond ) == null )
+ {
+ return false;
+ }
+ else if( unlessCond != null && p.getProperty( unlessCond ) != null )
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/Reference.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Reference.java
new file mode 100644
index 000000000..11cb33915
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Reference.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.types;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * Class to hold a reference to another object in the project.
+ *
+ * @author Stefan Bodewig
+ */
+public class Reference
+{
+
+ private String refid;
+
+ public Reference()
+ {
+ super();
+ }
+
+ public Reference( String id )
+ {
+ this();
+ setRefId( id );
+ }
+
+ public void setRefId( String id )
+ {
+ refid = id;
+ }
+
+ public String getRefId()
+ {
+ return refid;
+ }
+
+ public Object getReferencedObject( Project project )
+ throws BuildException
+ {
+ if( refid == null )
+ {
+ throw new BuildException( "No reference specified" );
+ }
+
+ Object o = project.getReference( refid );
+ if( o == null )
+ {
+ throw new BuildException( "Reference " + refid + " not found." );
+ }
+ return o;
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/RegularExpression.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/RegularExpression.java
new file mode 100644
index 000000000..6f0d31451
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/RegularExpression.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.types;
+import java.util.Stack;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.util.regexp.Regexp;
+import org.apache.tools.ant.util.regexp.RegexpFactory;
+
+/**
+ * A regular expression datatype. Keeps an instance of the compiled expression
+ * for speed purposes. This compiled expression is lazily evaluated (it is
+ * compiled the first time it is needed). The syntax is the dependent on which
+ * regular expression type you are using. The system property
+ * "ant.regexp.regexpimpl" will be the classname of the implementation that will
+ * be used.
+ * For jdk <= 1.3, there are two available implementations:
+ * org.apache.tools.ant.util.regexp.JakartaOroRegexp (the default)
+ * Based on the jakarta-oro package
+ *
+ * org.apache.tools.ant.util.regexp.JakartaRegexpRegexp
+ * Based on the jakarta-regexp package
+ *
+ * For jdk >= 1.4 an additional implementation is available:
+ * org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp
+ * Based on the jdk 1.4 built in regular expression package.
+ *
+ * <regularexpression [ [id="id"] pattern="expression" | refid="id" ]
+ * />
+ *
+ *
+ * @author Matthew Inger
+ * mattinger@mindless.com
+ * @see org.apache.oro.regex.Perl5Compiler
+ * @see org.apache.regexp.RE
+ * @see java.util.regex.Pattern
+ * @see org.apache.tools.ant.util.regexp.Regexp
+ */
+public class RegularExpression extends DataType
+{
+ public final static String DATA_TYPE_NAME = "regularexpression";
+
+ // The regular expression factory
+ private final static RegexpFactory factory = new RegexpFactory();
+
+ private Regexp regexp;
+
+ public RegularExpression()
+ {
+ this.regexp = factory.newRegexp();
+ }
+
+ public void setPattern( String pattern )
+ {
+ this.regexp.setPattern( pattern );
+ }
+
+ /**
+ * Gets the pattern string for this RegularExpression in the given project.
+ *
+ * @param p Description of Parameter
+ * @return The Pattern value
+ */
+ public String getPattern( Project p )
+ {
+ if( isReference() )
+ return getRef( p ).getPattern( p );
+
+ return regexp.getPattern();
+ }
+
+ /**
+ * Get the RegularExpression this reference refers to in the given project.
+ * Check for circular references too
+ *
+ * @param p Description of Parameter
+ * @return The Ref value
+ */
+ public RegularExpression getRef( Project p )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, p );
+ }
+
+ Object o = ref.getReferencedObject( p );
+ if( !( o instanceof RegularExpression ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a regularexpression";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( RegularExpression )o;
+ }
+ }
+
+ public Regexp getRegexp( Project p )
+ {
+ if( isReference() )
+ return getRef( p ).getRegexp( p );
+ return this.regexp;
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/Substitution.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Substitution.java
new file mode 100644
index 000000000..c640c0466
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/Substitution.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.types;
+import java.util.Stack;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.DataType;
+
+/**
+ * A regular expression substitution datatype. It is an expression that is meant
+ * to replace a regular expression.
+ * <substitition [ [id="id"] expression="expression" | refid="id" ]
+ * />
+ *
+ *
+ * @author Matthew Inger
+ * mattinger@mindless.com
+ * @see org.apache.oro.text.regex.Perl5Substitition
+ */
+public class Substitution extends DataType
+{
+ public final static String DATA_TYPE_NAME = "substitition";
+
+ private String expression;
+
+ public Substitution()
+ {
+ this.expression = null;
+ }
+
+ public void setExpression( String expression )
+ {
+ this.expression = expression;
+ }
+
+ /**
+ * Gets the pattern string for this RegularExpression in the given project.
+ *
+ * @param p Description of Parameter
+ * @return The Expression value
+ */
+ public String getExpression( Project p )
+ {
+ if( isReference() )
+ return getRef( p ).getExpression( p );
+
+ return expression;
+ }
+
+ /**
+ * Get the RegularExpression this reference refers to in the given project.
+ * Check for circular references too
+ *
+ * @param p Description of Parameter
+ * @return The Ref value
+ */
+ public Substitution getRef( Project p )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, p );
+ }
+
+ Object o = ref.getReferencedObject( p );
+ if( !( o instanceof Substitution ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a substitution";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( Substitution )o;
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/ZipFileSet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/ZipFileSet.java
new file mode 100644
index 000000000..c98d28ac7
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/ZipFileSet.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.File;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;
+
+/**
+ * A ZipFileSet is a FileSet with extra attributes useful in the context of
+ * Zip/Jar tasks. A ZipFileSet extends FileSets with the ability to extract a
+ * subset of the entries of a Zip file for inclusion in another Zip file. It
+ * also includes a prefix attribute which is prepended to each entry in the
+ * output Zip file. At present, ZipFileSets are not surfaced in the public API.
+ * FileSets nested in a Zip task are instantiated as ZipFileSets, and their
+ * attributes are only recognized in the context of the the Zip task. It is not
+ * possible to define a ZipFileSet outside of the Zip task and refer to it via a
+ * refid. However a standard FileSet may be included by reference in the Zip
+ * task, and attributes in the refering ZipFileSet can augment FileSet
+ * definition.
+ *
+ * @author Don Ferguson don@bea.com
+ */
+public class ZipFileSet extends FileSet
+{
+
+ private File srcFile = null;
+ private String prefix = "";
+ private String fullpath = "";
+ private boolean hasDir = false;
+
+ /**
+ * Set the directory for the fileset. Prevents both "dir" and "src" from
+ * being specified.
+ *
+ * @param dir The new Dir value
+ * @exception BuildException Description of Exception
+ */
+ public void setDir( File dir )
+ throws BuildException
+ {
+ if( srcFile != null )
+ {
+ throw new BuildException( "Cannot set both dir and src attributes" );
+ }
+ else
+ {
+ super.setDir( dir );
+ hasDir = true;
+ }
+ }
+
+ /**
+ * Set the full pathname of the single entry in this fileset.
+ *
+ * @param fullpath The new Fullpath value
+ */
+ public void setFullpath( String fullpath )
+ {
+ this.fullpath = fullpath;
+ }
+
+ /**
+ * Prepend this prefix to the path for each zip entry. Does not perform
+ * reference test; the referenced file set can be augmented with a prefix.
+ *
+ * @param prefix The prefix to prepend to entries in the zip file.
+ */
+ public void setPrefix( String prefix )
+ {
+ this.prefix = prefix;
+ }
+
+ /**
+ * Set the source Zip file for the zipfileset. Prevents both "dir" and "src"
+ * from being specified.
+ *
+ * @param srcFile The zip file from which to extract entries.
+ */
+ public void setSrc( File srcFile )
+ {
+ if( hasDir )
+ {
+ throw new BuildException( "Cannot set both dir and src attributes" );
+ }
+ this.srcFile = srcFile;
+ }
+
+ /**
+ * Return the DirectoryScanner associated with this FileSet. If the
+ * ZipFileSet defines a source Zip file, then a ZipScanner is returned
+ * instead.
+ *
+ * @param p Description of Parameter
+ * @return The DirectoryScanner value
+ */
+ public DirectoryScanner getDirectoryScanner( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getDirectoryScanner( p );
+ }
+ if( srcFile != null )
+ {
+ ZipScanner zs = new ZipScanner();
+ zs.setSrc( srcFile );
+ super.setDir( p.getBaseDir() );
+ setupDirectoryScanner( zs, p );
+ zs.init();
+ return zs;
+ }
+ else
+ {
+ return super.getDirectoryScanner( p );
+ }
+ }
+
+ /**
+ * Return the full pathname of the single entry in this fileset.
+ *
+ * @return The Fullpath value
+ */
+ public String getFullpath()
+ {
+ return fullpath;
+ }
+
+ /**
+ * Return the prefix prepended to entries in the zip file.
+ *
+ * @return The Prefix value
+ */
+ public String getPrefix()
+ {
+ return prefix;
+ }
+
+ /**
+ * Get the zip file from which entries will be extracted. References are not
+ * followed, since it is not possible to have a reference to a ZipFileSet,
+ * only to a FileSet.
+ *
+ * @return The Src value
+ */
+ public File getSrc()
+ {
+ return srcFile;
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/ZipScanner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/ZipScanner.java
new file mode 100644
index 000000000..4ed9fec95
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/ZipScanner.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.types;
+import java.io.File;
+import org.apache.tools.ant.DirectoryScanner;
+
+/**
+ * ZipScanner accesses the pattern matching algorithm in DirectoryScanner, which
+ * are protected methods that can only be accessed by subclassing. This
+ * implementation of FileScanner defines getIncludedFiles to return only the Zip
+ * File which is being scanned, not the matching Zip entries. Arguably, it
+ * should return the matching entries, however this would complicate existing
+ * code which assumes that FileScanners return a set of file system files that
+ * can be accessed directly.
+ *
+ * @author Don Ferguson don@bea.com
+ */
+public class ZipScanner extends DirectoryScanner
+{
+
+ /**
+ * The zip file which should be scanned.
+ */
+ protected File srcFile;
+
+ /**
+ * Sets the srcFile for scanning. This is the jar or zip file that is
+ * scanned for matching entries.
+ *
+ * @param srcFile the (non-null) zip file name for scanning
+ */
+ public void setSrc( File srcFile )
+ {
+ this.srcFile = srcFile;
+ }
+
+ /**
+ * Returns an empty list of directories to create.
+ *
+ * @return The IncludedDirectories value
+ */
+ public String[] getIncludedDirectories()
+ {
+ return new String[0];
+ }
+
+ /**
+ * Returns the zip file itself, not the matching entries within the zip
+ * file. This keeps the uptodate test in the Zip task simple; otherwise we'd
+ * need to treat zip filesets specially.
+ *
+ * @return the source file from which entries will be extracted.
+ */
+ public String[] getIncludedFiles()
+ {
+ String[] result = new String[1];
+ result[0] = srcFile.getAbsolutePath();
+ return result;
+ }
+
+ /**
+ * Initialize DirectoryScanner data structures.
+ */
+ public void init()
+ {
+ 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];
+ }
+ }
+
+ /**
+ * Matches a jar entry against the includes/excludes list, normalizing the
+ * path separator.
+ *
+ * @param path the (non-null) path name to test for inclusion
+ * @return true if the path should be included false
+ * otherwise.
+ */
+ public boolean match( String path )
+ {
+ String vpath = path.replace( '/', File.separatorChar ).
+ replace( '\\', File.separatorChar );
+ return isIncluded( vpath ) && !isExcluded( vpath );
+ }
+
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/defaults.properties b/proposal/myrmidon/src/main/org/apache/tools/ant/types/defaults.properties
new file mode 100644
index 000000000..eab5c9a8d
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/defaults.properties
@@ -0,0 +1,10 @@
+path=org.apache.tools.ant.types.Path
+fileset=org.apache.tools.ant.types.FileSet
+filelist=org.apache.tools.ant.types.FileList
+patternset=org.apache.tools.ant.types.PatternSet
+mapper=org.apache.tools.ant.types.Mapper
+filterset=org.apache.tools.ant.types.FilterSet
+description=org.apache.tools.ant.types.Description
+classfileset=org.apache.tools.ant.types.optional.depend.ClassfileSet
+substitution=org.apache.tools.ant.types.Substitution
+regularexpression=org.apache.tools.ant.types.RegularExpression
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/optional/depend/ClassfileSet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/optional/depend/ClassfileSet.java
new file mode 100644
index 000000000..287d945ac
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/optional/depend/ClassfileSet.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.types.optional.depend;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+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.util.depend.Dependencies;
+
+/**
+ * A DepSet is a FileSet, that enlists all classes that depend on a certain
+ * class. A DependSet extends FileSets and uses another FileSet as input. The
+ * nested FileSet attribute provides the domain, that is used for searching for
+ * dependent classes
+ *
+ * @author Holger Engels
+ */
+public class ClassfileSet extends FileSet
+{
+ private List rootClasses = new ArrayList();
+
+ protected ClassfileSet( ClassfileSet s )
+ {
+ super( s );
+ rootClasses = s.rootClasses;
+ }
+
+ public void setRootClass( String rootClass )
+ throws BuildException
+ {
+ rootClasses.add( rootClass );
+ }
+
+ /**
+ * Return the DirectoryScanner associated with this FileSet.
+ *
+ * @param p Description of Parameter
+ * @return The DirectoryScanner value
+ */
+ public DirectoryScanner getDirectoryScanner( Project p )
+ {
+ DependScanner scanner = new DependScanner();
+ scanner.setBasedir( getDir( p ) );
+ scanner.setRootClasses( rootClasses );
+ scanner.scan();
+ return scanner;
+ }
+
+ public void addConfiguredRoot( ClassRoot root )
+ {
+ rootClasses.add( root.getClassname() );
+ }
+
+ public Object clone()
+ {
+ if( isReference() )
+ {
+ return new ClassfileSet( ( ClassfileSet )getRef( getProject() ) );
+ }
+ else
+ {
+ return new ClassfileSet( this );
+ }
+ }
+
+ public static class ClassRoot
+ {
+ private String rootClass;
+
+ public void setClassname( String name )
+ {
+ this.rootClass = name;
+ }
+
+ public String getClassname()
+ {
+ return rootClass;
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/types/optional/depend/DependScanner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/types/optional/depend/DependScanner.java
new file mode 100644
index 000000000..4144487dc
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/types/optional/depend/DependScanner.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types.optional.depend;
+import java.io.*;
+import java.util.*;
+import org.apache.bcel.*;
+import org.apache.bcel.classfile.*;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.util.depend.Dependencies;
+import org.apache.tools.ant.util.depend.Filter;
+
+/**
+ * An interface used to describe the actions required by any type of directory
+ * scanner.
+ *
+ * @author RT
+ */
+public class DependScanner extends DirectoryScanner
+{
+ List included = new LinkedList();
+ File baseClass;
+ File basedir;
+
+ private List rootClasses;
+
+ /**
+ * Sets the basedir for scanning. This is the directory that is scanned
+ * recursively.
+ *
+ * @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;
+ }
+
+ public void setCaseSensitive( boolean isCaseSensitive ) { }
+
+ public void setExcludes( String[] excludes ) { }
+
+ public void setIncludes( String[] includes ) { }
+
+ /**
+ * Sets the domain, where dependant classes are searched
+ *
+ * @param rootClasses The new RootClasses value
+ */
+ public void setRootClasses( List rootClasses )
+ {
+ this.rootClasses = rootClasses;
+ }
+
+ /**
+ * Gets the basedir that is used for scanning.
+ *
+ * @return the basedir that is used for scanning
+ */
+ public File getBasedir()
+ {
+ return basedir;
+ }
+
+ public String[] getExcludedDirectories()
+ {
+ return null;
+ }
+
+ public String[] getExcludedFiles()
+ {
+ return null;
+ }
+
+ public String[] getIncludedDirectories()
+ {
+ return new String[0];
+ }
+
+ /**
+ * Get the names of the class files, baseClass depends on
+ *
+ * @return the names of the files
+ */
+ public String[] getIncludedFiles()
+ {
+ int count = included.size();
+ String[] files = new String[count];
+ for( int i = 0; i < count; i++ )
+ {
+ files[i] = included.get( i ) + ".class";
+ //System.err.println(" " + files[i]);
+ }
+ return files;
+ }
+
+ public String[] getNotIncludedDirectories()
+ {
+ return null;
+ }
+
+ public String[] getNotIncludedFiles()
+ {
+ return null;
+ }
+
+ public void addDefaultExcludes() { }
+
+ /**
+ * Scans the base directory for files that baseClass depends on
+ *
+ */
+ public void scan()
+ {
+ Dependencies visitor = new Dependencies();
+
+ Set set = new TreeSet();
+
+ final String base;
+ try
+ {
+ base = basedir.getCanonicalPath() + File.separator;
+ }
+ catch( Exception e )
+ {
+ throw new IllegalArgumentException( e.getMessage() );
+ }
+
+ for( Iterator rootClassIterator = rootClasses.iterator(); rootClassIterator.hasNext(); )
+ {
+ Set newSet = new HashSet();
+ String start = ( String )rootClassIterator.next();
+ start = start.replace( '.', '/' );
+
+ newSet.add( start );
+ set.add( start );
+
+ do
+ {
+ Iterator 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 )
+ {
+ System.err.println( "exception: " + e.getMessage() );
+ }
+ }
+ 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 );
+ }
+
+ included.clear();
+ included.addAll( set );
+ }
+}
diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/DOMElementWriter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/DOMElementWriter.java
new file mode 100644
index 000000000..ca4d1b867
--- /dev/null
+++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/DOMElementWriter.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.util;
+import java.io.IOException;
+import java.io.Writer;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+/**
+ * Writes a DOM tree to a given Writer.
+ *
+ * Utility class used by {@link org.apache.tools.ant.XmlLogger XmlLogger} and
+ * org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter
+ * XMLJUnitResultFormatter}.
+ *
+ * @author The original author of XmlLogger
+ * @author Stefan Bodewig
+ * @author Stephane Bailliez
+ */
+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( "" );
+ out.write( child.getNodeName() );
+ String data = child.getNodeValue();
+ if( data != null && data.length() > 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( element.getTagName() );
+ 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:
+ *
+ * The current directory, and,
+ * 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:
+ *
+ *
+ * Other parameters are:
+ *
+ *
+ * comment, key, operation, type and value (the final four being
+ * eliminated shortly)
+ *
+ * The <entry> task must have:
+ *
+ *
+ * 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
+ *
+ * is incremental build still broken in beta-1?
+ * is Win32Icon broken?
+ * 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 ... ... [-unused ...]
+ * Parameters
+ * path File or directory to audit.
+ * search-path File or directory to search for declaration uses.
+ * Options
+ * -arguments -A Includes command line arguments from file.
+ * -classpath -cp Sets class path (also source path unless one
+ * explicitly set). Overrides METAPATH/CLASSPATH.
+ * -exit -x Exits after the first error.
+ * -fix -f Automatically fixes certain errors.
+ * -fullpath Prints full path for locations.
+ * -help -h Prints help and exits.
+ * -list -l Creates listing file for each audited file.
+ * -offsets -off Offset and length for locations.
+ * -output -o Prints output to file.
+ * -quiet -q Suppresses copyright and summary messages.
+ * -sourcepath Sets source path. Overrides SOURCEPATH.
+ * -tab -t Prints a tab character after first argument.
+ * -unused -u Finds declarations unused in search paths.
+ * -verbose -v Prints all messages.
+ * -version -V Prints version and exits.
+ */
+ //---------------------- PUBLIC METHODS ------------------------------------
+
+ /**
+ * pattern used by maudit to report the error for a file
+ */
+ /**
+ * RE does not seems to support regexp pattern with comments so i'm
+ * stripping it
+ */
+ // (?:file:)?((?#filepath).+):((?#line)\\d+)\\s*:\\s+((?#message).*)
+ final static String AUDIT_PATTERN = "(?:file:)?(.+):(\\d+)\\s*:\\s+(.*)";
+
+ protected File outFile = null;
+
+ protected Path searchPath = null;
+
+ protected boolean fix = false;
+
+ protected boolean list = false;
+
+ protected boolean unused = false;
+
+ /**
+ * default constructor
+ */
+ public MAudit()
+ {
+ super( "com.metamata.gui.rc.MAudit" );
+ }
+
+ /**
+ * handy factory to create a violation
+ *
+ * @param line Description of Parameter
+ * @param msg Description of Parameter
+ * @return Description of the Returned Value
+ */
+ final static Violation createViolation( int line, String msg )
+ {
+ Violation violation = new Violation();
+ violation.line = line;
+ violation.error = msg;
+ return violation;
+ }
+
+ public void setFix( boolean flag )
+ {
+ this.fix = flag;
+ }
+
+ public void setList( boolean flag )
+ {
+ this.list = flag;
+ }
+
+ /**
+ * set the destination file which should be an xml file
+ *
+ * @param outFile The new Tofile value
+ */
+ public void setTofile( File outFile )
+ {
+ this.outFile = outFile;
+ }
+
+ public void setUnused( boolean flag )
+ {
+ this.unused = flag;
+ }
+
+ public Path createSearchpath()
+ {
+ if( searchPath == null )
+ {
+ searchPath = new Path( project );
+ }
+ return searchPath;
+ }
+
+ protected Vector getOptions()
+ {
+ Vector options = new Vector( 512 );
+ // there is a bug in Metamata 2.0 build 37. The sourcepath argument does
+ // not work. So we will use the sourcepath prepended to classpath. (order
+ // is important since Metamata looks at .class and .java)
+ if( sourcePath != null )
+ {
+ sourcePath.append( classPath );// srcpath is prepended
+ classPath = sourcePath;
+ sourcePath = null;// prevent from using -sourcepath
+ }
+
+ // don't forget to modify the pattern if you change the options reporting
+ if( classPath != null )
+ {
+ options.addElement( "-classpath" );
+ options.addElement( classPath.toString() );
+ }
+ // suppress copyright msg when running, we will let it so that this
+ // will be the only output to the console if in xml mode
+// options.addElement("-quiet");
+ if( fix )
+ {
+ options.addElement( "-fix" );
+ }
+ options.addElement( "-fullpath" );
+
+ // generate .maudit files much more detailed than the report
+ // I don't like it very much, I think it could be interesting
+ // to get all .maudit files and include them in the XML.
+ if( list )
+ {
+ options.addElement( "-list" );
+ }
+ if( sourcePath != null )
+ {
+ options.addElement( "-sourcepath" );
+ options.addElement( sourcePath.toString() );
+ }
+
+ if( unused )
+ {
+ options.addElement( "-unused" );
+ options.addElement( searchPath.toString() );
+ }
+ addAllVector( options, includedFiles.keys() );
+ return options;
+ }
+
+ protected void checkOptions()
+ throws BuildException
+ {
+ super.checkOptions();
+ if( unused && searchPath == null )
+ {
+ throw new BuildException( "'searchpath' element must be set when looking for 'unused' declarations." );
+ }
+ if( !unused && searchPath != null )
+ {
+ log( "'searchpath' element ignored. 'unused' attribute is disabled.", Project.MSG_WARN );
+ }
+ }
+
+ protected void cleanUp()
+ throws BuildException
+ {
+ super.cleanUp();
+ // at this point if -list is used, we should move
+ // the .maudit file since we cannot choose their location :(
+ // the .maudit files match the .java files
+ // we'll use includedFiles to get the .maudit files.
+
+ /*
+ * if (out != null){
+ * / close it if not closed by the handler...
+ * }
+ */
+ }
+
+ protected ExecuteStreamHandler createStreamHandler()
+ throws BuildException
+ {
+ ExecuteStreamHandler handler = null;
+ // if we didn't specify a file, then use a screen report
+ if( outFile == null )
+ {
+ handler = new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_INFO );
+ }
+ else
+ {
+ try
+ {
+ //XXX
+ OutputStream out = new FileOutputStream( outFile );
+ handler = new MAuditStreamHandler( this, out );
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( e );
+ }
+ }
+ return handler;
+ }
+
+ /**
+ * the inner class used to report violation information
+ *
+ * @author RT
+ */
+ final static class Violation
+ {
+ String error;
+ int line;
+ }
+
+}
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java
new file mode 100644
index 000000000..1096d6700
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
+import org.apache.tools.ant.util.DOMElementWriter;
+import org.apache.tools.ant.util.regexp.RegexpMatcher;
+import org.apache.tools.ant.util.regexp.RegexpMatcherFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+
+/**
+ * This is a very bad stream handler for the MAudit task. All report to stdout
+ * that does not match a specific report pattern is dumped to the Ant output as
+ * warn level. The report that match the pattern is stored in a map with the key
+ * being the filepath that caused the error report.
+ *
+ * The limitation with the choosen implementation is clear:
+ *
+ * it does not handle multiline report( message that has \n ). the part
+ * until the \n will be stored and the other part (which will not match the
+ * pattern) will go to Ant output in Warn level.
+ * it does not report error that goes to stderr.
+ *
+ *
+ *
+ * @author Stephane Bailliez
+ */
+class MAuditStreamHandler implements ExecuteStreamHandler
+{
+
+ /**
+ * this is where the XML output will go, should mostly be a file the caller
+ * is responsible for flushing and closing this stream
+ */
+ protected OutputStream xmlOut = null;
+
+ /**
+ * the multimap. The key in the map is the filepath that caused the audit
+ * error and the value is a vector of MAudit.Violation entries.
+ */
+ protected Hashtable auditedFiles = new Hashtable();
+
+ /**
+ * reader for stdout
+ */
+ protected BufferedReader br;
+
+ /**
+ * matcher that will be used to extract the info from the line
+ */
+ protected RegexpMatcher matcher;
+
+ protected MAudit task;
+
+ MAuditStreamHandler( MAudit task, OutputStream xmlOut )
+ {
+ this.task = task;
+ this.xmlOut = xmlOut;
+ /**
+ * the matcher should be the Oro one. I don't know about the other one
+ */
+ matcher = ( new RegexpMatcherFactory() ).newRegexpMatcher();
+ matcher.setPattern( MAudit.AUDIT_PATTERN );
+ }
+
+ protected static DocumentBuilder getDocumentBuilder()
+ {
+ try
+ {
+ return DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ }
+ catch( Exception exc )
+ {
+ throw new ExceptionInInitializerError( exc );
+ }
+ }
+
+ /**
+ * 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. This will block until the end :-(
+ *
+ * @exception IOException Description of Exception
+ */
+ public void start()
+ throws IOException
+ {
+ parseOutput( br );
+ }
+
+ /**
+ * Pretty dangerous business here. It serializes what was extracted from the
+ * MAudit output and write it to the output.
+ */
+ public void stop()
+ {
+ // serialize the content as XML, move this to another method
+ // this is the only code that could be needed to be overrided
+ Document doc = getDocumentBuilder().newDocument();
+ Element rootElement = doc.createElement( "classes" );
+ Enumeration keys = auditedFiles.keys();
+ Hashtable filemapping = task.getFileMapping();
+ rootElement.setAttribute( "audited", String.valueOf( filemapping.size() ) );
+ rootElement.setAttribute( "reported", String.valueOf( auditedFiles.size() ) );
+ int errors = 0;
+ while( keys.hasMoreElements() )
+ {
+ String filepath = ( String )keys.nextElement();
+ Vector v = ( Vector )auditedFiles.get( filepath );
+ String fullclassname = ( String )filemapping.get( filepath );
+ if( fullclassname == null )
+ {
+ task.getProject().log( "Could not find class mapping for " + filepath, Project.MSG_WARN );
+ continue;
+ }
+ int pos = fullclassname.lastIndexOf( '.' );
+ String pkg = ( pos == -1 ) ? "" : fullclassname.substring( 0, pos );
+ String clazzname = ( pos == -1 ) ? fullclassname : fullclassname.substring( pos + 1 );
+ Element clazz = doc.createElement( "class" );
+ clazz.setAttribute( "package", pkg );
+ clazz.setAttribute( "name", clazzname );
+ clazz.setAttribute( "violations", String.valueOf( v.size() ) );
+ errors += v.size();
+ for( int i = 0; i < v.size(); i++ )
+ {
+ MAudit.Violation violation = ( MAudit.Violation )v.elementAt( i );
+ Element error = doc.createElement( "violation" );
+ error.setAttribute( "line", String.valueOf( violation.line ) );
+ error.setAttribute( "message", violation.error );
+ clazz.appendChild( error );
+ }
+ rootElement.appendChild( clazz );
+ }
+ rootElement.setAttribute( "violations", String.valueOf( errors ) );
+
+ // now write it to the outputstream, not very nice code
+ if( xmlOut != null )
+ {
+ Writer wri = null;
+ try
+ {
+ wri = new OutputStreamWriter( xmlOut, "UTF-8" );
+ wri.write( "\n" );
+ ( new DOMElementWriter() ).write( rootElement, wri, 0, " " );
+ wri.flush();
+ }
+ catch( IOException exc )
+ {
+ task.log( "Unable to write log file", Project.MSG_ERR );
+ }
+ finally
+ {
+ if( xmlOut != System.out && xmlOut != System.err )
+ {
+ if( wri != null )
+ {
+ try
+ {
+ wri.close();
+ }
+ catch( IOException e )
+ {}
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * add a violation entry for the file
+ *
+ * @param file The feature to be added to the ViolationEntry attribute
+ * @param entry The feature to be added to the ViolationEntry attribute
+ */
+ protected void addViolationEntry( String file, MAudit.Violation entry )
+ {
+ Vector violations = ( Vector )auditedFiles.get( file );
+ // if there is no decl for this file yet, create it.
+ if( violations == null )
+ {
+ violations = new Vector();
+ auditedFiles.put( file, violations );
+ }
+ violations.add( entry );
+ }
+
+ /**
+ * read each line and process it
+ *
+ * @param br Description of Parameter
+ * @exception IOException Description of Exception
+ */
+ protected void parseOutput( BufferedReader br )
+ throws IOException
+ {
+ String line = null;
+ while( ( line = br.readLine() ) != null )
+ {
+ processLine( line );
+ }
+ }
+
+ // we suppose here that there is only one report / line.
+ // There will obviouslly be a problem if the message is on several lines...
+ protected void processLine( String line )
+ {
+ Vector matches = matcher.getGroups( line );
+ if( matches != null )
+ {
+ String file = ( String )matches.elementAt( 1 );
+ int lineNum = Integer.parseInt( ( String )matches.elementAt( 2 ) );
+ String msg = ( String )matches.elementAt( 3 );
+ addViolationEntry( file, MAudit.createViolation( lineNum, msg ) );
+ }
+ else
+ {
+ // this doesn't match..report it as info, it could be
+ // either the copyright, summary or a multiline message (damn !)
+ task.log( line, Project.MSG_INFO );
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java
new file mode 100644
index 000000000..6a5ae42ee
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+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;
+
+/**
+ * Calculates global complexity and quality metrics on Java source code. You
+ * will not be able to use this task with the evaluation version since as of
+ * Metamata 2.0, Metrics does not support command line :-( For more information,
+ * visit the website at www.metamata.com
+ *
+ * @author Stephane Bailliez
+ */
+public class MMetrics extends AbstractMetamataTask
+{
+ /*
+ * The command line options as of Metamata 2.0 are as follows:
+ * Usage
+ * mmetrics ... ...
+ * Parameters
+ * path File or directory to measure.
+ * Options
+ * -arguments -A Includes command line arguments from file.
+ * -classpath -cp Sets class path (also source path unless one
+ * explicitly set). Overrides METAPATH/CLASSPATH.
+ * -compilation-units Measure compilation units.
+ * -files Measure compilation units.
+ * -format -f Sets output format, default output file type.
+ * -help -h Prints help and exits.
+ * -indent -i Sets string used to indent labels one level.
+ * -methods Measure methods, types, and compilation units.
+ * -output -o Sets output file name.
+ * -quiet -q Suppresses copyright message.
+ * -sourcepath Sets source path. Overrides SOURCEPATH.
+ * -types Measure types and compilation units.
+ * -verbose -v Prints all messages.
+ * -version -V Prints version and exits.
+ * Format Options
+ * comma csv Format output as comma-separated text.
+ * html htm Format output as an HTML table.
+ * tab tab-separated tsv Format output as tab-separated text.
+ * text txt Format output as space-aligned text.
+ */
+ /**
+ * the granularity mode. Should be one of 'files', 'methods' and 'types'.
+ */
+ protected String granularity = null;
+
+ /**
+ * the XML output file
+ */
+ protected File outFile = null;
+
+ /**
+ * the location of the temporary txt report
+ */
+ protected File tmpFile = createTmpFile();
+
+ protected Path path = null;
+
+ //--------------------------- PUBLIC METHODS -------------------------------
+
+ /**
+ * default constructor
+ */
+ public MMetrics()
+ {
+ super( "com.metamata.sc.MMetrics" );
+ }
+
+ /**
+ * set the granularity of the audit. Should be one of 'files', 'methods' or
+ * 'types'.
+ *
+ * @param granularity the audit reporting mode.
+ */
+ public void setGranularity( String granularity )
+ {
+ this.granularity = granularity;
+ }
+
+ /**
+ * Set the output XML file
+ *
+ * @param file the xml file to write the XML report to.
+ */
+ public void setTofile( File file )
+ {
+ this.outFile = file;
+ }
+
+ /**
+ * Set a new path (directory) to measure metrics from.
+ *
+ * @return the path instance to use.
+ */
+ public Path createPath()
+ {
+ if( path == null )
+ {
+ path = new Path( project );
+ }
+ return path;
+ }
+
+
+ protected Vector getOptions()
+ {
+ Vector options = new Vector( 512 );
+ // there is a bug in Metamata 2.0 build 37. The sourcepath argument does
+ // not work. So we will use the sourcepath prepended to classpath. (order
+ // is important since Metamata looks at .class and .java)
+ if( sourcePath != null )
+ {
+ sourcePath.append( classPath );// srcpath is prepended
+ classPath = sourcePath;
+ sourcePath = null;// prevent from using -sourcepath
+ }
+
+ // don't forget to modify the pattern if you change the options reporting
+ if( classPath != null )
+ {
+ options.addElement( "-classpath" );
+ options.addElement( classPath );
+ }
+ options.addElement( "-output" );
+ options.addElement( tmpFile.toString() );
+
+ options.addElement( "-" + granularity );
+
+ // display the metamata copyright
+ // options.addElement( "-quiet");
+ options.addElement( "-format" );
+
+ // need this because that's what the handler is using, it's
+ // way easier to process than any other separator
+ options.addElement( "tab" );
+
+ // specify a / as the indent character, used by the handler.
+ options.addElement( "-i" );
+ options.addElement( "/" );
+
+ // directories
+ String[] dirs = path.list();
+ for( int i = 0; i < dirs.length; i++ )
+ {
+ options.addElement( dirs[i] );
+ }
+ // files next.
+ addAllVector( options, includedFiles.keys() );
+ return options;
+ }
+
+ //------------------- PROTECTED / PRIVATE METHODS --------------------------
+
+
+ // check for existing options and outfile, all other are optional
+ protected void checkOptions()
+ throws BuildException
+ {
+ super.checkOptions();
+
+ if( !"files".equals( granularity ) && !"methods".equals( granularity )
+ && !"types".equals( granularity ) )
+ {
+ throw new BuildException( "Metrics reporting granularity is invalid. Must be one of 'files', 'methods', 'types'" );
+ }
+ if( outFile == null )
+ {
+ throw new BuildException( "Output XML file must be set via 'tofile' attribute." );
+ }
+ if( path == null && fileSets.size() == 0 )
+ {
+ throw new BuildException( "Must set either paths (path element) or files (fileset element)" );
+ }
+ // I don't accept dirs and files at the same time, I cannot recognize the semantic in the result
+ if( path != null && fileSets.size() > 0 )
+ {
+ throw new BuildException( "Cannot set paths (path element) and files (fileset element) at the same time" );
+ }
+ }
+
+
+ /**
+ * cleanup the temporary txt report
+ *
+ * @exception BuildException Description of Exception
+ */
+ protected void cleanUp()
+ throws BuildException
+ {
+ try
+ {
+ super.cleanUp();
+ }
+ finally
+ {
+ if( tmpFile != null )
+ {
+ tmpFile.delete();
+ tmpFile = null;
+ }
+ }
+ }
+
+ /**
+ * if the report is transform via a temporary txt file we should use a a
+ * normal logger here, otherwise we could use the metrics handler directly
+ * to capture and transform the output on stdout to XML.
+ *
+ * @return Description of the Returned Value
+ */
+ protected ExecuteStreamHandler createStreamHandler()
+ {
+ // write the report directtly to an XML stream
+ // return new MMetricsStreamHandler(this, xmlStream);
+ return new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_INFO );
+ }
+
+ protected void execute0( ExecuteStreamHandler handler )
+ throws BuildException
+ {
+ super.execute0( handler );
+ transformFile();
+ }
+
+ /**
+ * transform the generated file via the handler This function can either be
+ * called if the result is written to the output file via -output or we
+ * could use the handler directly on stdout if not.
+ *
+ * @exception BuildException Description of Exception
+ * @see #createStreamHandler()
+ */
+ protected void transformFile()
+ throws BuildException
+ {
+ FileInputStream tmpStream = null;
+ try
+ {
+ tmpStream = new FileInputStream( tmpFile );
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Error reading temporary file: " + tmpFile, e );
+ }
+ FileOutputStream xmlStream = null;
+ try
+ {
+ xmlStream = new FileOutputStream( outFile );
+ ExecuteStreamHandler xmlHandler = new MMetricsStreamHandler( this, xmlStream );
+ xmlHandler.setProcessOutputStream( tmpStream );
+ xmlHandler.start();
+ xmlHandler.stop();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Error creating output file: " + outFile, e );
+ }
+ finally
+ {
+ if( xmlStream != null )
+ {
+ try
+ {
+ xmlStream.close();
+ }
+ catch( IOException ignored )
+ {}
+ }
+ if( tmpStream != null )
+ {
+ try
+ {
+ tmpStream.close();
+ }
+ catch( IOException ignored )
+ {}
+ }
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java
new file mode 100644
index 000000000..b09fd00fc
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.EmptyStackException;
+import java.util.Enumeration;
+import java.util.Stack;
+import java.util.Vector;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * A handy metrics handler. Most of this code was done only with the screenshots
+ * on the documentation since the evaluation version as of this writing does not
+ * allow to save metrics or to run it via command line.
+ *
+ * This class can be used to transform a text file or to process the output
+ * stream directly.
+ *
+ * @author Stephane Bailliez
+ */
+public class MMetricsStreamHandler implements ExecuteStreamHandler
+{
+
+ /**
+ * CLASS construct, it should be named something like 'MyClass'
+ */
+ protected final static String CLASS = "class";
+
+ /**
+ * package construct, it should be look like 'com.mycompany.something'
+ */
+ protected final static String PACKAGE = "package";
+
+ /**
+ * FILE construct, it should look like something 'MyClass.java' or
+ * 'MyClass.class'
+ */
+ protected final static String FILE = "file";
+
+ /**
+ * METHOD construct, it should looke like something 'doSomething(...)' or
+ * 'doSomething()'
+ */
+ protected final static String METHOD = "method";
+
+ protected final static String[] ATTRIBUTES = {"name", "vg", "loc",
+ "dit", "noa", "nrm", "nlm", "wmc", "rfc", "dac", "fanout", "cbo", "lcom", "nocl"
+ };
+
+ /**
+ * the stack where are stored the metrics element so that they we can know
+ * if we have to close an element or not.
+ */
+ protected Stack stack = new Stack();
+
+ /**
+ * metrics handler
+ */
+ protected TransformerHandler metricsHandler;
+
+ /**
+ * reader for stdout
+ */
+ protected InputStream metricsOutput;
+
+ /**
+ * the task
+ */
+ protected Task task;
+
+ /**
+ * this is where the XML output will go, should mostly be a file the caller
+ * is responsible for flushing and closing this stream
+ */
+ protected OutputStream xmlOutputStream;
+
+ /**
+ * initialize this handler
+ *
+ * @param task Description of Parameter
+ * @param xmlOut Description of Parameter
+ */
+ MMetricsStreamHandler( Task task, OutputStream xmlOut )
+ {
+ this.task = task;
+ this.xmlOutputStream = xmlOut;
+ }
+
+ /**
+ * Ignore.
+ *
+ * @param p1 The new ProcessErrorStream value
+ * @exception IOException Description of Exception
+ */
+ public void setProcessErrorStream( InputStream p1 )
+ throws IOException { }
+
+ /**
+ * Ignore.
+ *
+ * @param p1 The new ProcessInputStream value
+ * @exception IOException Description of Exception
+ */
+ public void setProcessInputStream( OutputStream p1 )
+ throws IOException { }
+
+ /**
+ * Set the inputstream
+ *
+ * @param is The new ProcessOutputStream value
+ * @exception IOException Description of Exception
+ */
+ public void setProcessOutputStream( InputStream is )
+ throws IOException
+ {
+ metricsOutput = is;
+ }
+
+ public void start()
+ throws IOException
+ {
+ // create the transformer handler that will be used to serialize
+ // the output.
+ TransformerFactory factory = TransformerFactory.newInstance();
+ if( !factory.getFeature( SAXTransformerFactory.FEATURE ) )
+ {
+ throw new IllegalStateException( "Invalid Transformer factory feature" );
+ }
+ try
+ {
+ metricsHandler = ( ( SAXTransformerFactory )factory ).newTransformerHandler();
+ metricsHandler.setResult( new StreamResult( new OutputStreamWriter( xmlOutputStream, "UTF-8" ) ) );
+ Transformer transformer = metricsHandler.getTransformer();
+ transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
+
+ // start the document with a 'metrics' root
+ metricsHandler.startDocument();
+ AttributesImpl attr = new AttributesImpl();
+ attr.addAttribute( "", "company", "company", "CDATA", "metamata" );
+ metricsHandler.startElement( "", "metrics", "metrics", attr );
+
+ // now parse the whole thing
+ parseOutput();
+
+ }
+ catch( Exception e )
+ {
+ e.printStackTrace();
+ throw new IOException( e.getMessage() );
+ }
+ }
+
+ /**
+ * Pretty dangerous business here.
+ */
+ public void stop()
+ {
+ try
+ {
+ // we need to pop everything and close elements that have not been
+ // closed yet.
+ while( stack.size() > 0 )
+ {
+ ElementEntry elem = ( ElementEntry )stack.pop();
+ metricsHandler.endElement( "", elem.getType(), elem.getType() );
+ }
+ // close the root
+ metricsHandler.endElement( "", "metrics", "metrics" );
+ // document is finished for good
+ metricsHandler.endDocument();
+ }
+ catch( SAXException e )
+ {
+ e.printStackTrace();
+ throw new IllegalStateException( e.getMessage() );
+ }
+ }
+
+ /**
+ * return the construct type of the element. We can hardly recognize the
+ * type of a metrics element, so we are kind of forced to do some black
+ * magic based on the name and indentation to recognize the type.
+ *
+ * @param elem the metrics element to guess for its type.
+ * @return the type of the metrics element, either PACKAGE, FILE, CLASS or
+ * METHOD.
+ */
+ protected String getConstructType( MetricsElement elem )
+ {
+ // ok no doubt, it's a file
+ if( elem.isCompilationUnit() )
+ {
+ return FILE;
+ }
+
+ // same, we're sure it's a method
+ if( elem.isMethod() )
+ {
+ return METHOD;
+ }
+
+ // if it's empty, and none of the above it should be a package
+ if( stack.size() == 0 )
+ {
+ return PACKAGE;
+ }
+
+ // ok, this is now black magic time, we will guess the type based on
+ // the previous type and its indent...
+ final ElementEntry previous = ( ElementEntry )stack.peek();
+ final String prevType = previous.getType();
+ final int prevIndent = previous.getIndent();
+ final int indent = elem.getIndent();
+ // we're just under a file with a bigger indent so it's a class
+ if( prevType.equals( FILE ) && indent > prevIndent )
+ {
+ return CLASS;
+ }
+
+ // we're just under a class with a greater or equals indent, it's a class
+ // (there might be several classes in a compilation unit and inner classes as well)
+ if( prevType.equals( CLASS ) && indent >= prevIndent )
+ {
+ return CLASS;
+ }
+
+ // we assume the other are package
+ return PACKAGE;
+ }
+
+
+ /**
+ * Create all attributes of a MetricsElement skipping those who have an
+ * empty string
+ *
+ * @param elem
+ * @return Description of the Returned Value
+ */
+ protected Attributes createAttributes( MetricsElement elem )
+ {
+ AttributesImpl impl = new AttributesImpl();
+ int i = 0;
+ String name = ATTRIBUTES[i++];
+ impl.addAttribute( "", name, name, "CDATA", elem.getName() );
+ Enumeration metrics = elem.getMetrics();
+ for( ; metrics.hasMoreElements(); i++ )
+ {
+ String value = ( String )metrics.nextElement();
+ if( value.length() > 0 )
+ {
+ name = ATTRIBUTES[i];
+ impl.addAttribute( "", name, name, "CDATA", value );
+ }
+ }
+ return impl;
+ }
+
+ /**
+ * read each line and process it
+ *
+ * @exception IOException Description of Exception
+ * @exception SAXException Description of Exception
+ */
+ protected void parseOutput()
+ throws IOException, SAXException
+ {
+ BufferedReader br = new BufferedReader( new InputStreamReader( metricsOutput ) );
+ String line = null;
+ while( ( line = br.readLine() ) != null )
+ {
+ processLine( line );
+ }
+ }
+
+ /**
+ * Process a metrics line. If the metrics is invalid and that this is not
+ * the header line, it is display as info.
+ *
+ * @param line the line to process, it is normally a line full of metrics.
+ * @exception SAXException Description of Exception
+ */
+ protected void processLine( String line )
+ throws SAXException
+ {
+ if( line.startsWith( "Construct\tV(G)\tLOC\tDIT\tNOA\tNRM\tNLM\tWMC\tRFC\tDAC\tFANOUT\tCBO\tLCOM\tNOCL" ) )
+ {
+ return;
+ }
+ try
+ {
+ MetricsElement elem = MetricsElement.parse( line );
+ startElement( elem );
+ }
+ catch( ParseException e )
+ {
+ e.printStackTrace();
+ // invalid lines are sent to the output as information, it might be anything,
+ task.log( line, Project.MSG_INFO );
+ }
+ }
+
+ /**
+ * Start a new construct. Elements are popped until we are on the same
+ * parent node, then the element type is guessed and pushed on the stack.
+ *
+ * @param elem the element to process.
+ * @throws SAXException thrown if there is a problem when sending SAX
+ * events.
+ */
+ protected void startElement( MetricsElement elem )
+ throws SAXException
+ {
+ // if there are elements in the stack we possibly need to close one or
+ // more elements previous to this one until we got its parent
+ int indent = elem.getIndent();
+ if( stack.size() > 0 )
+ {
+ ElementEntry previous = ( ElementEntry )stack.peek();
+ // close nodes until you got the parent.
+ try
+ {
+ while( indent <= previous.getIndent() && stack.size() > 0 )
+ {
+ stack.pop();
+ metricsHandler.endElement( "", previous.getType(), previous.getType() );
+ previous = ( ElementEntry )stack.peek();
+ }
+ }
+ catch( EmptyStackException ignored )
+ {}
+ }
+
+ // ok, now start the new construct
+ String type = getConstructType( elem );
+ Attributes attrs = createAttributes( elem );
+ metricsHandler.startElement( "", type, type, attrs );
+
+ // make sure we keep track of what we did, that's history
+ stack.push( new ElementEntry( type, indent ) );
+ }
+
+ /**
+ * helper class to keep track of elements via its type and indent that's all
+ * we need to guess a type.
+ *
+ * @author RT
+ */
+ private final static class ElementEntry
+ {
+ private int indent;
+ private String type;
+
+ ElementEntry( String type, int indent )
+ {
+ this.type = type;
+ this.indent = indent;
+ }
+
+ public int getIndent()
+ {
+ return indent;
+ }
+
+ public String getType()
+ {
+ return type;
+ }
+ }
+}
+
+class MetricsElement
+{
+
+ private final static NumberFormat METAMATA_NF;
+
+ private final static NumberFormat NEUTRAL_NF;
+
+ private String construct;
+
+ private int indent;
+
+ private Vector metrics;
+ static
+ {
+ METAMATA_NF = NumberFormat.getInstance();
+ METAMATA_NF.setMaximumFractionDigits( 1 );
+ NEUTRAL_NF = NumberFormat.getInstance();
+ if( NEUTRAL_NF instanceof DecimalFormat )
+ {
+ ( ( DecimalFormat )NEUTRAL_NF ).applyPattern( "###0.###;-###0.###" );
+ }
+ NEUTRAL_NF.setMaximumFractionDigits( 1 );
+ }
+
+ MetricsElement( int indent, String construct, Vector metrics )
+ {
+ this.indent = indent;
+ this.construct = construct;
+ this.metrics = metrics;
+ }
+
+ public static MetricsElement parse( String line )
+ throws ParseException
+ {
+ final Vector metrics = new Vector();
+ int pos;
+
+ // i'm using indexOf since I need to know if there are empty strings
+ // between tabs and I find it easier than with StringTokenizer
+ while( ( pos = line.indexOf( '\t' ) ) != -1 )
+ {
+ String token = line.substring( 0, pos );
+ // only parse what coudl be a valid number. ie not constructs nor no value
+ /*
+ * if (metrics.size() != 0 || token.length() != 0){
+ * Number num = METAMATA_NF.parse(token); // parse with Metamata NF
+ * token = NEUTRAL_NF.format(num.doubleValue()); // and format with a neutral NF
+ * }
+ */
+ metrics.addElement( token );
+ line = line.substring( pos + 1 );
+ }
+ metrics.addElement( line );
+
+ // there should be exactly 14 tokens (1 name + 13 metrics), if not, there is a problem !
+ if( metrics.size() != 14 )
+ {
+ throw new ParseException( "Could not parse the following line as a metrics: -->" + line + "<--", -1 );
+ }
+
+ // remove the first token it's made of the indentation string and the
+ // construct name, we'll need all this to figure out what type of
+ // construct it is since we lost all semantics :(
+ // (#indent[/]*)(#construct.*)
+ String name = ( String )metrics.elementAt( 0 );
+ metrics.removeElementAt( 0 );
+ int indent = 0;
+ pos = name.lastIndexOf( '/' );
+ if( pos != -1 )
+ {
+ name = name.substring( pos + 1 );
+ indent = pos + 1;// indentation is last position of token + 1
+ }
+ return new MetricsElement( indent, name, metrics );
+ }
+
+ public int getIndent()
+ {
+ return indent;
+ }
+
+ public Enumeration getMetrics()
+ {
+ return metrics.elements();
+ }
+
+ public String getName()
+ {
+ return construct;
+ }
+
+ public boolean isCompilationUnit()
+ {
+ return ( construct.endsWith( ".java" ) || construct.endsWith( ".class" ) );
+ }
+
+ public boolean isMethod()
+ {
+ return ( construct.endsWith( "(...)" ) || construct.endsWith( "()" ) );
+ }
+}
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MParse.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MParse.java
new file mode 100644
index 000000000..efbc76c0a
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MParse.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.Random;
+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.taskdefs.Execute;
+import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
+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;
+
+/**
+ * Simple Metamata MParse task based on the original written by Thomas Haas This version was
+ * written for Metamata 2.0 available at
+ * http://www.metamata.com
+ *
+ * @author Stephane Bailliez
+ */
+public class MParse extends Task
+{
+
+ private Path classpath = null;
+ private Path sourcepath = null;
+ private File metahome = null;
+ private File target = null;
+ private boolean verbose = false;
+ private boolean debugparser = false;
+ private boolean debugscanner = false;
+ private boolean cleanup = false;
+ private CommandlineJava cmdl = new CommandlineJava();
+ private File optionsFile = null;
+
+ public MParse()
+ {
+ cmdl.setVm( "java" );
+ cmdl.setClassname( "com.metamata.jj.MParse" );
+ }
+
+ /**
+ * create a temporary file in the current directory
+ *
+ * @return Description of the Returned Value
+ */
+ 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;
+ }
+
+ /**
+ * set the hack to cleanup the temp file
+ *
+ * @param value The new Cleanup value
+ */
+ public void setCleanup( boolean value )
+ {
+ cleanup = value;
+ }
+
+ /**
+ * set parser debug mode
+ *
+ * @param flag The new Debugparser value
+ */
+ public void setDebugparser( boolean flag )
+ {
+ debugparser = flag;
+ }
+
+ /**
+ * set scanner debug mode
+ *
+ * @param flag The new Debugscanner value
+ */
+ public void setDebugscanner( boolean flag )
+ {
+ debugscanner = flag;
+ }
+
+ /**
+ * -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 );
+ }
+ }
+
+ /**
+ * location of metamata dev environment
+ *
+ * @param metamatahome The new Metamatahome value
+ */
+ public void setMetamatahome( File metamatahome )
+ {
+ this.metahome = metamatahome;
+ }
+
+ /**
+ * the .jj file to process
+ *
+ * @param target The new Target value
+ */
+ public void setTarget( File target )
+ {
+ this.target = target;
+ }
+
+ /**
+ * set verbose mode
+ *
+ * @param flag The new Verbose value
+ */
+ public void setVerbose( boolean flag )
+ {
+ verbose = flag;
+ }
+
+ /**
+ * create a classpath entry
+ *
+ * @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();
+ }
+
+ /**
+ * creates a sourcepath entry
+ *
+ * @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();
+ _execute( handler );
+ }
+ finally
+ {
+ cleanUp();
+ }
+ }
+
+ /**
+ * 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 files
+ File[] jars = getMetamataLibs();
+ final Path classPath = cmdl.createClasspath( project );
+ for( int i = 0; i < jars.length; i++ )
+ {
+ classPath.createPathElement().setLocation( jars[i] );
+ }
+
+ // set the metamata.home property
+ final Commandline.Argument vmArgs = cmdl.createVmArgument();
+ vmArgs.setValue( "-Dmetamata.home=" + metahome.getAbsolutePath() );
+
+ // write all the options to a temp file and use it ro run the process
+ String[] options = getOptions();
+ optionsFile = createTmpFile();
+ generateOptionsFile( optionsFile, options );
+ Commandline.Argument args = cmdl.createArgument();
+ args.setLine( "-arguments " + optionsFile.getAbsolutePath() );
+ }
+
+ /**
+ * return an array of files containing the path to the needed libraries to
+ * run metamata. The file are not checked for existence. You should do this
+ * yourself if needed or simply let the forked process do it for you.
+ *
+ * @return array of jars/zips needed to run metamata.
+ */
+ protected File[] getMetamataLibs()
+ {
+ Vector files = new Vector();
+ files.addElement( new File( metahome, "lib/metamata.jar" ) );
+ files.addElement( new File( metahome, "bin/lib/JavaCC.zip" ) );
+
+ File[] array = new File[files.size()];
+ files.copyInto( array );
+ return array;
+ }
+
+ /**
+ * return all options of the command line as string elements
+ *
+ * @return The Options value
+ */
+ protected String[] getOptions()
+ {
+ Vector options = new Vector();
+ if( verbose )
+ {
+ options.addElement( "-verbose" );
+ }
+ if( debugscanner )
+ {
+ options.addElement( "-ds" );
+ }
+ if( debugparser )
+ {
+ options.addElement( "-dp" );
+ }
+ if( classpath != null )
+ {
+ options.addElement( "-classpath" );
+ options.addElement( classpath.toString() );
+ }
+ if( sourcepath != null )
+ {
+ options.addElement( "-sourcepath" );
+ options.addElement( sourcepath.toString() );
+ }
+ options.addElement( target.getAbsolutePath() );
+
+ String[] array = new String[options.size()];
+ options.copyInto( array );
+ return array;
+ }
+
+
+ /**
+ * execute the process with a specific handler
+ *
+ * @param handler Description of Parameter
+ * @exception BuildException Description of Exception
+ */
+ protected void _execute( ExecuteStreamHandler handler )
+ throws BuildException
+ {
+ // target has been checked as a .jj, see if there is a matching
+ // java file and if it is needed to run to process the grammar
+ String pathname = target.getAbsolutePath();
+ int pos = pathname.length() - ".jj".length();
+ pathname = pathname.substring( 0, pos ) + ".java";
+ File javaFile = new File( pathname );
+ if( javaFile.exists() && target.lastModified() < javaFile.lastModified() )
+ {
+ project.log( "Target is already build - skipping (" + target + ")" );
+ return;
+ }
+
+ 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 );
+ }
+ }
+
+
+ /**
+ * validate options set and resolve files and paths
+ *
+ * @throws BuildException thrown if an option has an incorrect state.
+ */
+ protected void checkOptions()
+ throws BuildException
+ {
+ // check that the home is ok.
+ if( metahome == null || !metahome.exists() )
+ {
+ throw new BuildException( "'metamatahome' must point to Metamata home directory." );
+ }
+ metahome = project.resolveFile( metahome.getPath() );
+
+ // check that the needed jar exists.
+ File[] jars = getMetamataLibs();
+ for( int i = 0; i < jars.length; i++ )
+ {
+ if( !jars[i].exists() )
+ {
+ throw new BuildException( jars[i] + " does not exist. Check your metamata installation." );
+ }
+ }
+
+ // check that the target is ok and resolve it.
+ if( target == null || !target.isFile() || !target.getName().endsWith( ".jj" ) )
+ {
+ throw new BuildException( "Invalid target: " + target );
+ }
+ target = project.resolveFile( target.getPath() );
+ }
+
+ /**
+ * clean up all the mess that we did with temporary objects
+ */
+ protected void cleanUp()
+ {
+ if( optionsFile != null )
+ {
+ optionsFile.delete();
+ optionsFile = null;
+ }
+ if( cleanup )
+ {
+ String name = target.getName();
+ int pos = name.length() - ".jj".length();
+ name = "__jj" + name.substring( 0, pos ) + ".sunjj";
+ final File sunjj = new File( target.getParent(), name );
+ if( sunjj.exists() )
+ {
+ project.log( "Removing stale file: " + sunjj.getName() );
+ sunjj.delete();
+ }
+ }
+ }
+
+ /**
+ * return the default stream handler for this task
+ *
+ * @return Description of the Returned Value
+ */
+ protected ExecuteStreamHandler createStreamHandler()
+ {
+ return new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_INFO );
+ }
+
+ /**
+ * write all options to a file with one option / line
+ *
+ * @param tofile the file to write the options to.
+ * @param options the array of options element to write to the file.
+ * @throws BuildException thrown if there is a problem while writing to the
+ * file.
+ */
+ protected void generateOptionsFile( File tofile, String[] options )
+ throws BuildException
+ {
+ FileWriter fw = null;
+ try
+ {
+ fw = new FileWriter( tofile );
+ PrintWriter pw = new PrintWriter( fw );
+ for( int i = 0; i < options.length; i++ )
+ {
+ pw.println( options[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 )
+ {}
+ }
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/FTP.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/FTP.java
new file mode 100644
index 000000000..5d6ba1f1c
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/FTP.java
@@ -0,0 +1,1106 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.net;
+import com.oroinc.net.ftp.FTPClient;
+import com.oroinc.net.ftp.FTPFile;
+import com.oroinc.net.ftp.FTPReply;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+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.FileScanner;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+import org.apache.tools.ant.types.FileSet;
+
+/**
+ * Basic FTP client that performs the following actions:
+ *
+ * send - send files to a remote server. This is the
+ * default action.
+ * get - retrive files from a remote server.
+ * del - delete files from a remote server.
+ * list - create a file listing.
+ *
+ * Note: Some FTP servers - notably the Solaris server - seem
+ * to hold data ports open after a "retr" operation, allowing them to timeout
+ * instead of shutting them down cleanly. This happens in active or passive
+ * mode, and the ports will remain open even after ending the FTP session. FTP
+ * "send" operations seem to close ports immediately. This behavior may cause
+ * problems on some systems when downloading large sets of files.
+ *
+ * @author Roger Vaughn
+ * rvaughn@seaconinc.com
+ * @author Glenn McAllister glennm@ca.ibm.com
+ *
+ * @author Magesh Umasankar
+ */
+public class FTP
+ extends Task
+{
+ protected final static int SEND_FILES = 0;
+ protected final static int GET_FILES = 1;
+ protected final static int DEL_FILES = 2;
+ protected final static int LIST_FILES = 3;
+ protected final static int MK_DIR = 4;
+
+ protected final static String[] ACTION_STRS = {
+ "sending",
+ "getting",
+ "deleting",
+ "listing",
+ "making directory"
+ };
+
+ protected final static String[] COMPLETED_ACTION_STRS = {
+ "sent",
+ "retrieved",
+ "deleted",
+ "listed",
+ "created directory"
+ };
+ private boolean binary = true;
+ private boolean passive = false;
+ private boolean verbose = false;
+ private boolean newerOnly = false;
+ private int action = SEND_FILES;
+ private Vector filesets = new Vector();
+ private Vector dirCache = new Vector();
+ private int transferred = 0;
+ private String remoteFileSep = "/";
+ private int port = 21;
+ private boolean skipFailedTransfers = false;
+ private int skipped = 0;
+ private boolean ignoreNoncriticalErrors = false;
+ private File listing;
+ private String password;
+
+ private String remotedir;
+ private String server;
+ private String userid;
+
+ /**
+ * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
+ * "mkdir" and "list".
+ *
+ * @param action The new Action value
+ * @exception BuildException Description of Exception
+ * @deprecated setAction(String) is deprecated and is replaced with
+ * setAction(FTP.Action) to make Ant's Introspection mechanism do the
+ * work and also to encapsulate operations on the type in its own
+ * class.
+ */
+ public void setAction( String action )
+ throws BuildException
+ {
+ log( "DEPRECATED - The setAction(String) method has been deprecated."
+ + " Use setAction(FTP.Action) instead." );
+ Action a = new Action();
+ a.setValue( action );
+ this.action = a.getAction();
+ }
+
+ /**
+ * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
+ * "mkdir" and "list".
+ *
+ * @param action The new Action value
+ * @exception BuildException Description of Exception
+ */
+ public void setAction( Action action )
+ throws BuildException
+ {
+ this.action = action.getAction();
+ }
+
+ /**
+ * Specifies whether to use binary-mode or text-mode transfers. Set to true
+ * to send binary mode. Binary mode is enabled by default.
+ *
+ * @param binary The new Binary value
+ */
+ public void setBinary( boolean binary )
+ {
+ this.binary = binary;
+ }
+
+ /**
+ * A synonym for setNewer. Set to true to transmit only new or changed
+ * files.
+ *
+ * @param depends The new Depends value
+ */
+ public void setDepends( boolean depends )
+ {
+ this.newerOnly = depends;
+ }
+
+ /**
+ * set the flag to skip errors on dir creation (and maybe later other server
+ * specific errors)
+ *
+ * @param ignoreNoncriticalErrors The new IgnoreNoncriticalErrors value
+ */
+ public void setIgnoreNoncriticalErrors( boolean ignoreNoncriticalErrors )
+ {
+ this.ignoreNoncriticalErrors = ignoreNoncriticalErrors;
+ }
+
+ /**
+ * The output file for the "list" action. This attribute is ignored for any
+ * other actions.
+ *
+ * @param listing The new Listing value
+ * @exception BuildException Description of Exception
+ */
+ public void setListing( File listing )
+ throws BuildException
+ {
+ this.listing = listing;
+ }
+
+ /**
+ * Set to true to transmit only files that are new or changed from their
+ * remote counterparts. The default is to transmit all files.
+ *
+ * @param newer The new Newer value
+ */
+ public void setNewer( boolean newer )
+ {
+ this.newerOnly = newer;
+ }
+
+ /**
+ * Specifies whether to use passive mode. Set to true if you are behind a
+ * firewall and cannot connect without it. Passive mode is disabled by
+ * default.
+ *
+ * @param passive The new Passive value
+ */
+ public void setPassive( boolean passive )
+ {
+ this.passive = passive;
+ }
+
+ /**
+ * Sets the login password for the given user id.
+ *
+ * @param password The new Password value
+ */
+ public void setPassword( String password )
+ {
+ this.password = password;
+ }
+
+ /**
+ * Sets the FTP port used by the remote server.
+ *
+ * @param port The new Port value
+ */
+ public void setPort( int port )
+ {
+ this.port = port;
+ }
+
+ /**
+ * Sets the remote directory where files will be placed. This may be a
+ * relative or absolute path, and must be in the path syntax expected by the
+ * remote server. No correction of path syntax will be performed.
+ *
+ * @param dir The new Remotedir value
+ */
+ public void setRemotedir( String dir )
+ {
+ this.remotedir = dir;
+ }
+
+ /**
+ * Sets the remote file separator character. This normally defaults to the
+ * Unix standard forward slash, but can be manually overridden using this
+ * call if the remote server requires some other separator. Only the first
+ * character of the string is used.
+ *
+ * @param separator The new Separator value
+ */
+ public void setSeparator( String separator )
+ {
+ remoteFileSep = separator;
+ }
+
+ /**
+ * Sets the FTP server to send files to.
+ *
+ * @param server The new Server value
+ */
+ public void setServer( String server )
+ {
+ this.server = server;
+ }
+
+
+ /**
+ * set the failed transfer flag
+ *
+ * @param skipFailedTransfers The new SkipFailedTransfers value
+ */
+ public void setSkipFailedTransfers( boolean skipFailedTransfers )
+ {
+ this.skipFailedTransfers = skipFailedTransfers;
+ }
+
+ /**
+ * Sets the login user id to use on the specified server.
+ *
+ * @param userid The new Userid value
+ */
+ public void setUserid( String userid )
+ {
+ this.userid = userid;
+ }
+
+ /**
+ * Set to true to receive notification about each file as it is transferred.
+ *
+ * @param verbose The new Verbose value
+ */
+ public void setVerbose( 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( FileSet set )
+ {
+ filesets.addElement( set );
+ }
+
+ /**
+ * Runs the task.
+ *
+ * @exception BuildException Description of Exception
+ */
+ public void execute()
+ throws BuildException
+ {
+ checkConfiguration();
+
+ FTPClient ftp = null;
+
+ try
+ {
+ log( "Opening FTP connection to " + server, Project.MSG_VERBOSE );
+
+ ftp = new FTPClient();
+
+ ftp.connect( server, port );
+ if( !FTPReply.isPositiveCompletion( ftp.getReplyCode() ) )
+ {
+ throw new BuildException( "FTP connection failed: " + ftp.getReplyString() );
+ }
+
+ log( "connected", Project.MSG_VERBOSE );
+ log( "logging in to FTP server", Project.MSG_VERBOSE );
+
+ if( !ftp.login( userid, password ) )
+ {
+ throw new BuildException( "Could not login to FTP server" );
+ }
+
+ log( "login succeeded", Project.MSG_VERBOSE );
+
+ if( binary )
+ {
+ ftp.setFileType( com.oroinc.net.ftp.FTP.IMAGE_FILE_TYPE );
+ if( !FTPReply.isPositiveCompletion( ftp.getReplyCode() ) )
+ {
+ throw new BuildException(
+ "could not set transfer type: " +
+ ftp.getReplyString() );
+ }
+ }
+
+ if( passive )
+ {
+ log( "entering passive mode", Project.MSG_VERBOSE );
+ ftp.enterLocalPassiveMode();
+ if( !FTPReply.isPositiveCompletion( ftp.getReplyCode() ) )
+ {
+ throw new BuildException(
+ "could not enter into passive mode: " +
+ ftp.getReplyString() );
+ }
+ }
+
+ // If the action is MK_DIR, then the specified remote directory is the
+ // directory to create.
+
+ if( action == MK_DIR )
+ {
+
+ makeRemoteDir( ftp, remotedir );
+
+ }
+ else
+ {
+ if( remotedir != null )
+ {
+ log( "changing the remote directory", Project.MSG_VERBOSE );
+ ftp.changeWorkingDirectory( remotedir );
+ if( !FTPReply.isPositiveCompletion( ftp.getReplyCode() ) )
+ {
+ throw new BuildException(
+ "could not change remote directory: " +
+ ftp.getReplyString() );
+ }
+ }
+ log( ACTION_STRS[action] + " files" );
+ transferFiles( ftp );
+ }
+
+ }
+ catch( IOException ex )
+ {
+ throw new BuildException( "error during FTP transfer: " + ex );
+ }
+ finally
+ {
+ if( ftp != null && ftp.isConnected() )
+ {
+ try
+ {
+ log( "disconnecting", Project.MSG_VERBOSE );
+ ftp.logout();
+ ftp.disconnect();
+ }
+ catch( IOException ex )
+ {
+ // ignore it
+ }
+ }
+ }
+ }
+
+ /**
+ * Retrieve a single file to the remote host. filename may
+ * contain a relative path specification. The file will then be retreived
+ * using the entire relative path spec - no attempt is made to change
+ * directories. It is anticipated that this may eventually cause problems
+ * with some FTP servers, but it simplifies the coding.
+ *
+ * @param ftp Description of Parameter
+ * @param dir Description of Parameter
+ * @param filename Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void getFile( FTPClient ftp, String dir, String filename )
+ throws IOException, BuildException
+ {
+ OutputStream outstream = null;
+ try
+ {
+ File file = project.resolveFile( new File( dir, filename ).getPath() );
+
+ if( newerOnly && isUpToDate( ftp, file, resolveFile( filename ) ) )
+ return;
+
+ if( verbose )
+ {
+ log( "transferring " + filename + " to " + file.getAbsolutePath() );
+ }
+
+ File pdir = new File( file.getParent() );// stay 1.1 compatible
+ if( !pdir.exists() )
+ {
+ pdir.mkdirs();
+ }
+ outstream = new BufferedOutputStream( new FileOutputStream( file ) );
+ ftp.retrieveFile( resolveFile( filename ), outstream );
+
+ if( !FTPReply.isPositiveCompletion( ftp.getReplyCode() ) )
+ {
+ String s = "could not get file: " + ftp.getReplyString();
+ if( skipFailedTransfers == true )
+ {
+ log( s, Project.MSG_WARN );
+ skipped++;
+ }
+ else
+ {
+ throw new BuildException( s );
+ }
+
+ }
+ else
+ {
+ log( "File " + file.getAbsolutePath() + " copied from " + server,
+ Project.MSG_VERBOSE );
+ transferred++;
+ }
+ }
+ finally
+ {
+ if( outstream != null )
+ {
+ try
+ {
+ outstream.close();
+ }
+ catch( IOException ex )
+ {
+ // ignore it
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks to see if the remote file is current as compared with the local
+ * file. Returns true if the remote file is up to date.
+ *
+ * @param ftp Description of Parameter
+ * @param localFile Description of Parameter
+ * @param remoteFile Description of Parameter
+ * @return The UpToDate value
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected boolean isUpToDate( FTPClient ftp, File localFile, String remoteFile )
+ throws IOException, BuildException
+ {
+ log( "checking date for " + remoteFile, Project.MSG_VERBOSE );
+
+ FTPFile[] files = ftp.listFiles( remoteFile );
+
+ // For Microsoft's Ftp-Service an Array with length 0 is
+ // returned if configured to return listings in "MS-DOS"-Format
+ if( files == null || files.length == 0 )
+ {
+ // If we are sending files, then assume out of date.
+ // If we are getting files, then throw an error
+
+ if( action == SEND_FILES )
+ {
+ log( "Could not date test remote file: " + remoteFile
+ + "assuming out of date.", Project.MSG_VERBOSE );
+ return false;
+ }
+ else
+ {
+ throw new BuildException( "could not date test remote file: " +
+ ftp.getReplyString() );
+ }
+ }
+
+ long remoteTimestamp = files[0].getTimestamp().getTime().getTime();
+ long localTimestamp = localFile.lastModified();
+ if( this.action == SEND_FILES )
+ {
+ return remoteTimestamp > localTimestamp;
+ }
+ else
+ {
+ return localTimestamp > remoteTimestamp;
+ }
+ }
+
+ /**
+ * Checks to see that all required parameters are set.
+ *
+ * @exception BuildException Description of Exception
+ */
+ protected void checkConfiguration()
+ throws BuildException
+ {
+ if( server == null )
+ {
+ throw new BuildException( "server attribute must be set!" );
+ }
+ if( userid == null )
+ {
+ throw new BuildException( "userid attribute must be set!" );
+ }
+ if( password == null )
+ {
+ throw new BuildException( "password attribute must be set!" );
+ }
+
+ if( ( action == LIST_FILES ) && ( listing == null ) )
+ {
+ throw new BuildException( "listing attribute must be set for list action!" );
+ }
+
+ if( action == MK_DIR && remotedir == null )
+ {
+ throw new BuildException( "remotedir attribute must be set for mkdir action!" );
+ }
+ }
+
+ /**
+ * Creates all parent directories specified in a complete relative pathname.
+ * Attempts to create existing directories will not cause errors.
+ *
+ * @param ftp Description of Parameter
+ * @param filename Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void createParents( FTPClient ftp, String filename )
+ throws IOException, BuildException
+ {
+ Vector parents = new Vector();
+ File dir = new File( filename );
+ String dirname;
+
+ while( ( dirname = dir.getParent() ) != null )
+ {
+ dir = new File( dirname );
+ parents.addElement( dir );
+ }
+
+ for( int i = parents.size() - 1; i >= 0; i-- )
+ {
+ dir = ( File )parents.elementAt( i );
+ if( !dirCache.contains( dir ) )
+ {
+ log( "creating remote directory " + resolveFile( dir.getPath() ),
+ Project.MSG_VERBOSE );
+ ftp.makeDirectory( resolveFile( dir.getPath() ) );
+ // Both codes 550 and 553 can be produced by FTP Servers
+ // to indicate that an attempt to create a directory has
+ // failed because the directory already exists.
+ int result = ftp.getReplyCode();
+ if( !FTPReply.isPositiveCompletion( result ) &&
+ ( result != 550 ) && ( result != 553 ) &&
+ !ignoreNoncriticalErrors )
+ {
+ throw new BuildException(
+ "could not create directory: " +
+ ftp.getReplyString() );
+ }
+ dirCache.addElement( dir );
+ }
+ }
+ }
+
+ /**
+ * Delete a file from the remote host.
+ *
+ * @param ftp Description of Parameter
+ * @param filename Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void delFile( FTPClient ftp, String filename )
+ throws IOException, BuildException
+ {
+ if( verbose )
+ {
+ log( "deleting " + filename );
+ }
+
+ if( !ftp.deleteFile( resolveFile( filename ) ) )
+ {
+ String s = "could not delete file: " + ftp.getReplyString();
+ if( skipFailedTransfers == true )
+ {
+ log( s, Project.MSG_WARN );
+ skipped++;
+ }
+ else
+ {
+ throw new BuildException( s );
+ }
+ }
+ else
+ {
+ log( "File " + filename + " deleted from " + server, Project.MSG_VERBOSE );
+ transferred++;
+ }
+ }
+
+ /**
+ * List information about a single file from the remote host. filename
+ * may contain a relative path specification. The file listing will then be
+ * retrieved using the entire relative path spec - no attempt is made to
+ * change directories. It is anticipated that this may eventually cause
+ * problems with some FTP servers, but it simplifies the coding.
+ *
+ * @param ftp Description of Parameter
+ * @param bw Description of Parameter
+ * @param filename Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void listFile( FTPClient ftp, BufferedWriter bw, String filename )
+ throws IOException, BuildException
+ {
+ if( verbose )
+ {
+ log( "listing " + filename );
+ }
+
+ FTPFile ftpfile = ftp.listFiles( resolveFile( filename ) )[0];
+ bw.write( ftpfile.toString() );
+ bw.newLine();
+
+ transferred++;
+ }
+
+ /**
+ * Create the specified directory on the remote host.
+ *
+ * @param ftp The FTP client connection
+ * @param dir The directory to create (format must be correct for host type)
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void makeRemoteDir( FTPClient ftp, String dir )
+ throws IOException, BuildException
+ {
+ if( verbose )
+ {
+ log( "creating directory: " + dir );
+ }
+
+ if( !ftp.makeDirectory( dir ) )
+ {
+ // codes 521, 550 and 553 can be produced by FTP Servers
+ // to indicate that an attempt to create a directory has
+ // failed because the directory already exists.
+
+ int rc = ftp.getReplyCode();
+ if( !( ignoreNoncriticalErrors && ( rc == 550 || rc == 553 || rc == 521 ) ) )
+ {
+ throw new BuildException( "could not create directory: " +
+ ftp.getReplyString() );
+ }
+
+ if( verbose )
+ {
+ log( "directory already exists" );
+ }
+ }
+ else
+ {
+ if( verbose )
+ {
+ log( "directory created OK" );
+ }
+ }
+ }
+
+ /**
+ * Correct a file path to correspond to the remote host requirements. This
+ * implementation currently assumes that the remote end can handle
+ * Unix-style paths with forward-slash separators. This can be overridden
+ * with the separator task parameter. No attempt is made to
+ * determine what syntax is appropriate for the remote host.
+ *
+ * @param file Description of Parameter
+ * @return Description of the Returned Value
+ */
+ protected String resolveFile( String file )
+ {
+ return file.replace( System.getProperty( "file.separator" ).charAt( 0 ),
+ remoteFileSep.charAt( 0 ) );
+ }
+
+ /**
+ * Sends a single file to the remote host. filename may contain
+ * a relative path specification. When this is the case, sendFile
+ * will attempt to create any necessary parent directories before sending
+ * the file. The file will then be sent using the entire relative path spec
+ * - no attempt is made to change directories. It is anticipated that this
+ * may eventually cause problems with some FTP servers, but it simplifies
+ * the coding.
+ *
+ * @param ftp Description of Parameter
+ * @param dir Description of Parameter
+ * @param filename Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void sendFile( FTPClient ftp, String dir, String filename )
+ throws IOException, BuildException
+ {
+ InputStream instream = null;
+ try
+ {
+ File file = project.resolveFile( new File( dir, filename ).getPath() );
+
+ if( newerOnly && isUpToDate( ftp, file, resolveFile( filename ) ) )
+ return;
+
+ if( verbose )
+ {
+ log( "transferring " + file.getAbsolutePath() );
+ }
+
+ instream = new BufferedInputStream( new FileInputStream( file ) );
+
+ createParents( ftp, filename );
+
+ ftp.storeFile( resolveFile( filename ), instream );
+ boolean success = FTPReply.isPositiveCompletion( ftp.getReplyCode() );
+ if( !success )
+ {
+ String s = "could not put file: " + ftp.getReplyString();
+ if( skipFailedTransfers == true )
+ {
+ log( s, Project.MSG_WARN );
+ skipped++;
+ }
+ else
+ {
+ throw new BuildException( s );
+ }
+
+ }
+ else
+ {
+
+ log( "File " + file.getAbsolutePath() +
+ " copied to " + server,
+ Project.MSG_VERBOSE );
+ transferred++;
+ }
+ }
+ finally
+ {
+ if( instream != null )
+ {
+ try
+ {
+ instream.close();
+ }
+ catch( IOException ex )
+ {
+ // ignore it
+ }
+ }
+ }
+ }
+
+ /**
+ * For each file in the fileset, do the appropriate action: send, get,
+ * delete, or list.
+ *
+ * @param ftp Description of Parameter
+ * @param fs Description of Parameter
+ * @return Description of the Returned Value
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected int transferFiles( FTPClient ftp, FileSet fs )
+ throws IOException, BuildException
+ {
+ FileScanner ds;
+
+ if( action == SEND_FILES )
+ {
+ ds = fs.getDirectoryScanner( project );
+ }
+ else
+ {
+ ds = new FTPDirectoryScanner( ftp );
+ fs.setupDirectoryScanner( ds, project );
+ ds.scan();
+ }
+
+ String[] dsfiles = ds.getIncludedFiles();
+ String dir = null;
+ if( ( ds.getBasedir() == null ) && ( ( action == SEND_FILES ) || ( action == GET_FILES ) ) )
+ {
+ throw new BuildException( "the dir attribute must be set for send and get actions" );
+ }
+ else
+ {
+ if( ( action == SEND_FILES ) || ( action == GET_FILES ) )
+ {
+ dir = ds.getBasedir().getAbsolutePath();
+ }
+ }
+
+ // If we are doing a listing, we need the output stream created now.
+ BufferedWriter bw = null;
+ if( action == LIST_FILES )
+ {
+ File pd = new File( listing.getParent() );
+ if( !pd.exists() )
+ {
+ pd.mkdirs();
+ }
+ bw = new BufferedWriter( new FileWriter( listing ) );
+ }
+
+ for( int i = 0; i < dsfiles.length; i++ )
+ {
+ switch ( action )
+ {
+ case SEND_FILES:
+ {
+ sendFile( ftp, dir, dsfiles[i] );
+ break;
+ }
+
+ case GET_FILES:
+ {
+ getFile( ftp, dir, dsfiles[i] );
+ break;
+ }
+
+ case DEL_FILES:
+ {
+ delFile( ftp, dsfiles[i] );
+ break;
+ }
+
+ case LIST_FILES:
+ {
+ listFile( ftp, bw, dsfiles[i] );
+ break;
+ }
+
+ default:
+ {
+ throw new BuildException( "unknown ftp action " + action );
+ }
+ }
+ }
+
+ if( action == LIST_FILES )
+ {
+ bw.close();
+ }
+
+ return dsfiles.length;
+ }
+
+ /**
+ * Sends all files specified by the configured filesets to the remote
+ * server.
+ *
+ * @param ftp Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ protected void transferFiles( FTPClient ftp )
+ throws IOException, BuildException
+ {
+ transferred = 0;
+ skipped = 0;
+
+ if( filesets.size() == 0 )
+ {
+ throw new BuildException( "at least one fileset must be specified." );
+ }
+ else
+ {
+ // get files from filesets
+ for( int i = 0; i < filesets.size(); i++ )
+ {
+ FileSet fs = ( FileSet )filesets.elementAt( i );
+ if( fs != null )
+ {
+ transferFiles( ftp, fs );
+ }
+ }
+ }
+
+ log( transferred + " files " + COMPLETED_ACTION_STRS[action] );
+ if( skipped != 0 )
+ {
+ log( skipped + " files were not successfully " + COMPLETED_ACTION_STRS[action] );
+ }
+ }
+
+ public static class Action extends EnumeratedAttribute
+ {
+
+ private final static String[] validActions = {
+ "send", "put", "recv", "get", "del", "delete", "list", "mkdir"
+ };
+
+ public int getAction()
+ {
+ String actionL = getValue().toLowerCase( Locale.US );
+ if( actionL.equals( "send" ) ||
+ actionL.equals( "put" ) )
+ {
+ return SEND_FILES;
+ }
+ else if( actionL.equals( "recv" ) ||
+ actionL.equals( "get" ) )
+ {
+ return GET_FILES;
+ }
+ else if( actionL.equals( "del" ) ||
+ actionL.equals( "delete" ) )
+ {
+ return DEL_FILES;
+ }
+ else if( actionL.equals( "list" ) )
+ {
+ return LIST_FILES;
+ }
+ else if( actionL.equals( "mkdir" ) )
+ {
+ return MK_DIR;
+ }
+ return SEND_FILES;
+ }
+
+ public String[] getValues()
+ {
+ return validActions;
+ }
+ }
+
+ protected class FTPDirectoryScanner extends DirectoryScanner
+ {
+ protected FTPClient ftp = null;
+
+ public FTPDirectoryScanner( FTPClient ftp )
+ {
+ super();
+ this.ftp = ftp;
+ }
+
+ 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];
+ }
+
+ filesIncluded = new Vector();
+ filesNotIncluded = new Vector();
+ filesExcluded = new Vector();
+ dirsIncluded = new Vector();
+ dirsNotIncluded = new Vector();
+ dirsExcluded = new Vector();
+
+ try
+ {
+ String cwd = ftp.printWorkingDirectory();
+ scandir( ".", "", true );// always start from the current ftp working dir
+ ftp.changeWorkingDirectory( cwd );
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Unable to scan FTP server: ", e );
+ }
+ }
+
+ protected void scandir( String dir, String vpath, boolean fast )
+ {
+ try
+ {
+ if( !ftp.changeWorkingDirectory( dir ) )
+ {
+ return;
+ }
+
+ FTPFile[] newfiles = ftp.listFiles();
+ if( newfiles == null )
+ {
+ ftp.changeToParentDirectory();
+ return;
+ }
+
+ for( int i = 0; i < newfiles.length; i++ )
+ {
+ FTPFile file = newfiles[i];
+ if( !file.getName().equals( "." ) && !file.getName().equals( ".." ) )
+ {
+ if( file.isDirectory() )
+ {
+ String name = file.getName();
+ if( isIncluded( name ) )
+ {
+ if( !isExcluded( name ) )
+ {
+ dirsIncluded.addElement( name );
+ if( fast )
+ {
+ scandir( name, vpath + name + File.separator, fast );
+ }
+ }
+ else
+ {
+ dirsExcluded.addElement( name );
+ }
+ }
+ else
+ {
+ dirsNotIncluded.addElement( name );
+ if( fast && couldHoldIncluded( name ) )
+ {
+ scandir( name, vpath + name + File.separator, fast );
+ }
+ }
+ if( !fast )
+ {
+ scandir( name, vpath + name + File.separator, fast );
+ }
+ }
+ else
+ {
+ if( file.isFile() )
+ {
+ String name = vpath + file.getName();
+ if( isIncluded( name ) )
+ {
+ if( !isExcluded( name ) )
+ {
+ filesIncluded.addElement( name );
+ }
+ else
+ {
+ filesExcluded.addElement( name );
+ }
+ }
+ else
+ {
+ filesNotIncluded.addElement( name );
+ }
+ }
+ }
+ }
+ }
+ ftp.changeToParentDirectory();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Error while communicating with FTP server: ", e );
+ }
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/MimeMail.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/MimeMail.java
new file mode 100644
index 000000000..1a498c6df
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/MimeMail.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.net;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;// Standard SDK imports
+import java.util.Properties;
+import java.util.Vector;//imported for data source and handler
+import javax.activation.DataHandler;
+import javax.activation.FileDataSource;
+import javax.mail.Message;
+import javax.mail.MessagingException;//imported for the mail api
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;// Ant imports
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+
+
+/**
+ * A task to send SMTP email. This version has near identical syntax to the
+ * SendEmail task, but is MIME aware. It also requires Sun's mail.jar and
+ * activation.jar to compile and execute, which puts it clearly into the very
+ * optional category.
+ *
+ * @author glenn_twiggs@bmc.com
+ * @author steve_l@iseran.com steve loughran
+ * @author erik@hatcher.net Erik Hatcher
+ * @author paulo.gaspar@krankikom.de Paulo Gaspar
+ * @created 01 May 2001
+ */
+public class MimeMail extends Task
+{
+ /**
+ * failure flag
+ */
+ private boolean failOnError = true;
+
+ /**
+ * sender
+ */
+ private String from = null;
+
+ /**
+ * host running SMTP
+ */
+ private String mailhost = "localhost";
+
+ /**
+ * any text
+ */
+ private String message = null;
+
+ /**
+ * message file (mutually exclusive from message)
+ */
+ private File messageFile = null;
+
+ /**
+ * TO recipients
+ */
+ private String toList = null;
+
+ /**
+ * CC (Carbon Copy) recipients
+ */
+ protected String ccList = null;
+
+ /**
+ * BCC (Blind Carbon Copy) recipients
+ */
+ protected String bccList = null;
+
+ /**
+ * subject field
+ */
+ private String subject = null;
+
+ /**
+ * file list
+ */
+ private Vector filesets = new Vector();
+
+ /**
+ * type of the text message, plaintext by default but text/html or text/xml
+ * is quite feasible
+ */
+ private String messageMimeType = "text/plain";
+
+ /**
+ * Creates new instance
+ */
+ public MimeMail() { }
+
+
+ // helper method to add recipients
+ private static void addRecipients( MimeMessage msg,
+ Message.RecipientType recipType,
+ String addrUserName,
+ String addrList
+ )
+ throws MessagingException, BuildException
+ {
+ if( ( null == addrList ) || ( addrList.trim().length() <= 0 ) )
+ return;
+
+ try
+ {
+ InternetAddress[] addrArray = InternetAddress.parse( addrList );
+
+ if( ( null == addrArray ) || ( 0 == addrArray.length ) )
+ throw new BuildException( "Empty " + addrUserName + " recipients list was specified" );
+
+ msg.setRecipients( recipType, addrArray );
+ }
+ catch( AddressException ae )
+ {
+ throw new BuildException( "Invalid " + addrUserName + " recipient list" );
+ }
+ }
+
+ /**
+ * Sets the toList parameter of this build task.
+ *
+ * @param bccList The new BccList value
+ */
+ public void setBccList( String bccList )
+ {
+ this.bccList = bccList;
+ }
+
+ /**
+ * Sets the toList parameter of this build task.
+ *
+ * @param ccList The new CcList value
+ */
+ public void setCcList( String ccList )
+ {
+ this.ccList = ccList;
+ }
+
+
+ /**
+ * Sets the FailOnError attribute of the MimeMail object
+ *
+ * @param failOnError The new FailOnError value
+ */
+ public void setFailOnError( boolean failOnError )
+ {
+ this.failOnError = failOnError;
+ }
+
+
+ /**
+ * Sets the "from" parameter of this build task.
+ *
+ * @param from Email address of sender.
+ */
+ public void setFrom( String from )
+ {
+ this.from = from;
+ }
+
+
+ /**
+ * Sets the mailhost parameter of this build task.
+ *
+ * @param mailhost Mail host name.
+ */
+ public void setMailhost( String mailhost )
+ {
+ this.mailhost = mailhost;
+ }
+
+
+ /**
+ * Sets the message parameter of this build task.
+ *
+ * @param message Message body of this email.
+ */
+ public void setMessage( String message )
+ {
+ this.message = message;
+ }
+
+ public void setMessageFile( File messageFile )
+ {
+ this.messageFile = messageFile;
+ }
+
+
+ /**
+ * set type of the text message, plaintext by default but text/html or
+ * text/xml is quite feasible
+ *
+ * @param type The new MessageMimeType value
+ */
+ public void setMessageMimeType( String type )
+ {
+ this.messageMimeType = type;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 );
+ }
+
+ /**
+ * here is where the mail is sent
+ *
+ * @exception MessagingException Description of Exception
+ * @exception AddressException Description of Exception
+ * @exception BuildException Description of Exception
+ */
+ public void doMail()
+ throws MessagingException, AddressException, BuildException
+ {
+ Properties props = new Properties();
+ props.put( "mail.smtp.host", mailhost );
+
+ //Aside, the JDK is clearly unaware of the scottish 'session', which
+ //involves excessive quantities of alcohol :-)
+ Session sesh = Session.getDefaultInstance( props, null );
+
+ //create the message
+ MimeMessage msg = new MimeMessage( sesh );
+
+ //set the sender
+ log( "message sender: " + from, Project.MSG_VERBOSE );
+ msg.setFrom( new InternetAddress( from ) );
+
+ // add recipient lists
+ addRecipients( msg, Message.RecipientType.TO, "To", toList );
+ addRecipients( msg, Message.RecipientType.CC, "Cc", ccList );
+ addRecipients( msg, Message.RecipientType.BCC, "Bcc", bccList );
+
+ if( subject != null )
+ {
+ log( "subject: " + subject, Project.MSG_VERBOSE );
+ msg.setSubject( subject );
+ }
+
+ //now the complex bit; adding multiple mime objects. And guessing
+ //the file type
+ MimeMultipart attachments = new MimeMultipart();
+
+ //first a message
+ if( messageFile != null )
+ {
+ int size = ( int )messageFile.length();
+ byte data[] = new byte[size];
+
+ try
+ {
+ FileInputStream inStream = new FileInputStream( messageFile );
+ inStream.read( data );
+ inStream.close();
+ message = new String( data );
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( e );
+ }
+ }
+
+ if( message != null )
+ {
+ MimeBodyPart textbody = new MimeBodyPart();
+ textbody.setContent( message, messageMimeType );
+ attachments.addBodyPart( textbody );
+ }
+
+ for( int i = 0; i < filesets.size(); i++ )
+ {
+ FileSet fs = ( FileSet )filesets.elementAt( i );
+ if( fs != null )
+ {
+ DirectoryScanner ds = fs.getDirectoryScanner( project );
+ String[] dsfiles = ds.getIncludedFiles();
+ File baseDir = ds.getBasedir();
+
+ for( int j = 0; j < dsfiles.length; j++ )
+ {
+ File file = new File( baseDir, dsfiles[j] );
+ MimeBodyPart body;
+ body = new MimeBodyPart();
+ if( !file.exists() || !file.canRead() )
+ {
+ throw new BuildException( "File \"" + file.getAbsolutePath()
+ + "\" does not exist or is not readable." );
+ }
+ log( "Attaching " + file.toString() + " - " + file.length() + " bytes",
+ Project.MSG_VERBOSE );
+ FileDataSource fileData = new FileDataSource( file );
+ DataHandler fileDataHandler = new DataHandler( fileData );
+ body.setDataHandler( fileDataHandler );
+ body.setFileName( file.getName() );
+ attachments.addBodyPart( body );
+ }// for j
+ }// if (fs != null)
+ }// for i
+
+ msg.setContent( attachments );
+ log( "sending email " );
+ Transport.send( msg );
+ }
+
+
+ /**
+ * 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();
+ doMail();
+ }
+ 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()
+ {
+ if( from == null )
+ {
+ throw new BuildException( "Attribute \"from\" is required." );
+ }
+
+ if( ( toList == null ) && ( ccList == null ) && ( bccList == null ) )
+ {
+ throw new BuildException( "Attribute \"toList\", \"ccList\" or \"bccList\" is required." );
+ }
+
+ if( message == null && filesets.isEmpty() && messageFile == null )
+ {
+ throw new BuildException( "FileSet, \"message\", or \"messageFile\" is required." );
+ }
+
+ if( message != null && messageFile != null )
+ {
+ throw new BuildException( "Only one of \"message\" or \"messageFile\" may be specified." );
+ }
+ }
+}
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/TelnetTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/TelnetTask.java
new file mode 100644
index 000000000..f2d1e7c46
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/TelnetTask.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.net;
+import com.oroinc.net.telnet.TelnetClient;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Calendar;
+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;
+
+/**
+ * Class to provide automated telnet protocol support for the Ant build tool
+ *
+ * @author ScottCarlson@email.com
+ * @version $Revision$
+ */
+
+public class TelnetTask extends Task
+{
+ /**
+ * The userid to login with, if automated login is used
+ */
+ private String userid = null;
+
+ /**
+ * The password to login with, if automated login is used
+ */
+ private String password = null;
+
+ /**
+ * The server to connect to.
+ */
+ private String server = null;
+
+ /**
+ * The tcp port to connect to.
+ */
+ private int port = 23;
+
+ /**
+ * The Object which handles the telnet session.
+ */
+ private AntTelnetClient telnet = null;
+
+ /**
+ * The list of read/write commands for this session
+ */
+ private Vector telnetTasks = new Vector();
+
+ /**
+ * If true, adds a CR to beginning of login script
+ */
+ private boolean addCarriageReturn = false;
+
+ /**
+ * Default time allowed for waiting for a valid response for all child
+ * reads. A value of 0 means no limit.
+ */
+ private Integer defaultTimeout = null;
+
+ /**
+ * Set the tcp port to connect to attribute
+ *
+ * @param b The new InitialCR value
+ */
+ public void setInitialCR( boolean b )
+ {
+ this.addCarriageReturn = b;
+ }
+
+ /**
+ * Set the password attribute
+ *
+ * @param p The new Password value
+ */
+ public void setPassword( String p )
+ {
+ this.password = p;
+ }
+
+ /**
+ * Set the tcp port to connect to attribute
+ *
+ * @param p The new Port value
+ */
+ public void setPort( int p )
+ {
+ this.port = p;
+ }
+
+ /**
+ * Set the server address attribute
+ *
+ * @param m The new Server value
+ */
+ public void setServer( String m )
+ {
+ this.server = m;
+ }
+
+ /**
+ * Change the default timeout to wait for valid responses
+ *
+ * @param i The new Timeout value
+ */
+ public void setTimeout( Integer i )
+ {
+ this.defaultTimeout = i;
+ }
+
+ /**
+ * Set the userid attribute
+ *
+ * @param u The new Userid value
+ */
+ public void setUserid( String u )
+ {
+ this.userid = u;
+ }
+
+ /**
+ * A subTask <read> tag was found. Create the object, Save it in our
+ * list, and return it.
+ *
+ * @return Description of the Returned Value
+ */
+
+ public TelnetSubTask createRead()
+ {
+ TelnetSubTask task = ( TelnetSubTask )new TelnetRead();
+ telnetTasks.addElement( task );
+ return task;
+ }
+
+ /**
+ * A subTask <write> tag was found. Create the object, Save it in our
+ * list, and return it.
+ *
+ * @return Description of the Returned Value
+ */
+ public TelnetSubTask createWrite()
+ {
+ TelnetSubTask task = ( TelnetSubTask )new TelnetWrite();
+ telnetTasks.addElement( task );
+ return task;
+ }
+
+ /**
+ * Verify that all parameters are included. Connect and possibly login
+ * Iterate through the list of Reads and writes
+ *
+ * @exception BuildException Description of Exception
+ */
+ public void execute()
+ throws BuildException
+ {
+ /**
+ * A server name is required to continue
+ */
+ if( server == null )
+ throw new BuildException( "No Server Specified" );
+ /**
+ * A userid and password must appear together if they appear. They are
+ * not required.
+ */
+ if( userid == null && password != null )
+ throw new BuildException( "No Userid Specified" );
+ if( password == null && userid != null )
+ throw new BuildException( "No Password Specified" );
+
+ /**
+ * Create the telnet client object
+ */
+ telnet = new AntTelnetClient();
+ try
+ {
+ telnet.connect( server, port );
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Can't connect to " + server );
+ }
+ /**
+ * Login if userid and password were specified
+ */
+ if( userid != null && password != null )
+ login();
+ /**
+ * Process each sub command
+ */
+ Enumeration tasksToRun = telnetTasks.elements();
+ while( tasksToRun != null && tasksToRun.hasMoreElements() )
+ {
+ TelnetSubTask task = ( TelnetSubTask )tasksToRun.nextElement();
+ if( task instanceof TelnetRead && defaultTimeout != null )
+ ( ( TelnetRead )task ).setDefaultTimeout( defaultTimeout );
+ task.execute( telnet );
+ }
+ }
+
+ /**
+ * Process a 'typical' login. If it differs, use the read and write tasks
+ * explicitely
+ */
+ private void login()
+ {
+ if( addCarriageReturn )
+ telnet.sendString( "\n", true );
+ telnet.waitForString( "ogin:" );
+ telnet.sendString( userid, true );
+ telnet.waitForString( "assword:" );
+ telnet.sendString( password, false );
+ }
+
+ /**
+ * This class handles the abstraction of the telnet protocol. Currently it
+ * is a wrapper around ORO 's NetComponents
+ *
+ * @author RT
+ */
+ public class AntTelnetClient extends TelnetClient
+ {
+
+ /**
+ * Write this string to the telnet session.
+ *
+ * @param s Description of Parameter
+ * @param echoString Description of Parameter
+ * @parm echoString Logs string sent
+ */
+ public void sendString( String s, boolean echoString )
+ {
+ OutputStream os = this.getOutputStream();
+ try
+ {
+ os.write( ( s + "\n" ).getBytes() );
+ if( echoString )
+ log( s, Project.MSG_INFO );
+ os.flush();
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( e );
+ }
+ }
+
+ /**
+ * Read from the telnet session until the string we are waiting for is
+ * found
+ *
+ * @param s Description of Parameter
+ * @parm s The string to wait on
+ */
+ public void waitForString( String s )
+ {
+ waitForString( s, null );
+ }
+
+ /**
+ * Read from the telnet session until the string we are waiting for is
+ * found or the timeout has been reached
+ *
+ * @param s Description of Parameter
+ * @param timeout Description of Parameter
+ * @parm s The string to wait on
+ * @parm timeout The maximum number of seconds to wait
+ */
+ public void waitForString( String s, Integer timeout )
+ {
+ InputStream is = this.getInputStream();
+ try
+ {
+ StringBuffer sb = new StringBuffer();
+ if( timeout == null || timeout.intValue() == 0 )
+ {
+ while( sb.toString().indexOf( s ) == -1 )
+ {
+ sb.append( ( char )is.read() );
+ }
+ }
+ else
+ {
+ Calendar endTime = Calendar.getInstance();
+ endTime.add( Calendar.SECOND, timeout.intValue() );
+ while( sb.toString().indexOf( s ) == -1 )
+ {
+ while( Calendar.getInstance().before( endTime ) &&
+ is.available() == 0 )
+ {
+ Thread.sleep( 250 );
+ }
+ if( is.available() == 0 )
+ throw new BuildException( "Response Timed-Out", getLocation() );
+ sb.append( ( char )is.read() );
+ }
+ }
+ log( sb.toString(), Project.MSG_INFO );
+ }
+ catch( BuildException be )
+ {
+ throw be;
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( e );
+ }
+ }
+ }
+
+ /**
+ * This class reads the output from the connected server until the required
+ * string is found.
+ *
+ * @author RT
+ */
+ public class TelnetRead extends TelnetSubTask
+ {
+ private Integer timeout = null;
+
+ /**
+ * Sets the default timeout if none has been set already
+ *
+ * @param defaultTimeout The new DefaultTimeout value
+ */
+ public void setDefaultTimeout( Integer defaultTimeout )
+ {
+ if( timeout == null )
+ timeout = defaultTimeout;
+ }
+
+ /**
+ * Override any default timeouts
+ *
+ * @param i The new Timeout value
+ */
+ public void setTimeout( Integer i )
+ {
+ this.timeout = i;
+ }
+
+ public void execute( AntTelnetClient telnet )
+ throws BuildException
+ {
+ telnet.waitForString( taskString, timeout );
+ }
+ }
+
+ /**
+ * This class is the parent of the Read and Write tasks. It handles the
+ * common attributes for both.
+ *
+ * @author RT
+ */
+ public class TelnetSubTask
+ {
+ protected String taskString = "";
+
+ public void setString( String s )
+ {
+ taskString += s;
+ }
+
+ public void addText( String s )
+ {
+ setString( s );
+ }
+
+ public void execute( AntTelnetClient telnet )
+ throws BuildException
+ {
+ throw new BuildException( "Shouldn't be able instantiate a SubTask directly" );
+ }
+ }
+
+ /**
+ * This class sends text to the connected server
+ *
+ * @author RT
+ */
+ public class TelnetWrite extends TelnetSubTask
+ {
+ private boolean echoString = true;
+
+ public void setEcho( boolean b )
+ {
+ echoString = b;
+ }
+
+ public void execute( AntTelnetClient telnet )
+ throws BuildException
+ {
+ telnet.sendString( taskString, echoString );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Add.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Add.java
new file mode 100644
index 000000000..9630aa7ff
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Add.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.perforce;
+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;
+
+/**
+ * P4Add - add the specified files to perforce. Example Usage:
+ *
+ *
+ *
+ * Function
+ *
+ *
+ *
+ * Command
+ *
+ *
+ *
+ *
+ *
+ * Add files using P4USER, P4PORT and P4CLIENT settings specified
+ *
+ *
+ *
+ * <P4add
+ * P4view="//projects/foo/main/source/..."
+ * P4User="fbloggs"
+ * P4Port="km01:1666"
+ * P4Client="fbloggsclient">
+ * <fileset basedir="dir" includes="**/*.java">
+ * </p4add>
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Add files using P4USER, P4PORT and P4CLIENT settings defined in
+ * environment
+ *
+ *
+ *
+ * <P4add P4view="//projects/foo/main/source/..." />
+ * <fileset basedir="dir" includes="**/*.java">
+ * </p4add>
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Specify the length of command line arguments to pass to each invocation
+ * of p4
+ *
+ *
+ *
+ * <p4add Commandlength="450">
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @author Les Hughes
+ * @author Anli Shundi
+ */
+public class P4Add extends P4Base
+{
+ private String addCmd = "";
+ private Vector filesets = new Vector();
+ private int m_cmdLength = 450;
+
+ private int m_changelist;
+
+ public void setChangelist( int changelist )
+ throws BuildException
+ {
+ if( changelist <= 0 )
+ throw new BuildException( "P4Add: Changelist# should be a positive number" );
+
+ this.m_changelist = changelist;
+ }
+
+ public void setCommandlength( int len )
+ throws BuildException
+ {
+ if( len <= 0 )
+ throw new BuildException( "P4Add: Commandlength should be a positive number" );
+ this.m_cmdLength = len;
+ }
+
+ public void addFileset( FileSet set )
+ {
+ filesets.addElement( set );
+ }
+
+ public void execute()
+ throws BuildException
+ {
+
+ if( P4View != null )
+ {
+ addCmd = P4View;
+ }
+
+ P4CmdOpts = ( m_changelist > 0 ) ? ( "-c " + m_changelist ) : "";
+
+ StringBuffer filelist = new StringBuffer();
+
+ 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();
+ if( srcFiles != null )
+ {
+ for( int j = 0; j < srcFiles.length; j++ )
+ {
+ File f = new File( ds.getBasedir(), srcFiles[j] );
+ filelist.append( " " ).append( '"' ).append( f.getAbsolutePath() ).append( '"' );
+ if( filelist.length() > m_cmdLength )
+ {
+ execP4Add( filelist );
+ filelist.setLength( 0 );
+ }
+ }
+ if( filelist.length() > 0 )
+ {
+ execP4Add( filelist );
+ }
+ }
+ else
+ {
+ log( "No files specified to add!", Project.MSG_WARN );
+ }
+ }
+
+ }
+
+ private void execP4Add( StringBuffer list )
+ {
+ log( "Execing add " + P4CmdOpts + " " + addCmd + list, Project.MSG_INFO );
+
+ execP4Command( "-s add " + P4CmdOpts + " " + addCmd + list, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Base.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Base.java
new file mode 100644
index 000000000..dd93d117c
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Base.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.perforce;
+import java.io.IOException;
+import org.apache.oro.text.perl.Perl5Util;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Execute;
+import org.apache.tools.ant.types.Commandline;
+
+
+/**
+ * Base class for Perforce (P4) ANT tasks. See individual task for example
+ * usage.
+ *
+ * @author Les Hughes
+ * @see P4Sync
+ * @see P4Have
+ * @see P4Change
+ * @see P4Edit
+ * @see P4Submit
+ * @see P4Label
+ * @see org.apache.tools.ant.taskdefs.Exec
+ */
+public abstract class P4Base extends org.apache.tools.ant.Task
+{
+
+ /**
+ * Perl5 regexp in Java - cool eh?
+ */
+ protected Perl5Util util = null;
+
+ //P4 runtime directives
+ /**
+ * Perforce Server Port (eg KM01:1666)
+ */
+ protected String P4Port = "";
+ /**
+ * Perforce Client (eg myclientspec)
+ */
+ protected String P4Client = "";
+ /**
+ * Perforce User (eg fbloggs)
+ */
+ protected String P4User = "";
+ /**
+ * Perforce view for commands (eg //projects/foobar/main/source/... )
+ */
+ protected String P4View = "";
+
+ //P4 g-opts and cmd opts (rtfm)
+ /**
+ * Perforce 'global' opts. Forms half of low level API
+ */
+ protected String P4Opts = "";
+ /**
+ * Perforce command opts. Forms half of low level API
+ */
+ protected String P4CmdOpts = "";
+ /**
+ * The OS shell to use (cmd.exe or /bin/sh)
+ */
+ protected String shell;
+
+ public void setClient( String P4Client )
+ {
+ this.P4Client = "-c" + P4Client;
+ }
+
+ public void setCmdopts( String P4CmdOpts )
+ {
+ this.P4CmdOpts = P4CmdOpts;
+ }
+
+ //Setters called by Ant
+ public void setPort( String P4Port )
+ {
+ this.P4Port = "-p" + P4Port;
+ }
+
+ public void setUser( String P4User )
+ {
+ this.P4User = "-u" + P4User;
+ }
+
+ public void setView( String P4View )
+ {
+ this.P4View = P4View;
+ }
+
+ public void init()
+ {
+
+ util = new Perl5Util();
+
+ //Get default P4 settings from environment - Mark would have done something cool with
+ //introspection here.....:-)
+ String tmpprop;
+ if( ( tmpprop = project.getProperty( "p4.port" ) ) != null )
+ setPort( tmpprop );
+ if( ( tmpprop = project.getProperty( "p4.client" ) ) != null )
+ setClient( tmpprop );
+ if( ( tmpprop = project.getProperty( "p4.user" ) ) != null )
+ setUser( tmpprop );
+ }
+
+ protected void execP4Command( String command )
+ throws BuildException
+ {
+ execP4Command( command, null );
+ }
+
+ /**
+ * Execute P4 command assembled by subclasses.
+ *
+ * @param command The command to run
+ * @param handler A P4Handler to process any input and output
+ * @exception BuildException Description of Exception
+ */
+ protected void execP4Command( String command, P4Handler handler )
+ throws BuildException
+ {
+ try
+ {
+
+ Commandline commandline = new Commandline();
+ commandline.setExecutable( "p4" );
+
+ //Check API for these - it's how CVS does it...
+ if( P4Port != null && P4Port.length() != 0 )
+ {
+ commandline.createArgument().setValue( P4Port );
+ }
+ if( P4User != null && P4User.length() != 0 )
+ {
+ commandline.createArgument().setValue( P4User );
+ }
+ if( P4Client != null && P4Client.length() != 0 )
+ {
+ commandline.createArgument().setValue( P4Client );
+ }
+ commandline.createArgument().setLine( command );
+
+ String[] cmdline = commandline.getCommandline();
+ String cmdl = "";
+ for( int i = 0; i < cmdline.length; i++ )
+ {
+ cmdl += cmdline[i] + " ";
+ }
+
+ log( "Execing " + cmdl, Project.MSG_VERBOSE );
+
+ if( handler == null )
+ handler = new SimpleP4OutputHandler( this );
+
+ Execute exe = new Execute( handler, null );
+
+ exe.setAntRun( project );
+
+ exe.setCommandline( commandline.getCommandline() );
+
+ try
+ {
+ exe.execute();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( e );
+ }
+ finally
+ {
+ try
+ {
+ handler.stop();
+ }
+ catch( Exception e )
+ {}
+ }
+
+
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( "Problem exec'ing P4 command: " + e.getMessage() );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Change.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Change.java
new file mode 100644
index 000000000..5068a9d13
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Change.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.taskdefs.optional.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * P4Change - grab a new changelist from Perforce. P4Change creates a new
+ * changelist in perforce. P4Change sets the property ${p4.change} with the new
+ * changelist number. This should then be passed into p4edit and p4submit.
+ *
+ * @author Les Hughes
+ * @see P4Edit
+ * @see P4Submit
+ */
+public class P4Change extends P4Base
+{
+
+ protected String emptyChangeList = null;
+ protected String description = "AutoSubmit By Ant";
+
+ /*
+ * Set Description Variable.
+ */
+ public void setDescription( String desc )
+ {
+ this.description = desc;
+ }
+
+
+ public String getEmptyChangeList()
+ throws BuildException
+ {
+ final StringBuffer stringbuf = new StringBuffer();
+
+ execP4Command( "change -o",
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ if( !util.match( "/^#/", line ) )
+ {
+ if( util.match( "/error/", line ) )
+ {
+
+ log( "Client Error", Project.MSG_VERBOSE );
+ throw new BuildException( "Perforce Error, check client settings and/or server" );
+ }
+ else if( util.match( "//", line ) )
+ {
+
+ // we need to escape the description in case there are /
+ description = backslash( description );
+ line = util.substitute( "s//" + description + "/", line );
+
+ }
+ else if( util.match( "/\\/\\//", line ) )
+ {
+ //Match "//" for begining of depot filespec
+ return;
+ }
+
+ stringbuf.append( line );
+ stringbuf.append( "\n" );
+
+ }
+ }
+ } );
+
+ return stringbuf.toString();
+ }
+
+ public void execute()
+ throws BuildException
+ {
+
+ if( emptyChangeList == null )
+ emptyChangeList = getEmptyChangeList();
+ final Project myProj = project;
+
+ P4Handler handler =
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ if( util.match( "/Change/", line ) )
+ {
+
+ //Remove any non-numerical chars - should leave the change number
+ line = util.substitute( "s/[^0-9]//g", line );
+
+ int changenumber = Integer.parseInt( line );
+ log( "Change Number is " + changenumber, Project.MSG_INFO );
+ myProj.setProperty( "p4.change", "" + changenumber );
+
+ }
+ else if( util.match( "/error/", line ) )
+ {
+ throw new BuildException( "Perforce Error, check client settings and/or server" );
+ }
+
+ }
+ };
+
+ handler.setOutput( emptyChangeList );
+
+ execP4Command( "change -i", handler );
+ }
+
+ /**
+ * Ensure that a string is backslashing slashes so that it does not confuse
+ * them with Perl substitution delimiter in Oro. Backslashes are always
+ * backslashes in a string unless they escape the delimiter.
+ *
+ * @param value the string to backslash for slashes
+ * @return the backslashed string
+ * @see < a href="http://jakarta.apache.org/oro/api/org/apache/oro/text/perl/Perl5Util.html#substitute(java.lang.String,%20java.lang.String)">
+ * Oro
+ */
+ protected String backslash( String value )
+ {
+ final StringBuffer buf = new StringBuffer( value.length() );
+ final int len = value.length();
+ for( int i = 0; i < len; i++ )
+ {
+ char c = value.charAt( i );
+ if( c == '/' )
+ {
+ buf.append( '\\' );
+ }
+ buf.append( c );
+ }
+ return buf.toString();
+ }
+
+}//EoF
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Counter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Counter.java
new file mode 100644
index 000000000..59949b617
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Counter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * P4Counter - Obtain or set the value of a counter. P4Counter can be used to
+ * either print the value of a counter to the output stream for the project (by
+ * setting the "name" attribute only), to set a property based on the value of a
+ * counter (by setting the "property" attribute) or to set the counter on the
+ * perforce server (by setting the "value" attribute). Example Usage:
+ * <p4counter name="${p4.counter}" property=${p4.change}"/>
+ *
+ * @author Kirk Wylie
+ */
+
+public class P4Counter extends P4Base
+{
+ public String counter = null;
+ public String property = null;
+ public boolean shouldSetValue = false;
+ public boolean shouldSetProperty = false;
+ public int value = 0;
+
+ public void setName( String counter )
+ {
+ this.counter = counter;
+ }
+
+ public void setProperty( String property )
+ {
+ this.property = property;
+ shouldSetProperty = true;
+ }
+
+ public void setValue( int value )
+ {
+ this.value = value;
+ shouldSetValue = true;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+
+ if( ( counter == null ) || counter.length() == 0 )
+ {
+ throw new BuildException( "No counter specified to retrieve" );
+ }
+
+ if( shouldSetValue && shouldSetProperty )
+ {
+ throw new BuildException( "Cannot both set the value of the property and retrieve the value of the property." );
+ }
+
+ String command = "counter " + P4CmdOpts + " " + counter;
+ if( !shouldSetProperty )
+ {
+ // NOTE kirk@radik.com 04-April-2001 -- If you put in the -s, you
+ // have to start running through regular expressions here. Much easier
+ // to just not include the scripting information than to try to add it
+ // and strip it later.
+ command = "-s " + command;
+ }
+ if( shouldSetValue )
+ {
+ command += " " + value;
+ }
+
+ if( shouldSetProperty )
+ {
+ final Project myProj = project;
+
+ P4Handler handler =
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( "P4Counter retrieved line \"" + line + "\"", Project.MSG_VERBOSE );
+ try
+ {
+ value = Integer.parseInt( line );
+ myProj.setProperty( property, "" + value );
+ }
+ catch( NumberFormatException nfe )
+ {
+ throw new BuildException( "Perforce error. Could not retrieve counter value." );
+ }
+ }
+ };
+
+ execP4Command( command, handler );
+ }
+ else
+ {
+ execP4Command( command, new SimpleP4OutputHandler( this ) );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Delete.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Delete.java
new file mode 100644
index 000000000..f711fd2ca
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Delete.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.optional.perforce;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * P4Delete - checkout file(s) for delete. Example Usage:
+ * <p4delete change="${p4.change}" view="//depot/project/foo.txt" />
+ * Simple re-write of P4Edit changing 'edit' to 'delete'.
+ * ToDo: What to do if file is already open in one of our changelists perhaps
+ * (See also {@link P4Edit P4Edit})?
+ *
+ *
+ * @author Mike Roberts , Les Hughes
+ */
+public class P4Delete extends P4Base
+{
+
+ public String change = null;
+
+ public void setChange( String change )
+ {
+ this.change = change;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ if( change != null )
+ P4CmdOpts = "-c " + change;
+ if( P4View == null )
+ throw new BuildException( "No view specified to delete" );
+ execP4Command( "-s delete " + P4CmdOpts + " " + P4View, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Edit.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Edit.java
new file mode 100644
index 000000000..c5b10ebc8
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Edit.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.optional.perforce;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * P4Edit - checkout file(s) for edit. Example Usage:
+ * <p4edit change="${p4.change}" view="//depot/project/foo.txt" />
+ *
+ * @author Les Hughes ToDo: Should
+ * call reopen if file is already open in one of our changelists perhaps?
+ */
+
+public class P4Edit extends P4Base
+{
+
+ public String change = null;
+
+ public void setChange( String change )
+ {
+ this.change = change;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ if( change != null )
+ P4CmdOpts = "-c " + change;
+ if( P4View == null )
+ throw new BuildException( "No view specified to edit" );
+ execP4Command( "-s edit " + P4CmdOpts + " " + P4View, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Handler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Handler.java
new file mode 100644
index 000000000..7175096c3
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Handler.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.taskdefs.optional.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
+
+/**
+ * Interface for p4 job output stream handler. Classes implementing this
+ * interface can be called back by P4Base.execP4Command();
+ *
+ * @author Les Hughes
+ */
+public interface P4Handler extends ExecuteStreamHandler
+{
+
+ public void process( String line )
+ throws BuildException;
+
+ public void setOutput( String line )
+ throws BuildException;
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4HandlerAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4HandlerAdapter.java
new file mode 100644
index 000000000..552817a5b
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4HandlerAdapter.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.perforce;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.SequenceInputStream;
+import org.apache.tools.ant.BuildException;
+
+public abstract class P4HandlerAdapter implements P4Handler
+{
+
+ String p4input = "";//Input
+ InputStream es;//OUtput
+ InputStream is;
+
+ OutputStream os;
+
+ //set any data to be written to P4's stdin - messy, needs work
+ public void setOutput( String p4Input )
+ {
+ this.p4input = p4Input;
+ }
+
+ public void setProcessErrorStream( InputStream is )
+ throws IOException
+ {
+ this.es = is;
+ }//Error
+
+ public void setProcessInputStream( OutputStream os )
+ throws IOException
+ {
+ this.os = os;
+ }
+
+ public void setProcessOutputStream( InputStream is )
+ throws IOException
+ {
+ this.is = is;
+ }
+
+ public abstract void process( String line );
+
+
+ public void start()
+ throws BuildException
+ {
+
+ try
+ {
+ //First write any output to P4
+ if( p4input != null && p4input.length() > 0 && os != null )
+ {
+ os.write( p4input.getBytes() );
+ os.flush();
+ os.close();
+ }
+
+ //Now read any input and process
+
+ BufferedReader input = new BufferedReader(
+ new InputStreamReader(
+ new SequenceInputStream( is, es ) ) );
+
+ String line;
+ while( ( line = input.readLine() ) != null )
+ {
+ process( line );
+ }
+
+ input.close();
+
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( e );
+ }
+ }
+
+ public void stop() { }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Have.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Have.java
new file mode 100644
index 000000000..406b08b52
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Have.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.taskdefs.optional.perforce;
+import org.apache.tools.ant.BuildException;
+
+
+/**
+ * P4Have - lists files currently on client. P4Have simply dumps the current
+ * file version info into the Ant log (or stdout).
+ *
+ * @author Les Hughes
+ */
+public class P4Have extends P4Base
+{
+
+ public void execute()
+ throws BuildException
+ {
+ execP4Command( "have " + P4CmdOpts + " " + P4View, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Label.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Label.java
new file mode 100644
index 000000000..277256019
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Label.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.perforce;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+
+/**
+ * P4Label - create a Perforce Label. P4Label inserts a label into perforce
+ * reflecting the current client contents. Label name defaults to AntLabel if
+ * none set. Example Usage:
+ * <P4Label name="MyLabel-${TSTAMP}-${DSTAMP}" desc="Auto Build Label" />
+ *
+ *
+ * @author Les Hughes
+ */
+public class P4Label extends P4Base
+{
+ protected String desc;
+ protected String lock;
+
+ protected String name;
+
+ public void setDesc( String desc )
+ {
+ this.desc = desc;
+ }
+
+ public void setLock( String lock )
+ {
+ this.lock = lock;
+ }
+
+ public void setName( String name )
+ {
+ this.name = name;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ log( "P4Label exec:", Project.MSG_INFO );
+
+ if( P4View == null || P4View.length() < 1 )
+ {
+ log( "View not set, assuming //depot/...", Project.MSG_WARN );
+ P4View = "//depot/...";
+ }
+
+ if( desc == null || desc.length() < 1 )
+ {
+ log( "Label Description not set, assuming 'AntLabel'", Project.MSG_WARN );
+ desc = "AntLabel";
+ }
+
+ if( lock != null && !lock.equalsIgnoreCase( "locked" ) )
+ {
+ log( "lock attribute invalid - ignoring", Project.MSG_WARN );
+ }
+
+ if( name == null || name.length() < 1 )
+ {
+ SimpleDateFormat formatter = new SimpleDateFormat( "yyyy.MM.dd-hh:mm" );
+ Date now = new Date();
+ name = "AntLabel-" + formatter.format( now );
+ log( "name not set, assuming '" + name + "'", Project.MSG_WARN );
+ }
+
+ //We have to create a unlocked label first
+ String newLabel =
+ "Label: " + name + "\n" +
+ "Description: " + desc + "\n" +
+ "Options: unlocked\n" +
+ "View: " + P4View + "\n";
+
+ P4Handler handler =
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( line, Project.MSG_VERBOSE );
+ }
+ };
+
+ handler.setOutput( newLabel );
+
+ execP4Command( "label -i", handler );
+
+ execP4Command( "labelsync -l " + name,
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( line, Project.MSG_VERBOSE );
+ }
+ } );
+
+ log( "Created Label " + name + " (" + desc + ")", Project.MSG_INFO );
+
+ //Now lock if required
+ if( lock != null && lock.equalsIgnoreCase( "locked" ) )
+ {
+
+ log( "Modifying lock status to 'locked'", Project.MSG_INFO );
+
+ final StringBuffer labelSpec = new StringBuffer();
+
+ //Read back the label spec from perforce,
+ //Replace Options
+ //Submit back to Perforce
+
+ handler =
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( line, Project.MSG_VERBOSE );
+
+ if( util.match( "/^Options:/", line ) )
+ {
+ line = "Options: " + lock;
+ }
+
+ labelSpec.append( line + "\n" );
+ }
+ };
+
+
+ execP4Command( "label -o " + name, handler );
+ log( labelSpec.toString(), Project.MSG_DEBUG );
+
+ log( "Now locking label...", Project.MSG_VERBOSE );
+ handler =
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( line, Project.MSG_VERBOSE );
+ }
+ };
+
+ handler.setOutput( labelSpec.toString() );
+ execP4Command( "label -i", handler );
+ }
+
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4OutputHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4OutputHandler.java
new file mode 100644
index 000000000..fdc248dc7
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4OutputHandler.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.perforce;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Interface for p4 job output stream handler. Classes implementing this
+ * interface can be called back by P4Base.execP4Command();
+ *
+ * @author Les Hughes
+ */
+public interface P4OutputHandler
+{
+
+ public void process( String line )
+ throws BuildException;
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Reopen.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Reopen.java
new file mode 100644
index 000000000..ce1031a3a
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Reopen.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.optional.perforce;
+import org.apache.tools.ant.BuildException;
+
+/*
+ * P4Reopen - move files to a new changelist
+ *
+ * @author Les Hughes
+ */
+public class P4Reopen extends P4Base
+{
+
+ private String toChange = "";
+
+ public void setToChange( String toChange )
+ throws BuildException
+ {
+ if( toChange == null && !toChange.equals( "" ) )
+ throw new BuildException( "P4Reopen: tochange cannot be null or empty" );
+
+ this.toChange = toChange;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ if( P4View == null )
+ if( P4View == null )
+ throw new BuildException( "No view specified to reopen" );
+ execP4Command( "-s reopen -c " + toChange + " " + P4View, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Revert.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Revert.java
new file mode 100644
index 000000000..f96d1862c
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Revert.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.perforce;
+import org.apache.tools.ant.BuildException;
+
+/*
+ * P4Revert - revert open files or files in a changelist
+ *
+ * @author Les Hughes
+ */
+public class P4Revert extends P4Base
+{
+
+ private String revertChange = null;
+ private boolean onlyUnchanged = false;
+
+ public void setChange( String revertChange )
+ throws BuildException
+ {
+ if( revertChange == null && !revertChange.equals( "" ) )
+ throw new BuildException( "P4Revert: change cannot be null or empty" );
+
+ this.revertChange = revertChange;
+
+ }
+
+ public void setRevertOnlyUnchanged( boolean onlyUnchanged )
+ {
+ this.onlyUnchanged = onlyUnchanged;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+
+ /*
+ * Here we can either revert any unchanged files in a changelist
+ * or
+ * any files regardless of whether they have been changed or not
+ *
+ *
+ * The whole process also accepts a p4 filespec
+ */
+ String p4cmd = "-s revert";
+ if( onlyUnchanged )
+ p4cmd += " -a";
+
+ if( revertChange != null )
+ p4cmd += " -c " + revertChange;
+
+ execP4Command( p4cmd + " " + P4View, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Submit.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Submit.java
new file mode 100644
index 000000000..07ebb1e97
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Submit.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.optional.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * P4Submit - submit a numbered changelist to Perforce. Note: P4Submit
+ * cannot (yet) submit the default changelist. This shouldn't be a problem with
+ * the ANT API as the usual flow is P4Change to create a new numbered change
+ * followed by P4Edit then P4Submit. Example Usage:-
+ * <p4submit change="${p4.change}" />
+ *
+ * @author Les Hughes
+ */
+public class P4Submit extends P4Base
+{
+
+ //ToDo: If dealing with default cl need to parse out
+ public String change;
+
+ public void setChange( String change )
+ {
+ this.change = change;
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ if( change != null )
+ {
+ execP4Command( "submit -c " + change,
+ new P4HandlerAdapter()
+ {
+ public void process( String line )
+ {
+ log( line, Project.MSG_VERBOSE );
+ }
+ }
+ );
+
+ }
+ else
+ {
+ //here we'd parse the output from change -o into submit -i
+ //in order to support default change.
+ throw new BuildException( "No change specified (no support for default change yet...." );
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Sync.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Sync.java
new file mode 100644
index 000000000..8c6e9bcc6
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Sync.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.optional.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * P4Sync - synchronise client space to a perforce depot view. The API allows
+ * additional functionality of the "p4 sync" command (such as "p4 sync -f
+ * //...#have" or other exotic invocations).
Example Usage:
+ *
+ *
+ *
+ * Function
+ *
+ *
+ *
+ * Command
+ *
+ *
+ *
+ *
+ *
+ * Sync to head using P4USER, P4PORT and P4CLIENT settings specified
+ *
+ *
+ *
+ * <P4Sync
+ * P4view="//projects/foo/main/source/..."
+ * P4User="fbloggs"
+ * P4Port="km01:1666"
+ * P4Client="fbloggsclient" />
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Sync to head using P4USER, P4PORT and P4CLIENT settings defined in
+ * environment
+ *
+ *
+ *
+ * <P4Sync P4view="//projects/foo/main/source/..." />
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Force a re-sync to head, refreshing all files
+ *
+ *
+ *
+ * <P4Sync force="yes" P4view="//projects/foo/main/source/..." />
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Sync to a label
+ *
+ *
+ *
+ * <P4Sync label="myPerforceLabel" />
+ *
+ *
+ *
+ *
+ *
+ * ToDo: Add decent label error handling for non-exsitant labels
+ *
+ * @author Les Hughes
+ */
+public class P4Sync extends P4Base
+{
+ private String syncCmd = "";
+
+ String label;
+
+
+ public void setForce( String force )
+ throws BuildException
+ {
+ if( force == null && !label.equals( "" ) )
+ throw new BuildException( "P4Sync: If you want to force, set force to non-null string!" );
+ P4CmdOpts = "-f";
+ }
+
+ public void setLabel( String label )
+ throws BuildException
+ {
+ if( label == null && !label.equals( "" ) )
+ throw new BuildException( "P4Sync: Labels cannot be Null or Empty" );
+
+ this.label = label;
+
+ }
+
+ public void execute()
+ throws BuildException
+ {
+
+ if( P4View != null )
+ {
+ syncCmd = P4View;
+ }
+
+ if( label != null && !label.equals( "" ) )
+ {
+ syncCmd = syncCmd + "@" + label;
+ }
+
+ log( "Execing sync " + P4CmdOpts + " " + syncCmd, Project.MSG_VERBOSE );
+
+ execP4Command( "-s sync " + P4CmdOpts + " " + syncCmd, new SimpleP4OutputHandler( this ) );
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/SimpleP4OutputHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/SimpleP4OutputHandler.java
new file mode 100644
index 000000000..fc8e97e66
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/SimpleP4OutputHandler.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.perforce;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+public class SimpleP4OutputHandler extends P4HandlerAdapter
+{
+
+ P4Base parent;
+
+ public SimpleP4OutputHandler( P4Base parent )
+ {
+ this.parent = parent;
+ }
+
+ public void process( String line )
+ throws BuildException
+ {
+ if( parent.util.match( "/^exit/", line ) )
+ return;
+
+ //Throw exception on errors (except up-to-date)
+ //p4 -s is unpredicatable. For example a server down
+ //does not return error: markup
+ //
+ //Some forms producing commands (p4 -s change -o) do tag the output
+ //others don't.....
+ //Others mark errors as info, for example edit a file
+ //which is already open for edit.....
+ //Just look for error: - catches most things....
+
+ if( parent.util.match( "/error:/", line ) && !parent.util.match( "/up-to-date/", line ) )
+ {
+ throw new BuildException( line );
+ }
+
+ parent.log( parent.util.substitute( "s/^.*: //", line ), Project.MSG_INFO );
+
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/package.html b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/package.html
new file mode 100644
index 000000000..125fc2aaf
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/package.html
@@ -0,0 +1,26 @@
+
+ANT Tasks for Perforce integration.
+
+These tasks provide basic P4 capabilities to automated ANT-based build systems. Note:
+the tasks in this package are linked against the Jakarta ORO 2.0 library which
+brings the power of Perl 5 regular expressions to Java.
+
+These tasks also require you to have the p4 (or p4.exe) client in your path.
+
+@see Jakarta Project
+@see Perforce
+
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Sync
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Label
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Have
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Change
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Edit
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Submit
+@see org.apache.tools.ant.taskdefs.optional.perforce.P4Counter
+
+
+
+@author Les Hughes
+
+
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/pvcs/Pvcs.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/pvcs/Pvcs.java
new file mode 100644
index 000000000..64ad0ad85
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/pvcs/Pvcs.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.pvcs;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.Enumeration;
+import java.util.Random;
+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.LogOutputStream;
+import org.apache.tools.ant.taskdefs.LogStreamHandler;
+import org.apache.tools.ant.taskdefs.PumpStreamHandler;
+import org.apache.tools.ant.types.Commandline;
+
+/**
+ * A task that fetches source files from a PVCS archive 19-04-2001
+ *
+ * The task now has a more robust parser. It allows for platform independant
+ * file paths and supports file names with () . Thanks to Erik Husby for
+ * bringing the bug to my attention. 27-04-2001
+ *
+ * UNC paths are now handled properly. Fix provided by Don Jeffery. He also
+ * added an UpdateOnly flag that, when true, conditions the PVCS get
+ * using the -U option to only update those files that have a modification time
+ * (in PVCS) that is newer than the existing workfile.
+ *
+ * @author Thomas Christensen
+ * @author Don Jeffery
+ * @author Steven E. Newton
+ */
+public class Pvcs extends org.apache.tools.ant.Task
+{
+ /**
+ * Constant for the thing to execute
+ */
+ private final static String PCLI_EXE = "pcli";
+
+ /**
+ * Constant for the PCLI listversionedfiles recursive i a format "get"
+ * understands
+ */
+ private final static String PCLI_LVF_ARGS = "lvf -z -aw";
+
+ /**
+ * Constant for the thing to execute
+ */
+ private final static String GET_EXE = "get";
+ private String filenameFormat;
+ private String force;
+ private boolean ignorerc;
+ private String label;
+ private String lineStart;
+ private String promotiongroup;
+ private String pvcsProject;
+ private Vector pvcsProjects;
+ private String pvcsbin;
+ private String repository;
+ private boolean updateOnly;
+ private String workspace;
+
+ /**
+ * Creates a Pvcs object
+ */
+ public Pvcs()
+ {
+ super();
+ pvcsProject = null;
+ pvcsProjects = new Vector();
+ workspace = null;
+ repository = null;
+ pvcsbin = null;
+ force = null;
+ promotiongroup = null;
+ label = null;
+ ignorerc = false;
+ updateOnly = false;
+ lineStart = "\"P:";
+ filenameFormat = "{0}_arc({1})";
+ }
+
+ public void setFilenameFormat( String f )
+ {
+ filenameFormat = f;
+ }
+
+ /**
+ * Specifies the value of the force argument
+ *
+ * @param f The new Force value
+ */
+ public void setForce( String f )
+ {
+ if( f != null && f.equalsIgnoreCase( "yes" ) )
+ force = "yes";
+ else
+ force = "no";
+ }
+
+ /**
+ * If set to true the return value from executing the pvcs commands are
+ * ignored.
+ *
+ * @param b The new IgnoreReturnCode value
+ */
+ public void setIgnoreReturnCode( boolean b )
+ {
+ ignorerc = b;
+ }
+
+ /**
+ * Specifies the name of the label argument
+ *
+ * @param l The new Label value
+ */
+ public void setLabel( String l )
+ {
+ label = l;
+ }
+
+ public void setLineStart( String l )
+ {
+ lineStart = l;
+ }
+
+ /**
+ * Specifies the name of the promotiongroup argument
+ *
+ * @param w The new Promotiongroup value
+ */
+ public void setPromotiongroup( String w )
+ {
+ promotiongroup = w;
+ }
+
+ /**
+ * Specifies the location of the PVCS bin directory
+ *
+ * @param bin The new Pvcsbin value
+ */
+ public void setPvcsbin( String bin )
+ {
+ pvcsbin = bin;
+ }
+
+ /**
+ * Specifies the name of the project in the PVCS repository
+ *
+ * @param prj String
+ */
+ public void setPvcsproject( String prj )
+ {
+ pvcsProject = prj;
+ }
+
+ /**
+ * Specifies the network name of the PVCS repository
+ *
+ * @param repo String
+ */
+ public void setRepository( String repo )
+ {
+ repository = repo;
+ }
+
+ /**
+ * If set to true files are gotten only if newer than existing local files.
+ *
+ * @param l The new UpdateOnly value
+ */
+ public void setUpdateOnly( boolean l )
+ {
+ updateOnly = l;
+ }
+
+ /**
+ * Specifies the name of the workspace to store retrieved files
+ *
+ * @param ws String
+ */
+ public void setWorkspace( String ws )
+ {
+ workspace = ws;
+ }
+
+ public String getFilenameFormat()
+ {
+ return filenameFormat;
+ }
+
+ /**
+ * Get value of force
+ *
+ * @return String
+ */
+ public String getForce()
+ {
+ return force;
+ }
+
+ /**
+ * Get value of ignorereturncode
+ *
+ * @return String
+ */
+ public boolean getIgnoreReturnCode()
+ {
+ return ignorerc;
+ }
+
+ /**
+ * Get value of label
+ *
+ * @return String
+ */
+ public String getLabel()
+ {
+ return label;
+ }
+
+ public String getLineStart()
+ {
+ return lineStart;
+ }
+
+ /**
+ * Get value of promotiongroup
+ *
+ * @return String
+ */
+ public String getPromotiongroup()
+ {
+ return promotiongroup;
+ }
+
+ /**
+ * Get name of the PVCS bin directory
+ *
+ * @return String
+ */
+ public String getPvcsbin()
+ {
+ return pvcsbin;
+ }
+
+ /**
+ * Get name of the project in the PVCS repository
+ *
+ * @return String
+ */
+ public String getPvcsproject()
+ {
+ return pvcsProject;
+ }
+
+ /**
+ * Get name of the project in the PVCS repository
+ *
+ * @return Vector
+ */
+ public Vector getPvcsprojects()
+ {
+ return pvcsProjects;
+ }
+
+ /**
+ * Get network name of the PVCS repository
+ *
+ * @return String
+ */
+ public String getRepository()
+ {
+ return repository;
+ }
+
+ public boolean getUpdateOnly()
+ {
+ return updateOnly;
+ }
+
+ /**
+ * Get name of the workspace to store the retrieved files
+ *
+ * @return String
+ */
+ public String getWorkspace()
+ {
+ return workspace;
+ }
+
+ /**
+ * handles <pvcsproject> subelements
+ *
+ * @param p The feature to be added to the Pvcsproject attribute
+ */
+ public void addPvcsproject( PvcsProject p )
+ {
+ pvcsProjects.addElement( p );
+ }
+
+ /**
+ * @exception org.apache.tools.ant.BuildException Something is stopping the
+ * build...
+ */
+ public void execute()
+ throws org.apache.tools.ant.BuildException
+ {
+ Project aProj = getProject();
+ int result = 0;
+
+ if( repository == null || repository.trim().equals( "" ) )
+ throw new BuildException( "Required argument repository not specified" );
+
+ // Check workspace exists
+ // Launch PCLI listversionedfiles -z -aw
+ // Capture output
+ // build the command line from what we got the format is
+ Commandline commandLine = new Commandline();
+ commandLine.setExecutable( getExecutable( PCLI_EXE ) );
+
+ commandLine.createArgument().setValue( "lvf" );
+ commandLine.createArgument().setValue( "-z" );
+ commandLine.createArgument().setValue( "-aw" );
+ if( getWorkspace() != null )
+ commandLine.createArgument().setValue( "-sp" + getWorkspace() );
+ commandLine.createArgument().setValue( "-pr" + getRepository() );
+
+ // default pvcs project is "/"
+ if( getPvcsproject() == null && getPvcsprojects().isEmpty() )
+ pvcsProject = "/";
+
+ if( getPvcsproject() != null )
+ commandLine.createArgument().setValue( getPvcsproject() );
+ if( !getPvcsprojects().isEmpty() )
+ {
+ Enumeration e = getPvcsprojects().elements();
+ while( e.hasMoreElements() )
+ {
+ String projectName = ( ( PvcsProject )e.nextElement() ).getName();
+ if( projectName == null || ( projectName.trim() ).equals( "" ) )
+ throw new BuildException( "name is a required attribute of pvcsproject" );
+ commandLine.createArgument().setValue( projectName );
+ }
+ }
+
+ File tmp = null;
+ File tmp2 = null;
+ try
+ {
+ Random rand = new Random( System.currentTimeMillis() );
+ tmp = new File( "pvcs_ant_" + rand.nextLong() + ".log" );
+ tmp2 = new File( "pvcs_ant_" + rand.nextLong() + ".log" );
+ log( "Executing " + commandLine.toString(), Project.MSG_VERBOSE );
+ result = runCmd( commandLine, new PumpStreamHandler( new FileOutputStream( tmp ), new LogOutputStream( this, Project.MSG_WARN ) ) );
+ if( result != 0 && !ignorerc )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+
+ if( !tmp.exists() )
+ throw new BuildException( "Communication between ant and pvcs failed. No output generated from executing PVCS commandline interface \"pcli\" and \"get\"" );
+
+ // Create folders in workspace
+ log( "Creating folders", Project.MSG_INFO );
+ createFolders( tmp );
+
+ // Massage PCLI lvf output transforming '\' to '/' so get command works appropriately
+ massagePCLI( tmp, tmp2 );
+
+ // Launch get on output captured from PCLI lvf
+ commandLine.clearArgs();
+ commandLine.setExecutable( getExecutable( GET_EXE ) );
+
+ if( getForce() != null && getForce().equals( "yes" ) )
+ commandLine.createArgument().setValue( "-Y" );
+ else
+ commandLine.createArgument().setValue( "-N" );
+
+ if( getPromotiongroup() != null )
+ commandLine.createArgument().setValue( "-G" + getPromotiongroup() );
+ else
+ {
+ if( getLabel() != null )
+ commandLine.createArgument().setValue( "-r" + getLabel() );
+ }
+
+ if( updateOnly )
+ {
+ commandLine.createArgument().setValue( "-U" );
+ }
+
+ commandLine.createArgument().setValue( "@" + tmp2.getAbsolutePath() );
+ log( "Getting files", Project.MSG_INFO );
+ log( "Executing " + commandLine.toString(), Project.MSG_VERBOSE );
+ result = runCmd( commandLine, new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ) );
+ if( result != 0 && !ignorerc )
+ {
+ String msg = "Failed executing: " + commandLine.toString() + ". Return code was " + result;
+ throw new BuildException( msg, location );
+ }
+
+ }
+ catch( FileNotFoundException e )
+ {
+ String msg = "Failed executing: " + commandLine.toString() + ". Exception: " + e.getMessage();
+ throw new BuildException( msg, location );
+ }
+ catch( IOException e )
+ {
+ String msg = "Failed executing: " + commandLine.toString() + ". Exception: " + e.getMessage();
+ throw new BuildException( msg, location );
+ }
+ catch( ParseException e )
+ {
+ String msg = "Failed executing: " + commandLine.toString() + ". Exception: " + e.getMessage();
+ throw new BuildException( msg, location );
+ }
+ finally
+ {
+ if( tmp != null )
+ {
+ tmp.delete();
+ }
+ if( tmp2 != null )
+ {
+ tmp2.delete();
+ }
+ }
+ }
+
+
+ protected int runCmd( Commandline cmd, ExecuteStreamHandler out )
+ {
+ try
+ {
+ Project aProj = getProject();
+ Execute exe = new Execute( out );
+ exe.setAntRun( aProj );
+ exe.setWorkingDirectory( aProj.getBaseDir() );
+ exe.setCommandline( cmd.getCommandline() );
+ return exe.execute();
+ }
+ catch( java.io.IOException e )
+ {
+ String msg = "Failed executing: " + cmd.toString() + ". Exception: " + e.getMessage();
+ throw new BuildException( msg, location );
+ }
+ }
+
+ private String getExecutable( String exe )
+ {
+ StringBuffer correctedExe = new StringBuffer();
+ if( getPvcsbin() != null )
+ if( pvcsbin.endsWith( File.separator ) )
+ correctedExe.append( pvcsbin );
+ else
+ correctedExe.append( pvcsbin ).append( File.separator );
+ return correctedExe.append( exe ).toString();
+ }
+
+ /**
+ * Parses the file and creates the folders specified in the output section
+ *
+ * @param file Description of Parameter
+ * @exception IOException Description of Exception
+ * @exception ParseException Description of Exception
+ */
+ private void createFolders( File file )
+ throws IOException, ParseException
+ {
+ BufferedReader in = new BufferedReader( new FileReader( file ) );
+ MessageFormat mf = new MessageFormat( getFilenameFormat() );
+ String line = in.readLine();
+ while( line != null )
+ {
+ log( "Considering \"" + line + "\"", Project.MSG_VERBOSE );
+ if( line.startsWith( "\"\\" ) ||
+ line.startsWith( "\"/" ) ||
+ line.startsWith( getLineStart() ) )
+ {
+ Object[] objs = mf.parse( line );
+ String f = ( String )objs[1];
+ // Extract the name of the directory from the filename
+ int index = f.lastIndexOf( File.separator );
+ if( index > -1 )
+ {
+ File dir = new File( f.substring( 0, index ) );
+ if( !dir.exists() )
+ {
+ log( "Creating " + dir.getAbsolutePath(), Project.MSG_VERBOSE );
+ if( dir.mkdirs() )
+ {
+ log( "Created " + dir.getAbsolutePath(), Project.MSG_INFO );
+ }
+ else
+ {
+ log( "Failed to create " + dir.getAbsolutePath(), Project.MSG_INFO );
+ }
+ }
+ else
+ {
+ log( dir.getAbsolutePath() + " exists. Skipping", Project.MSG_VERBOSE );
+ }
+ }
+ else
+ {
+ log( "File separator problem with " + line,
+ Project.MSG_WARN );
+ }
+ }
+ else
+ {
+ log( "Skipped \"" + line + "\"", Project.MSG_VERBOSE );
+ }
+ line = in.readLine();
+ }
+ }
+
+ /**
+ * Simple hack to handle the PVCS command-line tools botch when handling UNC
+ * notation.
+ *
+ * @param in Description of Parameter
+ * @param out Description of Parameter
+ * @exception FileNotFoundException Description of Exception
+ * @exception IOException Description of Exception
+ */
+ private void massagePCLI( File in, File out )
+ throws FileNotFoundException, IOException
+ {
+ BufferedReader inReader = new BufferedReader( new FileReader( in ) );
+ BufferedWriter outWriter = new BufferedWriter( new FileWriter( out ) );
+ String s = null;
+ while( ( s = inReader.readLine() ) != null )
+ {
+ String sNormal = s.replace( '\\', '/' );
+ outWriter.write( sNormal );
+ outWriter.newLine();
+ }
+ inReader.close();
+ outWriter.close();
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/pvcs/PvcsProject.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/pvcs/PvcsProject.java
new file mode 100644
index 000000000..524de2f1b
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/pvcs/PvcsProject.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.pvcs;
+
+
+/**
+ * class to handle <pvcsprojec> elements
+ *
+ * @author RT
+ */
+public class PvcsProject
+{
+ private String name;
+
+ public PvcsProject()
+ {
+ super();
+ }
+
+ public void setName( String name )
+ {
+ PvcsProject.this.name = name;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/scm/AntStarTeamCheckOut.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/scm/AntStarTeamCheckOut.java
new file mode 100644
index 000000000..1c7832216
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/scm/AntStarTeamCheckOut.java
@@ -0,0 +1,1113 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.scm;
+import com.starbase.starteam.Folder;
+import com.starbase.starteam.Item;
+import com.starbase.starteam.Property;
+import com.starbase.starteam.Server;
+import com.starbase.starteam.StarTeamFinder;
+import com.starbase.starteam.Type;
+import com.starbase.starteam.View;
+import com.starbase.util.Platform;
+import java.util.StringTokenizer;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;
+
+/**
+ * Checks out files from a specific StarTeam server, project, view, and folder.
+ *
+ *
+ * This program logs in to a StarTeam server and opens up the specified project
+ * and view. Then, it searches through that view for the given folder (or, if
+ * you prefer, it uses the root folder). Beginning with that folder and
+ * optionally continuing recursivesly, AntStarTeamCheckOut compares each file
+ * with your include and exclude filters and checks it out only if appropriate.
+ *
+ *
+ * Checked out files go to a directory you specify under the subfolder named for
+ * the default StarTeam path to the view. That is, if you entered
+ * /home/cpovirk/work as the target folder, your project was named "OurProject,"
+ * the given view was named "TestView," and that view is stored by default at
+ * "C:\projects\Test," your files would be checked out to
+ * /home/cpovirk/work/Test." I avoided using the project name in the path
+ * because you may want to keep several versions of the same project on your
+ * computer, and I didn't want to use the view name, as there may be many "Test"
+ * or "Version 1.0" views, for example. This system's success, of course,
+ * depends on what you set the default path to in StarTeam.
+ *
+ * You can set AntStarTeamCheckOut to verbose or quiet mode. Also, it has a
+ * safeguard against overwriting the files on your computer: If the target
+ * directory you specify already exists, the program will throw a
+ * BuildException. To override the exception, set force to true.
+ *
+ *
+ * This program makes use of functions from the StarTeam API. As a result
+ * AntStarTeamCheckOut is available only to licensed users of StarTeam and
+ * requires the StarTeam SDK to function. You must have starteam-sdk.jar
+ * in your classpath to run this program. For more information about the
+ * StarTeam API and how to license it, see the link below.
+ *
+ * @author Chris Povirk
+ * @author JC Mann
+ * @author Jeff Gettle
+ * @author Steve Cohen
+ * @version 1.0
+ * @see StarBase Web Site
+ */
+public class AntStarTeamCheckOut extends org.apache.tools.ant.Task
+{
+
+ /**
+ * This constant sets the filter to include all files. This default has the
+ * same result as setIncludes("*").
+ *
+ * @see #getIncludes()
+ * @see #setIncludes(String includes)
+ */
+ public final static String DEFAULT_INCLUDESETTING = "*";
+
+ /**
+ * This disables the exclude filter by default. In other words, no files are
+ * excluded. This setting is equivalent to setExcludes(null).
+ *
+ * @see #getExcludes()
+ * @see #setExcludes(String excludes)
+ */
+ public final static String DEFAULT_EXCLUDESETTING = null;
+
+ /**
+ * The default folder to search; the root folder. Since AntStarTeamCheckOut
+ * searches subfolders, by default it processes an entire view.
+ *
+ * @see #getFolderName()
+ * @see #setFolderName(String folderName)
+ */
+ public final static String DEFAULT_FOLDERSETTING = null;
+
+ /**
+ * This is used when formatting the output. The directory name is displayed
+ * only when it changes.
+ */
+ private Folder prevFolder = null;
+
+ /**
+ * This field keeps count of the number of files checked out.
+ */
+ private int checkedOut = 0;
+
+ // Change these through their GET and SET methods.
+
+ /**
+ * The name of the server you wish to connect to.
+ */
+ private String serverName = null;
+
+ /**
+ * The port on the server used for StarTeam.
+ */
+ private int serverPort = -1;
+
+ /**
+ * The name of your project.
+ */
+ private String projectName = null;
+
+ /**
+ * The name of the folder you want to check out files from. All subfolders
+ * will be searched, as well.
+ */
+ private String folderName = DEFAULT_FOLDERSETTING;
+
+ /**
+ * The view that the files you want are in.
+ */
+ private String viewName = null;
+
+ /**
+ * Your username on the StarTeam server.
+ */
+ private String username = null;
+
+ /**
+ * Your StarTeam password.
+ */
+ private String password = null;
+
+ /**
+ * The path to the root folder you want to check out to. This is a local
+ * directory.
+ */
+ private String targetFolder = null;
+
+ /**
+ * If force set to true, AntStarTeamCheckOut will overwrite files in the
+ * target directory.
+ */
+ private boolean force = false;
+
+ /**
+ * When verbose is true, the program will display all files and directories
+ * as they are checked out.
+ */
+ private boolean verbose = false;
+
+ /**
+ * Set recursion to false to check out files in only the given folder and
+ * not in its subfolders.
+ */
+ private boolean recursion = true;
+
+ // These fields deal with includes and excludes
+
+ /**
+ * All files that fit this pattern are checked out.
+ */
+ private String includes = DEFAULT_INCLUDESETTING;
+
+ /**
+ * All files fitting this pattern are ignored.
+ */
+ private String excludes = DEFAULT_EXCLUDESETTING;
+
+ /**
+ * The file delimitor on the user's system.
+ */
+ private String delim = Platform.getFilePathDelim();
+
+ /**
+ * whether to use the Starteam "default folder" when calculating the target
+ * paths to which files are checked out (false) or if targetFolder
+ * represents an absolute mapping to folderName.
+ */
+ private boolean targetFolderAbsolute = false;
+
+
+ /**
+ * convenient method to check for conditions
+ *
+ * @param value Description of Parameter
+ * @param msg Description of Parameter
+ * @exception BuildException Description of Exception
+ */
+ private static void assertTrue( boolean value, String msg )
+ throws BuildException
+ {
+ if( !value )
+ {
+ throw new BuildException( msg );
+ }
+ }
+
+ /**
+ * Sets the exclude filter. When filtering files, AntStarTeamCheckOut uses
+ * an unmodified version of DirectoryScanner's match
+ * method, so here are the patterns straight from the Ant source code:
+ *
+ * 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.
+ *
+ * Separate multiple exlcude filters by spaces , not commas as Ant
+ * uses. For example, if you want to check out all files except .XML and
+ * .HTML files, you would put the following line in your program: setExcludes("*.XML *.HTML");
+ * Finally, note that filters have no effect on the directories that
+ * are scanned; you could not skip over all files in directories whose names
+ * begin with "project," for instance.
+ *
+ * Treatment of overlapping inlcudes and excludes: To give a simplistic
+ * example suppose that you set your include filter to "*.htm *.html" and
+ * your exclude filter to "index.*". What happens to index.html?
+ * AntStarTeamCheckOut will not check out index.html, as it matches an
+ * exclude filter ("index.*"), even though it matches the include filter, as
+ * well.
+ *
+ * Please also read the following sections before using filters:
+ *
+ * @param excludes A string of filter patterns to exclude. Separate the
+ * patterns by spaces.
+ * @see #setIncludes(String includes)
+ * @see #getIncludes()
+ * @see #getExcludes()
+ */
+ public void setExcludes( String excludes )
+ {
+ this.excludes = excludes;
+ }
+
+ /**
+ * Sets the folderName attribute to the given value. To search
+ * the root folder, use a slash or backslash, or simply don't set a folder
+ * at all.
+ *
+ * @param folderName The subfolder from which to check out files.
+ * @see #getFolderName()
+ */
+ public void setFolderName( String folderName )
+ {
+ this.folderName = folderName;
+ }
+
+ /**
+ * Sets the force attribute to the given value.
+ *
+ * @param force if true, it overwrites files in the target directory. By
+ * default it set to false as a safeguard. Note that if the target
+ * directory does not exist, this setting has no effect.
+ * @see #getForce()
+ */
+ public void setForce( boolean force )
+ {
+ this.force = force;
+ }
+
+ // Begin filter getters and setters
+
+ /**
+ * Sets the include filter. When filtering files, AntStarTeamCheckOut uses
+ * an unmodified version of DirectoryScanner's match
+ * method, so here are the patterns straight from the Ant source code:
+ *
+ * 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.
+ *
+ * Separate multiple inlcude filters by spaces , not commas as Ant
+ * uses. For example, if you want to check out all .java and .class\ files,
+ * you would put the following line in your program: setIncludes("*.java *.class");
+ * Finally, note that filters have no effect on the directories that
+ * are scanned; you could not check out files from directories with names
+ * beginning only with "build," for instance. Of course, you could limit
+ * AntStarTeamCheckOut to a particular folder and its subfolders with the
+ * setFolderName(String folderName) command.
+ *
+ * Treatment of overlapping inlcudes and excludes: To give a simplistic
+ * example suppose that you set your include filter to "*.htm *.html" and
+ * your exclude filter to "index.*". What happens to index.html?
+ * AntStarTeamCheckOut will not check out index.html, as it matches an
+ * exclude filter ("index.*"), even though it matches the include filter, as
+ * well.
+ *
+ * Please also read the following sections before using filters:
+ *
+ * @param includes A string of filter patterns to include. Separate the
+ * patterns by spaces.
+ * @see #getIncludes()
+ * @see #setExcludes(String excludes)
+ * @see #getExcludes()
+ */
+ public void setIncludes( String includes )
+ {
+ this.includes = includes;
+ }
+
+ /**
+ * Sets the password attribute to the given value.
+ *
+ * @param password Your password for the specified StarTeam server.
+ * @see #getPassword()
+ */
+ public void setPassword( String password )
+ {
+ this.password = password;
+ }
+
+ /**
+ * Sets the projectName attribute to the given value.
+ *
+ * @param projectName The StarTeam project to search.
+ * @see #getProjectName()
+ */
+ public void setProjectName( String projectName )
+ {
+ this.projectName = projectName;
+ }
+
+ /**
+ * Turns recursion on or off.
+ *
+ * @param recursion if it is true, the default, subfolders are searched
+ * recursively for files to check out. Otherwise, only files specified
+ * by folderName are scanned.
+ * @see #getRecursion()
+ */
+ public void setRecursion( boolean recursion )
+ {
+ this.recursion = recursion;
+ }
+
+ // Begin SET and GET methods
+
+ /**
+ * Sets the serverName attribute to the given value.
+ *
+ * @param serverName The name of the server you wish to connect to.
+ * @see #getServerName()
+ */
+ public void setServerName( String serverName )
+ {
+ this.serverName = serverName;
+ }
+
+ /**
+ * Sets the serverPort attribute to the given value. The given
+ * value must be a valid integer, but it must be a string object.
+ *
+ * @param serverPort A string containing the port on the StarTeam server to
+ * use.
+ * @see #getServerPort()
+ */
+ public void setServerPort( int serverPort )
+ {
+ this.serverPort = serverPort;
+ }
+
+ /**
+ * Sets the targetFolder attribute to the given value.
+ *
+ * @param targetFolder The new TargetFolder value
+ * @see #getTargetFolder()
+ */
+ public void setTargetFolder( String targetFolder )
+ {
+ this.targetFolder = targetFolder;
+ }
+
+ /**
+ * sets the property that indicates whether or not the Star Team "default
+ * folder" is to be used when calculation paths for items on the target
+ * (false) or if targetFolder is an absolute mapping to the root folder
+ * named by foldername.
+ *
+ * @param targetFolderAbsolute true if the absolute mapping is to
+ * be used. false (the default) if the "default folder" is to
+ * be factored in.
+ * @see #getTargetFolderAbsolute()
+ */
+ public void setTargetFolderAbsolute( boolean targetFolderAbsolute )
+ {
+ this.targetFolderAbsolute = targetFolderAbsolute;
+ }
+
+ /**
+ * Sets the username attribute to the given value.
+ *
+ * @param username Your username for the specified StarTeam server.
+ * @see #getUsername()
+ */
+ public void setUsername( String username )
+ {
+ this.username = username;
+ }
+
+ /**
+ * Sets the verbose attribute to the given value.
+ *
+ * @param verbose whether to display all files as it checks them out. By
+ * default it is false, so the program only displays the total number
+ * of files unless you override this default.
+ * @see #getVerbose()
+ */
+ public void setVerbose( boolean verbose )
+ {
+ this.verbose = verbose;
+ }
+
+ /**
+ * Sets the viewName attribute to the given value.
+ *
+ * @param viewName The view to find the specified folder in.
+ * @see #getViewName()
+ */
+ public void setViewName( String viewName )
+ {
+ this.viewName = viewName;
+ }
+
+ /**
+ * Gets the patterns from the exclude filter. Rather that duplicate the
+ * details of AntStarTeanCheckOut's filtering here, refer to these links:
+ *
+ * @return A string of filter patterns separated by spaces.
+ * @see #setExcludes(String excludes)
+ * @see #setIncludes(String includes)
+ * @see #getIncludes()
+ */
+ public String getExcludes()
+ {
+ return excludes;
+ }
+
+ /**
+ * Gets the folderName attribute.
+ *
+ * @return The subfolder from which to check out files. All subfolders will
+ * be searched, as well.
+ * @see #setFolderName(String folderName)
+ */
+ public String getFolderName()
+ {
+ return folderName;
+ }
+
+ /**
+ * Gets the force attribute.
+ *
+ * @return whether to continue if the target directory exists.
+ * @see #setForce(boolean)
+ */
+ public boolean getForce()
+ {
+ return force;
+ }
+
+ /**
+ * Gets the patterns from the include filter. Rather that duplicate the
+ * details of AntStarTeanCheckOut's filtering here, refer to these links:
+ *
+ * @return A string of filter patterns separated by spaces.
+ * @see #setIncludes(String includes)
+ * @see #setExcludes(String excludes)
+ * @see #getExcludes()
+ */
+ public String getIncludes()
+ {
+ return includes;
+ }
+
+ /**
+ * Gets the password attribute.
+ *
+ * @return The password given by the user.
+ * @see #setPassword(String password)
+ */
+ public String getPassword()
+ {
+ return password;
+ }
+
+ /**
+ * Gets the projectName attribute.
+ *
+ * @return The StarTeam project to search.
+ * @see #setProjectName(String projectName)
+ */
+ public String getProjectName()
+ {
+ return projectName;
+ }
+
+ /**
+ * Gets the recursion attribute, which tells
+ * AntStarTeamCheckOut whether to search subfolders when checking out files.
+ *
+ * @return whether to search subfolders when checking out files.
+ * @see #setRecursion(boolean)
+ */
+ public boolean getRecursion()
+ {
+ return recursion;
+ }
+
+ /**
+ * Gets the serverName attribute.
+ *
+ * @return The StarTeam server to log in to.
+ * @see #setServerName(String serverName)
+ */
+ public String getServerName()
+ {
+ return serverName;
+ }
+
+ /**
+ * Gets the serverPort attribute.
+ *
+ * @return A string containing the port on the StarTeam server to use.
+ * @see #setServerPort(int)
+ */
+ public int getServerPort()
+ {
+ return serverPort;
+ }
+
+ /**
+ * Gets the targetFolder attribute.
+ *
+ * @return The target path on the local machine to check out to.
+ * @see #setTargetFolder(String targetFolder)
+ */
+ public String getTargetFolder()
+ {
+ return targetFolder;
+ }
+
+
+ /**
+ * returns whether the StarTeam default path is factored into calculated
+ * target path locations (false) or whether targetFolder is an absolute
+ * mapping to the root folder named by folderName
+ *
+ * @return returns true if absolute mapping is used, false if it is not
+ * used.
+ * @see #setTargetFolderAbsolute(boolean)
+ */
+ public boolean getTargetFolderAbsolute()
+ {
+ return this.targetFolderAbsolute;
+ }
+
+ /**
+ * Gets the username attribute.
+ *
+ * @return The username given by the user.
+ * @see #setUsername(String username)
+ */
+ public String getUsername()
+ {
+ return username;
+ }
+
+ /**
+ * Gets the verbose attribute.
+ *
+ * @return whether to display all files as it checks them out.
+ * @see #setVerbose(boolean verbose)
+ */
+ public boolean getVerbose()
+ {
+ return verbose;
+ }
+
+ /**
+ * Gets the viewName attribute.
+ *
+ * @return The view to find the specified folder in.
+ * @see #setViewName(String viewName)
+ */
+ public String getViewName()
+ {
+ return viewName;
+ }
+
+ /**
+ * Do the execution.
+ *
+ * @exception BuildException
+ */
+ public void execute()
+ throws BuildException
+ {
+ // Connect to the StarTeam server, and log on.
+ Server s = getServer();
+
+ try
+ {
+ // Search the items on this server.
+ runServer( s );
+ }
+ finally
+ {
+ // Disconnect from the server.
+ s.disconnect();
+ }
+ // after you are all of the properties are ok, do your thing
+ // with StarTeam. If there are any kind of exceptions then
+ // send the message to the project log.
+
+ // Tell how many files were checked out.
+ log( checkedOut + " files checked out." );
+ }
+
+ /**
+ * Get the primary descriptor of the given item type. Returns null if there
+ * isn't one. In practice, all item types have a primary descriptor.
+ *
+ * @param t An item type. At this point it will always be "file".
+ * @return The specified item's primary descriptor.
+ */
+ protected Property getPrimaryDescriptor( Type t )
+ {
+ Property[] properties = t.getProperties();
+ for( int i = 0; i < properties.length; i++ )
+ {
+ Property p = properties[i];
+ if( p.isPrimaryDescriptor() )
+ {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the secondary descriptor of the given item type. Returns null if
+ * there isn't one.
+ *
+ * @param t An item type. At this point it will always be "file".
+ * @return The specified item's secondary descriptor. There may not be one
+ * for every file.
+ */
+ protected Property getSecondaryDescriptor( Type t )
+ {
+ Property[] properties = t.getProperties();
+ for( int i = 0; i < properties.length; i++ )
+ {
+ Property p = properties[i];
+ if( p.isDescriptor() && !p.isPrimaryDescriptor() )
+ {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates and logs in to a StarTeam server.
+ *
+ * @return A StarTeam server.
+ */
+ protected Server getServer()
+ {
+ // Simplest constructor, uses default encryption algorithm and compression level.
+ Server s = new Server( getServerName(), getServerPort() );
+
+ // Optional; logOn() connects if necessary.
+ s.connect();
+
+ // Logon using specified user name and password.
+ s.logOn( getUsername(), getPassword() );
+
+ return s;
+ }
+
+ protected void checkParameters()
+ throws BuildException
+ {
+ // Check all of the properties that are required.
+ assertTrue( getServerName() != null, "ServerName must be set." );
+ assertTrue( getServerPort() != -1, "ServerPort must be set." );
+ assertTrue( getProjectName() != null, "ProjectName must be set." );
+ assertTrue( getViewName() != null, "ViewName must be set." );
+ assertTrue( getUsername() != null, "Username must be set." );
+ assertTrue( getPassword() != null, "Password must be set." );
+ assertTrue( getTargetFolder() != null, "TargetFolder must be set." );
+
+ // Because of the way I create the full target path, there
+ // must be NO slash at the end of targetFolder and folderName
+ // However, if the slash or backslash is the only character, leave it alone
+ if( ( getTargetFolder().endsWith( "/" ) ||
+ getTargetFolder().endsWith( "\\" ) ) && getTargetFolder().length() > 1 )
+ {
+ setTargetFolder( getTargetFolder().substring( 0, getTargetFolder().length() - 1 ) );
+ }
+
+ // Check to see if the target directory exists.
+ java.io.File dirExist = new java.io.File( getTargetFolder() );
+ if( dirExist.isDirectory() && !getForce() )
+ {
+ throw new BuildException( "Target directory exists. Set \"force\" to \"true\" " +
+ "to continue anyway." );
+ }
+ }
+
+ /**
+ * Formats a property value for display to the user.
+ *
+ * @param p An item property to format.
+ * @param value
+ * @return A string containing the property, which is truncated to 35
+ * characters for display.
+ */
+ protected String formatForDisplay( Property p, Object value )
+ {
+ if( p.getTypeCode() == Property.Types.TEXT )
+ {
+ String str = value.toString();
+ if( str.length() > 35 )
+ {
+ str = str.substring( 0, 32 ) + "...";
+ }
+ return "\"" + str + "\"";
+ }
+ else
+ {
+ if( p.getTypeCode() == Property.Types.ENUMERATED )
+ {
+ return "\"" + p.getEnumDisplayName( ( ( Integer )value ).intValue() ) + "\"";
+ }
+ else
+ {
+ return value.toString();
+ }
+ }
+ }
+
+ /**
+ * Convenient method to see if a string match a one pattern in given set of
+ * space-separated patterns.
+ *
+ * @param patterns the space-separated list of patterns.
+ * @param pName the name to look for matching.
+ * @return whether the name match at least one pattern.
+ */
+ protected boolean matchPatterns( String patterns, String pName )
+ {
+ if( patterns == null )
+ {
+ return false;
+ }
+ StringTokenizer exStr = new StringTokenizer( patterns, " " );
+ while( exStr.hasMoreTokens() )
+ {
+ if( DirectoryScanner.match( exStr.nextToken(), pName ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Searches for files in the given folder. This method is recursive and thus
+ * searches all subfolders.
+ *
+ * @param s A StarTeam server.
+ * @param p A valid project on the server.
+ * @param v A view name from the specified project.
+ * @param t An item type which is currently always "file".
+ * @param f The folder to search.
+ * @param tgt Target folder on local machine
+ */
+ protected void runFolder( Server s,
+ com.starbase.starteam.Project p,
+ View v,
+ Type t,
+ Folder f,
+ java.io.File tgt )
+ {
+ // Process all items in this folder.
+ Item[] items = f.getItems( t.getName() );
+ for( int i = 0; i < items.length; i++ )
+ {
+ runItem( s, p, v, t, f, items[i], tgt );
+ }
+
+ // Process all subfolders recursively if recursion is on.
+ if( getRecursion() )
+ {
+ Folder[] subfolders = f.getSubFolders();
+ for( int i = 0; i < subfolders.length; i++ )
+ {
+ runFolder( s, p, v, t, subfolders[i], new java.io.File( tgt, subfolders[i].getName() ) );
+ }
+ }
+ }
+
+ /**
+ * Check out one file if it matches the include filter but not the exclude
+ * filter.
+ *
+ * @param s A StarTeam server.
+ * @param p A valid project on the server.
+ * @param v A view name from the specified project.
+ * @param t An item type which is currently always "file".
+ * @param f The folder the file is localed in.
+ * @param item The file to check out.
+ * @param tgt target folder on local machine
+ */
+ protected void runItem( Server s,
+ com.starbase.starteam.Project p,
+ View v,
+ Type t,
+ Folder f,
+ Item item,
+ java.io.File tgt )
+ {
+ // Get descriptors for this item type.
+ Property p1 = getPrimaryDescriptor( t );
+ Property p2 = getSecondaryDescriptor( t );
+
+ String pName = ( String )item.get( p1.getName() );
+ if( !shouldCheckout( pName ) )
+ {
+ return;
+ }
+
+ // VERBOSE MODE ONLY
+ if( getVerbose() )
+ {
+ // Show folder only if changed.
+ boolean bShowHeader = ( f != prevFolder );
+ if( bShowHeader )
+ {
+ // We want to display the folder the same way you would
+ // enter it on the command line ... so we remove the
+ // View name (which is also the name of the root folder,
+ // and therefore shows up at the start of the path).
+ String strFolder = f.getFolderHierarchy();
+ int i = strFolder.indexOf( delim );
+ if( i >= 0 )
+ {
+ strFolder = strFolder.substring( i + 1 );
+ }
+ log( " Folder: \"" + strFolder + "\"" );
+ prevFolder = f;
+
+ // If we displayed the project, view, item type, or folder,
+ // then show the list of relevant item properties.
+ StringBuffer header = new StringBuffer( " Item" );
+ header.append( ",\t" ).append( p1.getDisplayName() );
+ if( p2 != null )
+ {
+ header.append( ",\t" ).append( p2.getDisplayName() );
+ }
+ log( header.toString() );
+ }
+
+ // Finally, show the Item properties ...
+ // Always show the ItemID.
+ StringBuffer itemLine = new StringBuffer( " " );
+ itemLine.append( item.getItemID() );
+
+ // Show the primary descriptor.
+ // There should always be one.
+ itemLine.append( ",\t" ).append( formatForDisplay( p1, item.get( p1.getName() ) ) );
+
+ // Show the secondary descriptor, if there is one.
+ // Some item types have one, some don't.
+ if( p2 != null )
+ {
+ itemLine.append( ",\t" ).append( formatForDisplay( p2, item.get( p2.getName() ) ) );
+ }
+
+ // Show if the file is locked.
+ int locker = item.getLocker();
+ if( locker > -1 )
+ {
+ itemLine.append( ",\tLocked by " ).append( locker );
+ }
+ else
+ {
+ itemLine.append( ",\tNot locked" );
+ }
+ log( itemLine.toString() );
+ }
+ // END VERBOSE ONLY
+
+ // Check it out; also ugly.
+
+ // Change the item to be checked out to a StarTeam File.
+ com.starbase.starteam.File remote = ( com.starbase.starteam.File )item;
+
+ // The local file name is simply the local target path (tgt) which has
+ // been passed recursively down from the top of the tree, with the item's name appended.
+ java.io.File local = new java.io.File( tgt, ( String )item.get( p1.getName() ) );
+
+ try
+ {
+ remote.checkoutTo( local, Item.LockType.UNCHANGED, false, true, true );
+ checkedOut++;
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( "Failed to checkout '" + local + "'", e );
+ }
+ }
+
+ /**
+ * Searches for the given view in the project.
+ *
+ * @param s A StarTeam server.
+ * @param p A valid project on the given server.
+ */
+ protected void runProject( Server s, com.starbase.starteam.Project p )
+ {
+ View[] views = p.getViews();
+ for( int i = 0; i < views.length; i++ )
+ {
+ View v = views[i];
+ if( v.getName().equals( getViewName() ) )
+ {
+ if( getVerbose() )
+ {
+ log( "Found " + getProjectName() + delim + getViewName() + delim );
+ }
+ runType( s, p, v, s.typeForName( ( String )s.getTypeNames().FILE ) );
+ break;
+ }
+ }
+ }
+
+ /**
+ * Searches for the specified project on the server.
+ *
+ * @param s A StarTeam server.
+ */
+ protected void runServer( Server s )
+ {
+ com.starbase.starteam.Project[] projects = s.getProjects();
+ for( int i = 0; i < projects.length; i++ )
+ {
+ com.starbase.starteam.Project p = projects[i];
+
+ if( p.getName().equals( getProjectName() ) )
+ {
+ if( getVerbose() )
+ {
+ log( "Found " + getProjectName() + delim );
+ }
+ runProject( s, p );
+ break;
+ }
+ }
+ }
+
+ /**
+ * Searches for folders in the given view.
+ *
+ * @param s A StarTeam server.
+ * @param p A valid project on the server.
+ * @param v A view name from the specified project.
+ * @param t An item type which is currently always "file".
+ */
+ protected void runType( Server s, com.starbase.starteam.Project p, View v, Type t )
+ {
+ // This is ugly; checking for the root folder.
+ Folder f = v.getRootFolder();
+ if( getFolderName() != null )
+ {
+ if( getFolderName().equals( "\\" ) || getFolderName().equals( "/" ) )
+ {
+ setFolderName( null );
+ }
+ else
+ {
+ f = StarTeamFinder.findFolder( v.getRootFolder(), getFolderName() );
+ assertTrue( null != f, "ERROR: " + getProjectName() + delim + getViewName() + delim +
+ v.getRootFolder() + delim + getFolderName() + delim +
+ " does not exist." );
+ }
+ }
+
+ if( getVerbose() && getFolderName() != null )
+ {
+ log( "Found " + getProjectName() + delim + getViewName() +
+ delim + getFolderName() + delim + "\n" );
+ }
+
+ // For performance reasons, it is important to pre-fetch all the
+ // properties we'll need for all the items we'll be searching.
+
+ // We always display the ItemID (OBJECT_ID) and primary descriptor.
+ int nProperties = 2;
+
+ // We'll need this item type's primary descriptor.
+ Property p1 = getPrimaryDescriptor( t );
+
+ // Does this item type have a secondary descriptor?
+ // If so, we'll need it.
+ Property p2 = getSecondaryDescriptor( t );
+ if( p2 != null )
+ {
+ nProperties++;
+ }
+
+ // Now, build an array of the property names.
+ String[] strNames = new String[nProperties];
+ int iProperty = 0;
+ strNames[iProperty++] = s.getPropertyNames().OBJECT_ID;
+ strNames[iProperty++] = p1.getName();
+ if( p2 != null )
+ {
+ strNames[iProperty++] = p2.getName();
+ }
+
+ // Pre-fetch the item properties and cache them.
+ f.populateNow( t.getName(), strNames, -1 );
+
+ // Now, search for items in the selected folder.
+ runFolder( s, p, v, t, f, calcTargetFolder( v, f ) );
+
+ // Free up the memory used by the cached items.
+ f.discardItems( t.getName(), -1 );
+ }
+
+ /**
+ * Look if the file should be checked out. Don't check it out if It fits no
+ * include filters and It fits an exclude filter.
+ *
+ * @param pName the item name to look for being included.
+ * @return whether the file should be checked out or not.
+ */
+ protected boolean shouldCheckout( String pName )
+ {
+ boolean includeIt = matchPatterns( getIncludes(), pName );
+ boolean excludeIt = matchPatterns( getExcludes(), pName );
+ return ( includeIt && !excludeIt );
+ }
+
+ /**
+ * returns a file object that defines the root of the local checkout tree
+ * Depending on the value of targetFolderAbsolute, this will be either the
+ * targetFolder exactly as set by the user or the path formed by appending
+ * the default folder onto the specified target folder.
+ *
+ * @param v view from which the file is checked out, supplies the "default
+ * folder"
+ * @param rootSourceFolder root folder of the checkout operation in Star
+ * Team
+ * @return an object referencing the local file
+ * @see getTargetFolderAbsolute()
+ */
+ private java.io.File calcTargetFolder( View v, Folder rootSourceFolder )
+ {
+ java.io.File root = new java.io.File( getTargetFolder() );
+ if( !getTargetFolderAbsolute() )
+ {
+ // Create a variable dir that contains the name of
+ // the StarTeam folder that is the root folder in this view.
+ // Get the default path to the current view.
+ String defaultPath = v.getDefaultPath();
+
+ // convert whatever separator char is in starteam to that of the target system.
+ defaultPath = defaultPath.replace( '/', java.io.File.separatorChar );
+ defaultPath = defaultPath.replace( '\\', java.io.File.separatorChar );
+
+ java.io.File dir = new java.io.File( defaultPath );
+ String dirName = dir.getName();
+
+ // If it ends with separator then strip it off
+ if( dirName.endsWith( delim ) )
+ {
+ dirName = dirName.substring( 0, dirName.length() - 1 );
+ }
+
+ // Replace the projectName in the file's absolute path to the viewName.
+ // This makes the root target of a checkout operation equal to:
+ // targetFolder + dirName
+ StringTokenizer pathTokenizer = new StringTokenizer( rootSourceFolder.getFolderHierarchy(), delim );
+ String currentToken = null;
+ boolean foundRoot = false;
+ while( pathTokenizer.hasMoreTokens() )
+ {
+ currentToken = pathTokenizer.nextToken();
+ if( currentToken.equals( getProjectName() ) && !foundRoot )
+ {
+ currentToken = dirName;
+ foundRoot = true;// only want to do this the first time
+ }
+ root = new java.io.File( root, currentToken );
+ }
+ }
+
+ return root;
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/CovMerge.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/CovMerge.java
new file mode 100644
index 000000000..23abe6fe7
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/CovMerge.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+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.taskdefs.Execute;
+import org.apache.tools.ant.taskdefs.LogStreamHandler;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.FileSet;
+
+/**
+ * Convenient task to run the snapshot merge utility for JProbe Coverage.
+ *
+ * @author Stephane Bailliez
+ */
+public class CovMerge extends Task
+{
+
+ /**
+ * coverage home, it is mandatory
+ */
+ private File home = null;
+
+ /**
+ * the name of the output snapshot
+ */
+ private File tofile = null;
+
+ /**
+ * the filesets that will get all snapshots to merge
+ */
+ private Vector filesets = new Vector();
+
+ private boolean verbose;
+
+ //---------------- the tedious job begins here
+
+ public CovMerge() { }
+
+ /**
+ * set the coverage home. it must point to JProbe coverage directories where
+ * are stored native librairies and jars
+ *
+ * @param value The new Home value
+ */
+ public void setHome( File value )
+ {
+ this.home = value;
+ }
+
+ /**
+ * Set the output snapshot file
+ *
+ * @param value The new Tofile value
+ */
+ public void setTofile( File value )
+ {
+ this.tofile = value;
+ }
+
+ /**
+ * run the merging in verbose mode
+ *
+ * @param flag The new Verbose value
+ */
+ public void setVerbose( boolean flag )
+ {
+ this.verbose = flag;
+ }
+
+ /**
+ * add a fileset containing the snapshots to include/exclude
+ *
+ * @param fs The feature to be added to the Fileset attribute
+ */
+ public void addFileset( FileSet fs )
+ {
+ filesets.addElement( fs );
+ }
+
+ /**
+ * execute the jpcovmerge by providing a parameter file
+ *
+ * @exception BuildException Description of Exception
+ */
+ public void execute()
+ throws BuildException
+ {
+ checkOptions();
+
+ File paramfile = createParamFile();
+ try
+ {
+ Commandline cmdl = new Commandline();
+ cmdl.setExecutable( new File( home, "jpcovmerge" ).getAbsolutePath() );
+ if( verbose )
+ {
+ cmdl.createArgument().setValue( "-v" );
+ }
+ cmdl.createArgument().setValue( "-jp_paramfile=" + paramfile.getAbsolutePath() );
+
+ LogStreamHandler handler = new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN );
+ Execute exec = new Execute( handler );
+ log( cmdl.toString(), Project.MSG_VERBOSE );
+ exec.setCommandline( cmdl.getCommandline() );
+
+ // JProbe process always return 0 so we will not be
+ // able to check for failure ! :-(
+ int exitValue = exec.execute();
+ if( exitValue != 0 )
+ {
+ throw new BuildException( "JProbe Coverage Merging failed (" + exitValue + ")" );
+ }
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Failed to run JProbe Coverage Merge: " + e );
+ }
+ finally
+ {
+ //@todo should be removed once switched to JDK1.2
+ paramfile.delete();
+ }
+ }
+
+ /**
+ * get the snapshots from the filesets
+ *
+ * @return The Snapshots value
+ */
+ protected File[] getSnapshots()
+ {
+ 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( getProject() );
+ ds.scan();
+ String[] f = ds.getIncludedFiles();
+ for( int j = 0; j < f.length; j++ )
+ {
+ String pathname = f[j];
+ 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;
+ }
+
+ /**
+ * check for mandatory options
+ *
+ * @exception BuildException Description of Exception
+ */
+ protected void checkOptions()
+ throws BuildException
+ {
+ if( tofile == null )
+ {
+ throw new BuildException( "'tofile' attribute must be set." );
+ }
+
+ // check coverage home
+ if( home == null || !home.isDirectory() )
+ {
+ throw new BuildException( "Invalid home directory. Must point to JProbe home directory" );
+ }
+ home = new File( home, "coverage" );
+ File jar = new File( home, "coverage.jar" );
+ if( !jar.exists() )
+ {
+ throw new BuildException( "Cannot find Coverage directory: " + home );
+ }
+ }
+
+
+ /**
+ * create the parameters file that contains all file to merge and the output
+ * filename.
+ *
+ * @return Description of the Returned Value
+ * @exception BuildException Description of Exception
+ */
+ protected File createParamFile()
+ throws BuildException
+ {
+ File[] snapshots = getSnapshots();
+ File file = createTmpFile();
+ FileWriter fw = null;
+ try
+ {
+ fw = new FileWriter( file );
+ PrintWriter pw = new PrintWriter( fw );
+ for( int i = 0; i < snapshots.length; i++ )
+ {
+ pw.println( snapshots[i].getAbsolutePath() );
+ }
+ // last file is the output snapshot
+ pw.println( project.resolveFile( tofile.getPath() ) );
+ pw.flush();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "I/O error while writing to " + file, e );
+ }
+ finally
+ {
+ if( fw != null )
+ {
+ try
+ {
+ fw.close();
+ }
+ catch( IOException ignored )
+ {
+ }
+ }
+ }
+ return file;
+ }
+
+ /**
+ * create a temporary file in the current dir (For JDK1.1 support)
+ *
+ * @return Description of the Returned Value
+ */
+ protected File createTmpFile()
+ {
+ final long rand = ( new Random( System.currentTimeMillis() ) ).nextLong();
+ File file = new File( "jpcovmerge" + rand + ".tmp" );
+ return file;
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/CovReport.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/CovReport.java
new file mode 100644
index 000000000..287b559d2
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/CovReport.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka;
+import java.io.File;
+import java.io.IOException;
+import java.util.Vector;
+import javax.xml.transform.OutputKeys;
+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 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.EnumeratedAttribute;
+import org.apache.tools.ant.types.Path;
+import org.w3c.dom.Document;
+
+
+/**
+ * Convenient task to run the snapshot merge utility for JProbe Coverage 3.0.
+ *
+ * @author Stephane Bailliez
+ */
+public class CovReport extends Task
+{
+ /*
+ * jpcoverport [options] -output=file -snapshot=snapshot.jpc
+ * jpcovreport [options] [-paramfile=file] -output= -snapshot=
+ * Generate a report based on the indicated snapshot
+ * -paramfile=file
+ * A text file containing the report generation options.
+ * -format=(html|text|xml) defaults to html
+ * The format of the generated report.
+ * -type=(executive|summary|detailed|verydetailed) defaults to detailed
+ * The type of report to be generated. For -format=xml,
+ * use -type=verydetailed to include source code lines.
+ * Note: A very detailed report can be VERY large.
+ * -percent=num Min 1 Max 101 Default 101
+ * An integer representing a percentage of coverage.
+ * Only methods with test case coverage less than the
+ * percentage are included in reports.
+ * -filters=string
+ * A comma-separated list of filters in the form
+ * .:V, where V can be I for Include or
+ * E for Exclude. For the default package, omit .
+ * -filters_method=string
+ * Optional. A comma-separated list of methods that
+ * correspond one-to-one with the entries in -filters.
+ * -output=string Must be specified
+ * The absolute path and file name for the generated
+ * report file.
+ * -snapshot=string Must be specified
+ * The absolute path and file name of the snapshot file.
+ * -inc_src_text=(on|off) defaults to on
+ * Include text of the source code lines.
+ * Only applies for -format=xml and -type=verydetailed.
+ * -sourcepath=string defaults to .
+ * A semicolon-separated list of source paths.
+ * *
+ * ** coverage home, mandatory
+ */
+ private File home = null;
+
+ /**
+ * format of generated report, optional
+ */
+ private String format = null;
+
+ /**
+ * the name of the output snapshot, mandatory
+ */
+ private File tofile = null;
+
+ /**
+ * type of report, optional
+ */
+ private String type = null;
+
+ /**
+ * threshold value for printing methods, optional
+ */
+ private Integer percent = null;
+
+ /**
+ * comma separated list of filters (???)
+ */
+ private String filters = null;
+
+ /**
+ * name of the snapshot file to create report from
+ */
+ private File snapshot = null;
+
+ /**
+ * sourcepath to use
+ */
+ private Path sourcePath = null;
+
+ /**
+ * include the text for each line of code (xml report verydetailed)
+ */
+ private boolean includeSource = true;
+
+ private Path coveragePath = null;
+
+ /**
+ */
+ private Reference reference = null;
+
+
+ public CovReport() { }
+
+ /**
+ * set the filters
+ *
+ * @param values The new Filters value
+ */
+ public void setFilters( String values )
+ {
+ this.filters = values;
+ }
+
+ /**
+ * set the format of the report html|text|xml
+ *
+ * @param value The new Format value
+ */
+ public void setFormat( ReportFormat value )
+ {
+ this.format = value.getValue();
+ }
+
+
+ /**
+ * Set the coverage home. it must point to JProbe coverage directories where
+ * are stored native libraries and jars.
+ *
+ * @param value The new Home value
+ */
+ public void setHome( File value )
+ {
+ this.home = value;
+ }
+
+ /**
+ * include source code lines. XML report only
+ *
+ * @param value The new Includesource value
+ */
+ public void setIncludesource( boolean value )
+ {
+ this.includeSource = value;
+ }
+
+ /**
+ * sets the threshold printing method 0-100
+ *
+ * @param value The new Percent value
+ */
+ public void setPercent( Integer value )
+ {
+ this.percent = value;
+ }
+
+ public void setSnapshot( File value )
+ {
+ this.snapshot = value;
+ }
+
+ /**
+ * Set the output snapshot file
+ *
+ * @param value The new Tofile value
+ */
+ public void setTofile( File value )
+ {
+ this.tofile = value;
+ }
+
+ /**
+ * sets the report type executive|summary|detailed|verydetailed
+ *
+ * @param value The new Type value
+ */
+ public void setType( ReportType value )
+ {
+ this.type = value.getValue();
+ }
+
+ //@todo to remove
+ public Path createCoveragepath()
+ {
+ if( coveragePath == null )
+ {
+ coveragePath = new Path( project );
+ }
+ return coveragePath.createPath();
+ }
+
+ public Reference createReference()
+ {
+ if( reference == null )
+ {
+ reference = new Reference();
+ }
+ return reference;
+ }
+
+ public Path createSourcepath()
+ {
+ if( sourcePath == null )
+ {
+ sourcePath = new Path( project );
+ }
+ return sourcePath.createPath();
+ }
+
+ public void execute()
+ throws BuildException
+ {
+ checkOptions();
+ try
+ {
+ Commandline cmdl = new Commandline();
+ // we need to run Coverage from his directory due to dll/jar issues
+ cmdl.setExecutable( new File( home, "jpcovreport" ).getAbsolutePath() );
+ String[] params = getParameters();
+ for( int i = 0; i < params.length; i++ )
+ {
+ cmdl.createArgument().setValue( params[i] );
+ }
+
+ // use the custom handler for stdin issues
+ LogStreamHandler handler = new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN );
+ Execute exec = new Execute( handler );
+ log( cmdl.toString(), Project.MSG_VERBOSE );
+ exec.setCommandline( cmdl.getCommandline() );
+ int exitValue = exec.execute();
+ if( exitValue != 0 )
+ {
+ throw new BuildException( "JProbe Coverage Report failed (" + exitValue + ")" );
+ }
+ log( "coveragePath: " + coveragePath, Project.MSG_VERBOSE );
+ log( "format: " + format, Project.MSG_VERBOSE );
+ if( reference != null && "xml".equals( format ) )
+ {
+ reference.createEnhancedXMLReport();
+ }
+
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Failed to execute JProbe Coverage Report.", e );
+ }
+ }
+
+
+ protected String[] getParameters()
+ {
+ Vector v = new Vector();
+ if( format != null )
+ {
+ v.addElement( "-format=" + format );
+ }
+ if( type != null )
+ {
+ v.addElement( "-type=" + type );
+ }
+ if( percent != null )
+ {
+ v.addElement( "-percent=" + percent );
+ }
+ if( filters != null )
+ {
+ v.addElement( "-filters=" + filters );
+ }
+ v.addElement( "-output=" + project.resolveFile( tofile.getPath() ) );
+ v.addElement( "-snapshot=" + project.resolveFile( snapshot.getPath() ) );
+ // as a default -sourcepath use . in JProbe, so use project .
+ if( sourcePath == null )
+ {
+ sourcePath = new Path( project );
+ sourcePath.createPath().setLocation( project.resolveFile( "." ) );
+ }
+ v.addElement( "-sourcepath=" + sourcePath );
+
+ if( "verydetailed".equalsIgnoreCase( format ) && "xml".equalsIgnoreCase( type ) )
+ {
+ v.addElement( "-inc_src_text=" + ( includeSource ? "on" : "off" ) );
+ }
+
+ String[] params = new String[v.size()];
+ v.copyInto( params );
+ return params;
+ }
+
+ /**
+ * check for mandatory options
+ *
+ * @exception BuildException Description of Exception
+ */
+ protected void checkOptions()
+ throws BuildException
+ {
+ if( tofile == null )
+ {
+ throw new BuildException( "'tofile' attribute must be set." );
+ }
+ if( snapshot == null )
+ {
+ throw new BuildException( "'snapshot' attribute must be set." );
+ }
+ if( home == null )
+ {
+ throw new BuildException( "'home' attribute must be set to JProbe home directory" );
+ }
+ home = new File( home, "coverage" );
+ File jar = new File( home, "coverage.jar" );
+ if( !jar.exists() )
+ {
+ throw new BuildException( "Cannot find Coverage directory: " + home );
+ }
+ if( reference != null && !"xml".equals( format ) )
+ {
+ log( "Ignored reference. It cannot be used in non XML report." );
+ reference = null;// nullify it so that there is no ambiguity
+ }
+
+ }
+
+ public static class ReportFormat extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"html", "text", "xml"};
+ }
+ }
+
+ public static class ReportType extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"executive", "summary", "detailed", "verydetailed"};
+ }
+ }
+
+
+ public class Reference
+ {
+ protected Path classPath;
+ protected ReportFilters filters;
+
+ public Path createClasspath()
+ {
+ if( classPath == null )
+ {
+ classPath = new Path( CovReport.this.project );
+ }
+ return classPath.createPath();
+ }
+
+ public ReportFilters createFilters()
+ {
+ if( filters == null )
+ {
+ filters = new ReportFilters();
+ }
+ return filters;
+ }
+
+ protected void createEnhancedXMLReport()
+ throws BuildException
+ {
+ // we need a classpath element
+ if( classPath == null )
+ {
+ throw new BuildException( "Need a 'classpath' element." );
+ }
+ // and a valid one...
+ String[] paths = classPath.list();
+ if( paths.length == 0 )
+ {
+ throw new BuildException( "Coverage path is invalid. It does not contain any existing path." );
+ }
+ // and we need at least one filter include/exclude.
+ if( filters == null || filters.size() == 0 )
+ {
+ createFilters();
+ log( "Adding default include filter to *.*()", Project.MSG_VERBOSE );
+ ReportFilters.Include include = new ReportFilters.Include();
+ filters.addInclude( include );
+ }
+ try
+ {
+ log( "Creating enhanced XML report", Project.MSG_VERBOSE );
+ XMLReport report = new XMLReport( CovReport.this, tofile );
+ report.setReportFilters( filters );
+ report.setJProbehome( new File( home.getParent() ) );
+ Document doc = report.createDocument( paths );
+ TransformerFactory tfactory = TransformerFactory.newInstance();
+ Transformer transformer = tfactory.newTransformer();
+ transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
+ transformer.setOutputProperty( OutputKeys.METHOD, "xml" );
+ Source src = new DOMSource( doc );
+ Result res = new StreamResult( "file:///" + tofile.toString() );
+ transformer.transform( src, res );
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( "Error while performing enhanced XML report from file " + tofile, e );
+ }
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Coverage.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Coverage.java
new file mode 100644
index 000000000..3dc028c44
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Coverage.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Random;
+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.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.EnumeratedAttribute;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Convenient task to run Sitraka JProbe Coverage from Ant. Options are pretty
+ * numerous, you'd better check the manual for a full descriptions of options.
+ * (not that simple since they differ from the online help, from the usage
+ * command line and from the examples...)
+ *
+ * For additional information, visit
+ * www.sitraka.com
+ *
+ * @author Stephane Bailliez
+ */
+public class Coverage extends Task
+{
+
+ protected Commandline cmdl = new Commandline();
+
+ protected CommandlineJava cmdlJava = new CommandlineJava();
+
+ protected String function = "coverage";
+
+ protected boolean applet = false;
+
+ /**
+ * this is a somewhat annoying thing, set it to never
+ */
+ protected String exitPrompt = "never";
+
+ protected Filters filters = new Filters();
+
+ protected String finalSnapshot = "coverage";
+
+ protected String recordFromStart = "coverage";
+
+ protected boolean trackNatives = false;
+
+ protected int warnLevel = 0;
+
+ protected Vector filesets = new Vector();
+
+ protected File home;
+
+ protected File inputFile;
+
+ protected File javaExe;
+
+ protected String seedName;
+
+ protected File snapshotDir;
+
+ protected Socket socket;
+
+ protected Triggers triggers;
+
+ protected String vm;
+
+ protected File workingDir;
+
+
+ //---------------- the tedious job begins here
+
+ public Coverage() { }
+
+ /**
+ * default to false unless file is htm or html
+ *
+ * @param value The new Applet value
+ */
+ public void setApplet( boolean value )
+ {
+ applet = value;
+ }
+
+ /**
+ * classname to run as standalone or runner for filesets
+ *
+ * @param value The new Classname value
+ */
+ public void setClassname( String value )
+ {
+ cmdlJava.setClassname( value );
+ }
+
+ /**
+ * always, error, never
+ *
+ * @param value The new Exitprompt value
+ */
+ public void setExitprompt( String value )
+ {
+ exitPrompt = value;
+ }
+
+ /**
+ * none, coverage, all. can be null, default to none
+ *
+ * @param value The new Finalsnapshot value
+ */
+ public void setFinalsnapshot( String value )
+ {
+ finalSnapshot = value;
+ }
+
+ //--------- setters used via reflection --
+
+ /**
+ * set the coverage home directory where are libraries, jars and jplauncher
+ *
+ * @param value The new Home value
+ */
+ public void setHome( File value )
+ {
+ home = value;
+ }
+
+ public void setInputfile( File value )
+ {
+ inputFile = value;
+ }
+
+ public void setJavaexe( File value )
+ {
+ javaExe = value;
+ }
+
+ /**
+ * all, coverage, none
+ *
+ * @param value The new Recordfromstart value
+ */
+ public void setRecordfromstart( Recordfromstart value )
+ {
+ recordFromStart = value.getValue();
+ }
+
+ /**
+ * seed name for snapshot file. can be null, default to snap
+ *
+ * @param value The new Seedname value
+ */
+ public void setSeedname( String value )
+ {
+ seedName = value;
+ }
+
+ public void setSnapshotdir( File value )
+ {
+ snapshotDir = value;
+ }
+
+ public void setTracknatives( boolean value )
+ {
+ trackNatives = value;
+ }
+
+ /**
+ * jdk117, jdk118 or java2, can be null, default to java2
+ *
+ * @param value The new Vm value
+ */
+ public void setVm( Javavm value )
+ {
+ vm = value.getValue();
+ }
+
+ public void setWarnlevel( Integer value )
+ {
+ warnLevel = value.intValue();
+ }
+
+ public void setWorkingdir( File value )
+ {
+ workingDir = value;
+ }
+
+ /**
+ * the classnames to execute
+ *
+ * @param fs The feature to be added to the Fileset attribute
+ */
+ public void addFileset( FileSet fs )
+ {
+ filesets.addElement( fs );
+ }
+
+ /**
+ * the command arguments
+ *
+ * @return Description of the Returned Value
+ */
+ public Commandline.Argument createArg()
+ {
+ return cmdlJava.createArgument();
+ }
+
+ /**
+ * classpath to run the files
+ *
+ * @return Description of the Returned Value
+ */
+ public Path createClasspath()
+ {
+ return cmdlJava.createClasspath( project ).createPath();
+ }
+
+ public Filters createFilters()
+ {
+ return filters;
+ }
+
+ //
+
+ /**
+ * the jvm arguments
+ *
+ * @return Description of the Returned Value
+ */
+ public Commandline.Argument createJvmarg()
+ {
+ return cmdlJava.createVmArgument();
+ }
+
+ public Socket createSocket()
+ {
+ if( socket == null )
+ {
+ socket = new Socket();
+ }
+ return socket;
+ }
+
+ public Triggers createTriggers()
+ {
+ if( triggers == null )
+ {
+ triggers = new Triggers();
+ }
+ return triggers;
+ }
+
+ /**
+ * execute the jplauncher by providing a parameter file
+ *
+ * @exception BuildException Description of Exception
+ */
+ public void execute()
+ throws BuildException
+ {
+ File paramfile = null;
+ // if an input file is used, all other options are ignored...
+ if( inputFile == null )
+ {
+ checkOptions();
+ paramfile = createParamFile();
+ }
+ else
+ {
+ paramfile = inputFile;
+ }
+ try
+ {
+ // we need to run Coverage from his directory due to dll/jar issues
+ cmdl.setExecutable( new File( home, "jplauncher" ).getAbsolutePath() );
+ cmdl.createArgument().setValue( "-jp_input=" + paramfile.getAbsolutePath() );
+
+ // use the custom handler for stdin issues
+ LogStreamHandler handler = new CoverageStreamHandler( this );
+ Execute exec = new Execute( handler );
+ log( cmdl.toString(), Project.MSG_VERBOSE );
+ exec.setCommandline( cmdl.getCommandline() );
+ int exitValue = exec.execute();
+ if( exitValue != 0 )
+ {
+ throw new BuildException( "JProbe Coverage failed (" + exitValue + ")" );
+ }
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Failed to execute JProbe Coverage.", e );
+ }
+ finally
+ {
+ //@todo should be removed once switched to JDK1.2
+ if( inputFile == null && paramfile != null )
+ {
+ paramfile.delete();
+ }
+ }
+ }
+
+ /**
+ * return the command line parameters. Parameters can either be passed to
+ * the command line and stored to a file (then use the
+ * -jp_input=<filename>) if they are too numerous.
+ *
+ * @return The Parameters value
+ */
+ protected String[] getParameters()
+ {
+ Vector params = new Vector();
+ params.addElement( "-jp_function=" + function );
+ if( vm != null )
+ {
+ params.addElement( "-jp_vm=" + vm );
+ }
+ if( javaExe != null )
+ {
+ params.addElement( "-jp_java_exe=" + project.resolveFile( javaExe.getPath() ) );
+ }
+ params.addElement( "-jp_working_dir=" + workingDir.getPath() );
+ params.addElement( "-jp_snapshot_dir=" + snapshotDir.getPath() );
+ params.addElement( "-jp_record_from_start=" + recordFromStart );
+ params.addElement( "-jp_warn=" + warnLevel );
+ if( seedName != null )
+ {
+ params.addElement( "-jp_output_file=" + seedName );
+ }
+ params.addElement( "-jp_filter=" + filters.toString() );
+ if( triggers != null )
+ {
+ params.addElement( "-jp_trigger=" + triggers.toString() );
+ }
+ if( finalSnapshot != null )
+ {
+ params.addElement( "-jp_final_snapshot=" + finalSnapshot );
+ }
+ params.addElement( "-jp_exit_prompt=" + exitPrompt );
+ //params.addElement("-jp_append=" + append);
+ params.addElement( "-jp_track_natives=" + trackNatives );
+ //.... now the jvm
+ // arguments
+ String[] vmargs = cmdlJava.getVmCommand().getArguments();
+ for( int i = 0; i < vmargs.length; i++ )
+ {
+ params.addElement( vmargs[i] );
+ }
+ // classpath
+ Path classpath = cmdlJava.getClasspath();
+ if( classpath != null && classpath.size() > 0 )
+ {
+ params.addElement( "-classpath " + classpath.toString() );
+ }
+ // classname (runner or standalone)
+ if( cmdlJava.getClassname() != null )
+ {
+ params.addElement( cmdlJava.getClassname() );
+ }
+ // arguments for classname
+ String[] args = cmdlJava.getJavaCommand().getArguments();
+ for( int i = 0; i < args.length; i++ )
+ {
+ params.addElement( args[i] );
+ }
+
+ String[] array = new String[params.size()];
+ params.copyInto( array );
+ return array;
+ }
+
+ /**
+ * wheck what is necessary to check, Coverage will do the job for us
+ *
+ * @exception BuildException Description of Exception
+ */
+ protected void checkOptions()
+ throws BuildException
+ {
+ // check coverage home
+ if( home == null || !home.isDirectory() )
+ {
+ throw new BuildException( "Invalid home directory. Must point to JProbe home directory" );
+ }
+ home = new File( home, "coverage" );
+ File jar = new File( home, "coverage.jar" );
+ if( !jar.exists() )
+ {
+ throw new BuildException( "Cannot find Coverage directory: " + home );
+ }
+
+ // make sure snapshot dir exists and is resolved
+ if( snapshotDir == null )
+ {
+ snapshotDir = new File( "." );
+ }
+ snapshotDir = project.resolveFile( snapshotDir.getPath() );
+ if( !snapshotDir.isDirectory() || !snapshotDir.exists() )
+ {
+ throw new BuildException( "Snapshot directory does not exists :" + snapshotDir );
+ }
+ if( workingDir == null )
+ {
+ workingDir = new File( "." );
+ }
+ workingDir = project.resolveFile( workingDir.getPath() );
+
+ // check for info, do your best to select the java executable.
+ // JProbe 3.0 fails if there is no javaexe option. So
+ if( javaExe == null && ( vm == null || "java2".equals( vm ) ) )
+ {
+ String version = System.getProperty( "java.version" );
+ // make we are using 1.2+, if it is, then do your best to
+ // get a javaexe
+ if( !version.startsWith( "1.1" ) )
+ {
+ if( vm == null )
+ {
+ vm = "java2";
+ }
+ // if we are here obviously it is java2
+ String home = System.getProperty( "java.home" );
+ boolean isUnix = File.separatorChar == '/';
+ javaExe = isUnix ? new File( home, "bin/java" ) : new File( home, "/bin/java.exe" );
+ }
+ }
+ }
+
+
+ /**
+ * create the parameter file from the given options. The file is created
+ * with a random name in the current directory.
+ *
+ * @return the file object where are written the configuration to run JProbe
+ * Coverage
+ * @throws BuildException thrown if something bad happens while writing the
+ * arguments to the file.
+ */
+ protected File createParamFile()
+ throws BuildException
+ {
+ //@todo change this when switching to JDK 1.2 and use File.createTmpFile()
+ File file = createTmpFile();
+ log( "Creating parameter file: " + file, Project.MSG_VERBOSE );
+
+ // options need to be one per line in the parameter file
+ // so write them all in a single string
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter( sw );
+ String[] params = getParameters();
+ for( int i = 0; i < params.length; i++ )
+ {
+ pw.println( params[i] );
+ }
+ pw.flush();
+ log( "JProbe Coverage parameters:\n" + sw.toString(), Project.MSG_VERBOSE );
+
+ // now write them to the file
+ FileWriter fw = null;
+ try
+ {
+ fw = new FileWriter( file );
+ fw.write( sw.toString() );
+ fw.flush();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( "Could not write parameter file " + file, e );
+ }
+ finally
+ {
+ if( fw != null )
+ {
+ try
+ {
+ fw.close();
+ }
+ catch( IOException ignored )
+ {
+ }
+ }
+ }
+ return file;
+ }
+
+ /**
+ * create a temporary file in the current dir (For JDK1.1 support)
+ *
+ * @return Description of the Returned Value
+ */
+ protected File createTmpFile()
+ {
+ final long rand = ( new Random( System.currentTimeMillis() ) ).nextLong();
+ File file = new File( "jpcoverage" + rand + ".tmp" );
+ return file;
+ }
+
+ public static class Finalsnapshot extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"coverage", "none", "all"};
+ }
+ }
+
+ public static class Javavm extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"java2", "jdk118", "jdk117"};
+ }
+ }
+
+ public static class Recordfromstart extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"coverage", "none", "all"};
+ }
+ }
+
+ /**
+ * specific pumper to avoid those nasty stdin issues
+ *
+ * @author RT
+ */
+ static class CoverageStreamHandler extends LogStreamHandler
+ {
+ CoverageStreamHandler( Task task )
+ {
+ super( task, Project.MSG_INFO, Project.MSG_WARN );
+ }
+
+ /**
+ * there are some issues concerning all JProbe executable In our case a
+ * 'Press ENTER to close this window..." will be displayed in the
+ * current window waiting for enter. So I'm closing the stream right
+ * away to avoid problems.
+ *
+ * @param os The new ProcessInputStream value
+ */
+ public void setProcessInputStream( OutputStream os )
+ {
+ try
+ {
+ os.close();
+ }
+ catch( IOException ignored )
+ {
+ }
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Filters.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Filters.java
new file mode 100644
index 000000000..6b6c9b345
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Filters.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.optional.sitraka;
+import java.util.Vector;
+
+/**
+ * Filters information from coverage, somewhat similar to a FileSet .
+ *
+ * @author Stephane Bailliez
+ */
+public class Filters
+{
+
+ /**
+ * default regexp to exclude everything
+ */
+ public final static String DEFAULT_EXCLUDE = "*.*():E";
+
+ /**
+ * say whether we should use the default excludes or not
+ */
+ protected boolean defaultExclude = true;
+
+ /**
+ * user defined filters
+ */
+ protected Vector filters = new Vector();
+
+ public Filters() { }
+
+ public void setDefaultExclude( boolean value )
+ {
+ defaultExclude = value;
+ }
+
+ public void addExclude( Exclude excl )
+ {
+ filters.addElement( excl );
+ }
+
+ public void addInclude( Include incl )
+ {
+ filters.addElement( incl );
+ }
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ final int size = filters.size();
+ if( defaultExclude )
+ {
+ buf.append( DEFAULT_EXCLUDE );
+ if( size > 0 )
+ {
+ buf.append( ',' );
+ }
+ }
+ for( int i = 0; i < size; i++ )
+ {
+ buf.append( filters.elementAt( i ).toString() );
+ if( i < size - 1 )
+ {
+ buf.append( ',' );
+ }
+ }
+ return buf.toString();
+ }
+
+ public static class Exclude extends FilterElement
+ {
+ public String toString()
+ {
+ return super.toString() + ":E" + ( enabled ? "" : "#" );
+ }
+ }
+
+ public abstract static class FilterElement
+ {
+ protected String method = "*";// default is all methods
+ protected boolean enabled = true;
+ protected String clazz;
+
+ public void setClass( String value )
+ {
+ clazz = value;
+ }
+
+ public void setEnabled( boolean value )
+ {
+ enabled = value;
+ }
+
+ public void setMethod( String value )
+ {
+ method = value;
+ }// default is enable
+
+ public void setName( String value )
+ {// this one is deprecated.
+ clazz = value;
+ }
+
+ public String toString()
+ {
+ return clazz + "." + method + "()";
+ }
+ }
+
+ public static class Include extends FilterElement
+ {
+ public String toString()
+ {
+ return super.toString() + ":I" + ( enabled ? "" : "#" );
+ }
+ }
+}
+
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/ReportFilters.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/ReportFilters.java
new file mode 100644
index 000000000..d8b5f2307
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/ReportFilters.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka;
+import java.util.Vector;
+import org.apache.tools.ant.util.regexp.RegexpMatcher;
+import org.apache.tools.ant.util.regexp.RegexpMatcherFactory;
+
+/**
+ * Filters information from coverage, somewhat similar to a FileSet .
+ *
+ * @author Stephane Bailliez
+ */
+public class ReportFilters
+{
+
+ /**
+ * user defined filters
+ */
+ protected Vector filters = new Vector();
+
+ /**
+ * cached matcher for each filter
+ */
+ protected Vector matchers = null;
+
+ public ReportFilters() { }
+
+ /**
+ * Check whether a given <classname><method>() is accepted by
+ * the list of filters or not.
+ *
+ * @param methodname the full method name in the format
+ * <classname><method>()
+ * @return Description of the Returned Value
+ */
+ public boolean accept( String methodname )
+ {
+ // I'm deferring matcher instantiations at runtime to avoid computing
+ // the filters at parsing time
+ if( matchers == null )
+ {
+ createMatchers();
+ }
+ boolean result = false;
+ // assert filters.size() == matchers.size()
+ final int size = filters.size();
+ for( int i = 0; i < size; i++ )
+ {
+ FilterElement filter = ( FilterElement )filters.elementAt( i );
+ RegexpMatcher matcher = ( RegexpMatcher )matchers.elementAt( i );
+ if( filter instanceof Include )
+ {
+ result = result || matcher.matches( methodname );
+ }
+ else if( filter instanceof Exclude )
+ {
+ result = result && !matcher.matches( methodname );
+ }
+ else
+ {
+ //not possible
+ throw new IllegalArgumentException( "Invalid filter element: " + filter.getClass().getName() );
+ }
+ }
+ return result;
+ }
+
+ public void addExclude( Exclude excl )
+ {
+ filters.addElement( excl );
+ }
+
+ public void addInclude( Include incl )
+ {
+ filters.addElement( incl );
+ }
+
+ public int size()
+ {
+ return filters.size();
+ }
+
+ /**
+ * should be called only once to cache matchers
+ */
+ protected void createMatchers()
+ {
+ RegexpMatcherFactory factory = new RegexpMatcherFactory();
+ final int size = filters.size();
+ matchers = new Vector();
+ for( int i = 0; i < size; i++ )
+ {
+ FilterElement filter = ( FilterElement )filters.elementAt( i );
+ RegexpMatcher matcher = factory.newRegexpMatcher();
+ String pattern = filter.getAsPattern();
+ matcher.setPattern( pattern );
+ matchers.addElement( matcher );
+ }
+ }
+
+ /**
+ * concrete exclude class
+ *
+ * @author RT
+ */
+ public static class Exclude extends FilterElement
+ {
+ }
+
+
+ /**
+ * default abstract filter element class
+ *
+ * @author RT
+ */
+ public abstract static class FilterElement
+ {
+ protected String clazz = "*";// default is all classes
+ protected String method = "*";// default is all methods
+
+ public void setClass( String value )
+ {
+ clazz = value;
+ }
+
+ public void setMethod( String value )
+ {
+ method = value;
+ }
+
+ public String getAsPattern()
+ {
+ StringBuffer buf = new StringBuffer( toString() );
+ StringUtil.replace( buf, ".", "\\." );
+ StringUtil.replace( buf, "*", ".*" );
+ StringUtil.replace( buf, "(", "\\(" );
+ StringUtil.replace( buf, ")", "\\)" );
+ return buf.toString();
+ }
+
+ public String toString()
+ {
+ return clazz + "." + method + "()";
+ }
+ }
+
+ /**
+ * concrete include class
+ *
+ * @author RT
+ */
+ public static class Include extends FilterElement
+ {
+ }
+}
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Socket.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Socket.java
new file mode 100644
index 000000000..2199077a8
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Socket.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.sitraka;
+
+/**
+ * Socket element for connection. <socket/> defaults to host
+ * 127.0.0.1 and port 4444 Otherwise it requires the host and port attributes to
+ * be set: <socket host="e;175.30.12.1"e;
+ * port="e;4567"e;/>
+ *
+ * @author RT
+ */
+public class Socket
+{
+
+ /**
+ * default to localhost
+ */
+ private String host = "127.0.0.1";
+
+ /**
+ * default to 4444
+ */
+ private int port = 4444;
+
+ public void setHost( String value )
+ {
+ host = value;
+ }
+
+ public void setPort( Integer value )
+ {
+ port = value.intValue();
+ }
+
+ /**
+ * if no host is set, returning ':<port>', will take localhost
+ *
+ * @return Description of the Returned Value
+ */
+ public String toString()
+ {
+ return host + ":" + port;
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/StringUtil.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/StringUtil.java
new file mode 100644
index 000000000..5675959ed
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/StringUtil.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.taskdefs.optional.sitraka;
+
+/**
+ * String utilities method.
+ *
+ * @author Stephane Bailliez
+ */
+public final class StringUtil
+{
+ /**
+ * private constructor, it's a utility class
+ */
+ private StringUtil() { }
+
+ /**
+ * Replaces all occurences of find with replacement in the
+ * source StringBuffer.
+ *
+ * @param src the original string buffer to modify.
+ * @param find the string to be replaced.
+ * @param replacement the replacement string for find matches.
+ */
+ public static void replace( StringBuffer src, String find, String replacement )
+ {
+ int index = 0;
+ while( index < src.length() )
+ {
+ index = src.toString().indexOf( find, index );
+ if( index == -1 )
+ {
+ break;
+ }
+ src.delete( index, index + find.length() );
+ src.insert( index, replacement );
+ index += replacement.length() + 1;
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Triggers.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Triggers.java
new file mode 100644
index 000000000..c7d716f44
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Triggers.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.optional.sitraka;
+import java.util.Hashtable;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Trigger information. It will return as a command line argument by calling the
+ * toString() method.
+ *
+ * @author Stephane Bailliez
+ */
+public class Triggers
+{
+
+ /**
+ * mapping of actions to cryptic command line mnemonics
+ */
+ private final static Hashtable actionMap = new Hashtable( 3 );
+
+ /**
+ * mapping of events to cryptic command line mnemonics
+ */
+ private final static Hashtable eventMap = new Hashtable( 3 );
+
+ protected Vector triggers = new Vector();
+
+ static
+ {
+ actionMap.put( "enter", "E" );
+ actionMap.put( "exit", "X" );
+ // clear|pause|resume|snapshot|suspend|exit
+ eventMap.put( "clear", "C" );
+ eventMap.put( "pause", "P" );
+ eventMap.put( "resume", "R" );
+ eventMap.put( "snapshot", "S" );
+ eventMap.put( "suspend", "A" );
+ eventMap.put( "exit", "X" );
+ }
+
+ public Triggers() { }
+
+ public void addMethod( Method method )
+ {
+ triggers.addElement( method );
+ }
+
+ // -jp_trigger=ClassName.*():E:S,ClassName.MethodName():X:X
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ final int size = triggers.size();
+ for( int i = 0; i < size; i++ )
+ {
+ buf.append( triggers.elementAt( i ).toString() );
+ if( i < size - 1 )
+ {
+ buf.append( ',' );
+ }
+ }
+ return buf.toString();
+ }
+
+
+ public static class Method
+ {
+ protected String action;
+ protected String event;
+ protected String name;
+ protected String param;
+
+ public void setAction( String value )
+ throws BuildException
+ {
+ if( actionMap.get( value ) == null )
+ {
+ throw new BuildException( "Invalid action, must be one of " + actionMap );
+ }
+ action = value;
+ }
+
+ public void setEvent( String value )
+ {
+ if( eventMap.get( value ) == null )
+ {
+ throw new BuildException( "Invalid event, must be one of " + eventMap );
+ }
+ event = value;
+ }
+
+ public void setName( String value )
+ {
+ name = value;
+ }
+
+ public void setParam( String value )
+ {
+ param = value;
+ }
+
+ // return ::[:param]
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append( name ).append( ":" );//@todo name must not be null, check for it
+ buf.append( eventMap.get( event ) ).append( ":" );
+ buf.append( actionMap.get( action ) );
+ if( param != null )
+ {
+ buf.append( ":" ).append( param );
+ }
+ return buf.toString();
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/XMLReport.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/XMLReport.java
new file mode 100644
index 000000000..f6ce7d400
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/XMLReport.java
@@ -0,0 +1,676 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka;
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.NoSuchElementException;
+import java.util.Vector;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.ClassFile;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.ClassPathLoader;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.MethodInfo;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.Utils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+/**
+ * Little hack to process XML report from JProbe. It will fix some reporting
+ * errors from JProbe 3.0 and makes use of a reference classpath to add
+ * classes/methods that were not reported by JProbe as being used (ie loaded)
+ *
+ * @author Stephane Bailliez
+ */
+public class XMLReport
+{
+
+ /**
+ * mapping of class names to ClassFiles from the reference
+ * classpath. It is used to filter the JProbe report.
+ */
+ protected Hashtable classFiles;
+
+ /**
+ * mapping classname / class node for faster access
+ */
+ protected Hashtable classMap;
+
+ /**
+ * the XML file to process just from CovReport
+ */
+ protected File file;
+
+ /**
+ * method filters
+ */
+ protected ReportFilters filters;
+
+ /**
+ * jprobe home path. It is used to get the DTD
+ */
+ protected File jprobeHome;
+
+ /**
+ * mapping package name / package node for faster access
+ */
+ protected Hashtable pkgMap;
+
+ /**
+ * parsed document
+ */
+ protected Document report;
+ /**
+ * task caller, can be null, used for logging purpose
+ */
+ protected Task task;
+
+ /**
+ * create a new XML report, logging will be on stdout
+ *
+ * @param file Description of Parameter
+ */
+ public XMLReport( File file )
+ {
+ this( null, file );
+ }
+
+ /**
+ * create a new XML report, logging done on the task
+ *
+ * @param task Description of Parameter
+ * @param file Description of Parameter
+ */
+ public XMLReport( Task task, File file )
+ {
+ this.file = file;
+ this.task = task;
+ }
+
+ private static DocumentBuilder newBuilder()
+ {
+ try
+ {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringComments( true );
+ factory.setValidating( false );
+ return factory.newDocumentBuilder();
+ }
+ catch( Exception e )
+ {
+ throw new ExceptionInInitializerError( e );
+ }
+ }
+
+ /**
+ * set the JProbe home path. Used to get the DTD
+ *
+ * @param home The new JProbehome value
+ */
+ public void setJProbehome( File home )
+ {
+ jprobeHome = home;
+ }
+
+ /**
+ * set the
+ *
+ * @param filters The new ReportFilters value
+ */
+ public void setReportFilters( ReportFilters filters )
+ {
+ this.filters = filters;
+ }
+
+ /**
+ * create the whole new document
+ *
+ * @param classPath Description of Parameter
+ * @return Description of the Returned Value
+ * @exception Exception Description of Exception
+ */
+ public Document createDocument( String[] classPath )
+ throws Exception
+ {
+
+ // Iterate over the classpath to identify reference classes
+ classFiles = new Hashtable();
+ ClassPathLoader cpl = new ClassPathLoader( classPath );
+ Enumeration enum = cpl.loaders();
+ while( enum.hasMoreElements() )
+ {
+ ClassPathLoader.FileLoader fl = ( ClassPathLoader.FileLoader )enum.nextElement();
+ ClassFile[] classes = fl.getClasses();
+ log( "Processing " + classes.length + " classes in " + fl.getFile() );
+ // process all classes
+ for( int i = 0; i < classes.length; i++ )
+ {
+ classFiles.put( classes[i].getFullName(), classes[i] );
+ }
+ }
+
+ // Load the JProbe coverage XML report
+ DocumentBuilder dbuilder = newBuilder();
+ InputSource is = new InputSource( new FileInputStream( file ) );
+ if( jprobeHome != null )
+ {
+ File dtdDir = new File( jprobeHome, "dtd" );
+ is.setSystemId( "file:///" + dtdDir.getAbsolutePath() + "/" );
+ }
+ report = dbuilder.parse( is );
+ report.normalize();
+
+ // create maps for faster node access (also filters out unwanted nodes)
+ createNodeMaps();
+
+ // Make sure each class from the reference path ends up in the report
+ Enumeration classes = classFiles.elements();
+ while( classes.hasMoreElements() )
+ {
+ ClassFile cf = ( ClassFile )classes.nextElement();
+ serializeClass( cf );
+ }
+ // update the document with the stats
+ update();
+ return report;
+ }
+
+ public void log( String message )
+ {
+ if( task == null )
+ {
+ //System.out.println(message);
+ }
+ else
+ {
+ task.log( message, Project.MSG_DEBUG );
+ }
+ }
+
+ protected Element[] getClasses( Element pkg )
+ {
+ Vector v = new Vector();
+ NodeList children = pkg.getChildNodes();
+ int len = children.getLength();
+ for( int i = 0; i < len; i++ )
+ {
+ Node child = children.item( i );
+ if( child.getNodeType() == Node.ELEMENT_NODE )
+ {
+ Element elem = ( Element )child;
+ if( "class".equals( elem.getNodeName() ) )
+ {
+ v.addElement( elem );
+ }
+ }
+ }
+ Element[] elems = new Element[v.size()];
+ v.copyInto( elems );
+ return elems;
+ }
+
+ protected Element getCovDataChild( Element parent )
+ {
+ NodeList children = parent.getChildNodes();
+ int len = children.getLength();
+ for( int i = 0; i < len; i++ )
+ {
+ Node child = children.item( i );
+ if( child.getNodeType() == Node.ELEMENT_NODE )
+ {
+ Element elem = ( Element )child;
+ if( "cov.data".equals( elem.getNodeName() ) )
+ {
+ return elem;
+ }
+ }
+ }
+ throw new NoSuchElementException( "Could not find 'cov.data' element in parent '" + parent.getNodeName() + "'" );
+ }
+
+ protected Vector getFilteredMethods( ClassFile classFile )
+ {
+ MethodInfo[] methodlist = classFile.getMethods();
+ Vector methods = new Vector( methodlist.length );
+ for( int i = 0; i < methodlist.length; i++ )
+ {
+ MethodInfo method = methodlist[i];
+ String signature = getMethodSignature( classFile, method );
+ if( filters.accept( signature ) )
+ {
+ methods.addElement( method );
+ log( "keeping " + signature );
+ }
+ else
+ {
+// log("discarding " + signature);
+ }
+ }
+ return methods;
+ }
+
+ /**
+ * JProbe does not put the java.lang prefix for classes in this package, so
+ * used this nice method so that I have the same signature for methods
+ *
+ * @param method Description of Parameter
+ * @return The MethodSignature value
+ */
+ protected String getMethodSignature( MethodInfo method )
+ {
+ StringBuffer buf = new StringBuffer( method.getName() );
+ buf.append( "(" );
+ String[] params = method.getParametersType();
+ for( int i = 0; i < params.length; i++ )
+ {
+ String type = params[i];
+ int pos = type.lastIndexOf( '.' );
+ if( pos != -1 )
+ {
+ String pkg = type.substring( 0, pos );
+ if( "java.lang".equals( pkg ) )
+ {
+ params[i] = type.substring( pos + 1 );
+ }
+ }
+ buf.append( params[i] );
+ if( i != params.length - 1 )
+ {
+ buf.append( ", " );
+ }
+ }
+ buf.append( ")" );
+ return buf.toString();
+ }
+
+ /**
+ * Convert to a CovReport-like signature ie,
+ * <classname>.<method>()
+ *
+ * @param clazz Description of Parameter
+ * @param method Description of Parameter
+ * @return The MethodSignature value
+ */
+ protected String getMethodSignature( ClassFile clazz, MethodInfo method )
+ {
+ StringBuffer buf = new StringBuffer( clazz.getFullName() );
+ buf.append( "." );
+ buf.append( method.getName() );
+ buf.append( "()" );
+ return buf.toString();
+ }
+
+ protected Hashtable getMethods( Element clazz )
+ {
+ Hashtable map = new Hashtable();
+ NodeList children = clazz.getChildNodes();
+ int len = children.getLength();
+ for( int i = 0; i < len; i++ )
+ {
+ Node child = children.item( i );
+ if( child.getNodeType() == Node.ELEMENT_NODE )
+ {
+ Element elem = ( Element )child;
+ if( "method".equals( elem.getNodeName() ) )
+ {
+ String name = elem.getAttribute( "name" );
+ map.put( name, elem );
+ }
+ }
+ }
+ return map;
+ }
+
+ protected Element[] getPackages( Element snapshot )
+ {
+ Vector v = new Vector();
+ NodeList children = snapshot.getChildNodes();
+ int len = children.getLength();
+ for( int i = 0; i < len; i++ )
+ {
+ Node child = children.item( i );
+ if( child.getNodeType() == Node.ELEMENT_NODE )
+ {
+ Element elem = ( Element )child;
+ if( "package".equals( elem.getNodeName() ) )
+ {
+ v.addElement( elem );
+ }
+ }
+ }
+ Element[] elems = new Element[v.size()];
+ v.copyInto( elems );
+ return elems;
+ }
+
+ /**
+ * create an empty class element with its default cov.data (0)
+ *
+ * @param classFile Description of Parameter
+ * @return Description of the Returned Value
+ */
+ protected Element createClassElement( ClassFile classFile )
+ {
+ // create the class element
+ Element classElem = report.createElement( "class" );
+ classElem.setAttribute( "name", classFile.getName() );
+ // source file possibly does not exist in the bytecode
+ if( null != classFile.getSourceFile() )
+ {
+ classElem.setAttribute( "source", classFile.getSourceFile() );
+ }
+ // create the cov.data elem
+ Element classData = report.createElement( "cov.data" );
+ classElem.appendChild( classData );
+ // create the class cov.data element
+ classData.setAttribute( "calls", "0" );
+ classData.setAttribute( "hit_methods", "0" );
+ classData.setAttribute( "total_methods", "0" );
+ classData.setAttribute( "hit_lines", "0" );
+ classData.setAttribute( "total_lines", "0" );
+ return classElem;
+ }
+
+ /**
+ * create an empty method element with its cov.data values
+ *
+ * @param method Description of Parameter
+ * @return Description of the Returned Value
+ */
+ protected Element createMethodElement( MethodInfo method )
+ {
+ String methodsig = getMethodSignature( method );
+ Element methodElem = report.createElement( "method" );
+ methodElem.setAttribute( "name", methodsig );
+ // create the method cov.data element
+ Element methodData = report.createElement( "cov.data" );
+ methodElem.appendChild( methodData );
+ methodData.setAttribute( "calls", "0" );
+ methodData.setAttribute( "hit_lines", "0" );
+ methodData.setAttribute( "total_lines", String.valueOf( method.getNumberOfLines() ) );
+ return methodElem;
+ }
+
+
+ /**
+ * create node maps so that we can access node faster by their name
+ */
+ protected void createNodeMaps()
+ {
+ pkgMap = new Hashtable();
+ classMap = new Hashtable();
+ // create a map index of all packages by their name
+ // @todo can be done faster by direct access.
+ NodeList packages = report.getElementsByTagName( "package" );
+ final int pkglen = packages.getLength();
+ log( "Indexing " + pkglen + " packages" );
+ for( int i = pkglen - 1; i > -1; i-- )
+ {
+ Element pkg = ( Element )packages.item( i );
+ String pkgname = pkg.getAttribute( "name" );
+
+ int nbclasses = 0;
+ // create a map index of all classes by their fully
+ // qualified name.
+ NodeList classes = pkg.getElementsByTagName( "class" );
+ final int classlen = classes.getLength();
+ log( "Indexing " + classlen + " classes in package " + pkgname );
+ for( int j = classlen - 1; j > -1; j-- )
+ {
+ Element clazz = ( Element )classes.item( j );
+ String classname = clazz.getAttribute( "name" );
+ if( pkgname != null && pkgname.length() != 0 )
+ {
+ classname = pkgname + "." + classname;
+ }
+
+ int nbmethods = 0;
+ NodeList methods = clazz.getElementsByTagName( "method" );
+ final int methodlen = methods.getLength();
+ for( int k = methodlen - 1; k > -1; k-- )
+ {
+ Element meth = ( Element )methods.item( k );
+ StringBuffer methodname = new StringBuffer( meth.getAttribute( "name" ) );
+ methodname.delete( methodname.toString().indexOf( "(" ), methodname.toString().length() );
+ String signature = classname + "." + methodname + "()";
+ if( filters.accept( signature ) )
+ {
+ log( "kept method:" + signature );
+ nbmethods++;
+ }
+ else
+ {
+ clazz.removeChild( meth );
+ }
+ }
+ // if we don't keep any method, we don't keep the class
+ if( nbmethods != 0 && classFiles.containsKey( classname ) )
+ {
+ log( "Adding class '" + classname + "'" );
+ classMap.put( classname, clazz );
+ nbclasses++;
+ }
+ else
+ {
+ pkg.removeChild( clazz );
+ }
+ }
+ if( nbclasses != 0 )
+ {
+ log( "Adding package '" + pkgname + "'" );
+ pkgMap.put( pkgname, pkg );
+ }
+ else
+ {
+ pkg.getParentNode().removeChild( pkg );
+ }
+ }
+ log( "Indexed " + classMap.size() + " classes in " + pkgMap.size() + " packages" );
+ }
+
+ /**
+ * create an empty package element with its default cov.data (0)
+ *
+ * @param pkgname Description of Parameter
+ * @return Description of the Returned Value
+ */
+ protected Element createPackageElement( String pkgname )
+ {
+ Element pkgElem = report.createElement( "package" );
+ pkgElem.setAttribute( "name", pkgname );
+ // create the package cov.data element / default
+ // must be updated at the end of the whole process
+ Element pkgData = report.createElement( "cov.data" );
+ pkgElem.appendChild( pkgData );
+ pkgData.setAttribute( "calls", "0" );
+ pkgData.setAttribute( "hit_methods", "0" );
+ pkgData.setAttribute( "total_methods", "0" );
+ pkgData.setAttribute( "hit_lines", "0" );
+ pkgData.setAttribute( "total_lines", "0" );
+ return pkgElem;
+ }
+
+ /**
+ * Do additional work on an element to remove abstract methods that are
+ * reported by JProbe 3.0
+ *
+ * @param classFile Description of Parameter
+ * @param classNode Description of Parameter
+ */
+ protected void removeAbstractMethods( ClassFile classFile, Element classNode )
+ {
+ MethodInfo[] methods = classFile.getMethods();
+ Hashtable methodNodeList = getMethods( classNode );
+ // assert xmlMethods.size() == methods.length()
+ final int size = methods.length;
+ for( int i = 0; i < size; i++ )
+ {
+ MethodInfo method = methods[i];
+ String methodSig = getMethodSignature( method );
+ Element methodNode = ( Element )methodNodeList.get( methodSig );
+ if( methodNode != null &&
+ Utils.isAbstract( method.getAccessFlags() ) )
+ {
+ log( "\tRemoving abstract method " + methodSig );
+ classNode.removeChild( methodNode );
+ }
+ }
+ }
+
+ /**
+ * serialize a classfile into XML
+ *
+ * @param classFile Description of Parameter
+ */
+ protected void serializeClass( ClassFile classFile )
+ {
+ // the class already is reported so ignore it
+ String fullclassname = classFile.getFullName();
+ log( "Looking for '" + fullclassname + "'" );
+ Element clazz = ( Element )classMap.get( fullclassname );
+
+ // ignore classes that are already reported, all the information is
+ // already there.
+ if( clazz != null )
+ {
+ log( "Ignoring " + fullclassname );
+ removeAbstractMethods( classFile, clazz );
+ return;
+ }
+
+ // ignore interfaces files, there is no code in there to cover.
+ if( Utils.isInterface( classFile.getAccess() ) )
+ {
+ return;
+ }
+
+ Vector methods = getFilteredMethods( classFile );
+ // no need to process, there are no methods to add for this class.
+ if( methods.size() == 0 )
+ {
+ return;
+ }
+
+ String pkgname = classFile.getPackage();
+ // System.out.println("Looking for package " + pkgname);
+ Element pkgElem = ( Element )pkgMap.get( pkgname );
+ if( pkgElem == null )
+ {
+ pkgElem = createPackageElement( pkgname );
+ report.getDocumentElement().appendChild( pkgElem );
+ pkgMap.put( pkgname, pkgElem );// add the pkg to the map
+ }
+ // this is a brand new class, so we have to create a new node
+
+ // create the class element
+ Element classElem = createClassElement( classFile );
+ pkgElem.appendChild( classElem );
+
+ int total_lines = 0;
+ int total_methods = 0;
+ for( int i = 0; i < methods.size(); i++ )
+ {
+ // create the method element
+ MethodInfo method = ( MethodInfo )methods.elementAt( i );
+ if( Utils.isAbstract( method.getAccessFlags() ) )
+ {
+ continue;// no need to report abstract methods
+ }
+ Element methodElem = createMethodElement( method );
+ classElem.appendChild( methodElem );
+ total_lines += method.getNumberOfLines();
+ total_methods++;
+ }
+ // create the class cov.data element
+ Element classData = getCovDataChild( classElem );
+ classData.setAttribute( "total_methods", String.valueOf( total_methods ) );
+ classData.setAttribute( "total_lines", String.valueOf( total_lines ) );
+
+ // add itself to the node map
+ classMap.put( fullclassname, classElem );
+ }
+
+
+ /**
+ * update the count of the XML, that is accumulate the stats on methods,
+ * classes and package so that the numbers are valid according to the info
+ * that was appended to the XML.
+ */
+ protected void update()
+ {
+ int calls = 0;
+ int hit_methods = 0;
+ int total_methods = 0;
+ int hit_lines = 0;
+ int total_lines = 0;
+
+ // use the map for access, all nodes should be there
+ Enumeration enum = pkgMap.elements();
+ while( enum.hasMoreElements() )
+ {
+ Element pkgElem = ( Element )enum.nextElement();
+ String pkgname = pkgElem.getAttribute( "name" );
+ Element[] classes = getClasses( pkgElem );
+ int pkg_calls = 0;
+ int pkg_hit_methods = 0;
+ int pkg_total_methods = 0;
+ int pkg_hit_lines = 0;
+ int pkg_total_lines = 0;
+ //System.out.println("Processing package '" + pkgname + "': " + classes.length + " classes");
+ for( int j = 0; j < classes.length; j++ )
+ {
+ Element clazz = classes[j];
+ String classname = clazz.getAttribute( "name" );
+ if( pkgname != null && pkgname.length() != 0 )
+ {
+ classname = pkgname + "." + classname;
+ }
+ // there's only cov.data as a child so bet on it
+ Element covdata = getCovDataChild( clazz );
+ try
+ {
+ pkg_calls += Integer.parseInt( covdata.getAttribute( "calls" ) );
+ pkg_hit_methods += Integer.parseInt( covdata.getAttribute( "hit_methods" ) );
+ pkg_total_methods += Integer.parseInt( covdata.getAttribute( "total_methods" ) );
+ pkg_hit_lines += Integer.parseInt( covdata.getAttribute( "hit_lines" ) );
+ pkg_total_lines += Integer.parseInt( covdata.getAttribute( "total_lines" ) );
+ }
+ catch( NumberFormatException e )
+ {
+ log( "Error parsing '" + classname + "' (" + j + "/" + classes.length + ") in package '" + pkgname + "'" );
+ throw e;
+ }
+ }
+ Element covdata = getCovDataChild( pkgElem );
+ covdata.setAttribute( "calls", String.valueOf( pkg_calls ) );
+ covdata.setAttribute( "hit_methods", String.valueOf( pkg_hit_methods ) );
+ covdata.setAttribute( "total_methods", String.valueOf( pkg_total_methods ) );
+ covdata.setAttribute( "hit_lines", String.valueOf( pkg_hit_lines ) );
+ covdata.setAttribute( "total_lines", String.valueOf( pkg_total_lines ) );
+ calls += pkg_calls;
+ hit_methods += pkg_hit_methods;
+ total_methods += pkg_total_methods;
+ hit_lines += pkg_hit_lines;
+ total_lines += pkg_total_lines;
+ }
+ Element covdata = getCovDataChild( report.getDocumentElement() );
+ covdata.setAttribute( "calls", String.valueOf( calls ) );
+ covdata.setAttribute( "hit_methods", String.valueOf( hit_methods ) );
+ covdata.setAttribute( "total_methods", String.valueOf( total_methods ) );
+ covdata.setAttribute( "hit_lines", String.valueOf( hit_lines ) );
+ covdata.setAttribute( "total_lines", String.valueOf( total_lines ) );
+ }
+
+}
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassFile.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassFile.java
new file mode 100644
index 000000000..25bf53a4d
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassFile.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.sitraka.bytecode;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+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.Utf8CPInfo;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.attributes.AttributeInfo;
+
+
+/**
+ * Object representing a class. Information are kept to the strict minimum for
+ * JProbe reports so that not too many objects are created for a class,
+ * otherwise the JVM can quickly run out of memory when analyzing a great deal
+ * of classes and keeping them in memory for global analysis.
+ *
+ * @author Stephane Bailliez
+ */
+public final class ClassFile
+{
+
+ private int access_flags;
+
+ private String fullname;
+
+ private MethodInfo[] methods;
+
+ private String sourceFile;
+
+ public ClassFile( InputStream is )
+ throws IOException
+ {
+ DataInputStream dis = new DataInputStream( is );
+ ConstantPool constantPool = new ConstantPool();
+
+ int magic = dis.readInt();// 0xCAFEBABE
+ int minor = dis.readShort();
+ int major = dis.readShort();
+
+ constantPool.read( dis );
+ constantPool.resolve();
+
+ // class information
+ access_flags = dis.readShort();
+ int this_class = dis.readShort();
+ fullname = ( ( ClassCPInfo )constantPool.getEntry( this_class ) ).getClassName().replace( '/', '.' );
+ int super_class = dis.readShort();
+
+ // skip interfaces...
+ int count = dis.readShort();
+ dis.skipBytes( count * 2 );// short
+
+ // skip fields...
+ int numFields = dis.readShort();
+ for( int i = 0; i < numFields; i++ )
+ {
+ // 3 short: access flags, name index, descriptor index
+ dis.skip( 2 * 3 );
+ // attribute list...
+ int attributes_count = dis.readUnsignedShort();
+ for( int j = 0; j < attributes_count; j++ )
+ {
+ dis.skipBytes( 2 );// skip attr_id (short)
+ int len = dis.readInt();
+ dis.skipBytes( len );
+ }
+ }
+
+ // read methods
+ int method_count = dis.readShort();
+ methods = new MethodInfo[method_count];
+ for( int i = 0; i < method_count; i++ )
+ {
+ methods[i] = new MethodInfo();
+ methods[i].read( constantPool, dis );
+ }
+
+ // get interesting attributes.
+ int attributes_count = dis.readUnsignedShort();
+ for( int j = 0; j < attributes_count; j++ )
+ {
+ int attr_id = dis.readShort();
+ int len = dis.readInt();
+ String attr_name = Utils.getUTF8Value( constantPool, attr_id );
+ if( AttributeInfo.SOURCE_FILE.equals( attr_name ) )
+ {
+ int name_index = dis.readShort();
+ sourceFile = ( ( Utf8CPInfo )constantPool.getEntry( name_index ) ).getValue();
+ }
+ else
+ {
+ dis.skipBytes( len );
+ }
+ }
+ }
+
+ public int getAccess()
+ {
+ return access_flags;
+ }
+
+ public String getFullName()
+ {
+ return fullname;
+ }
+
+ public MethodInfo[] getMethods()
+ {
+ return methods;
+ }
+
+ public String getName()
+ {
+ String name = getFullName();
+ int pos = name.lastIndexOf( '.' );
+ if( pos == -1 )
+ {
+ return "";
+ }
+ return name.substring( pos + 1 );
+ }
+
+ public String getPackage()
+ {
+ String name = getFullName();
+ int pos = name.lastIndexOf( '.' );
+ if( pos == -1 )
+ {
+ return "";
+ }
+ return name.substring( 0, pos );
+ }
+
+ public String getSourceFile()
+ {
+ return sourceFile;
+ }
+
+}
+
+
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassPathLoader.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassPathLoader.java
new file mode 100644
index 000000000..26f19526c
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassPathLoader.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka.bytecode;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Core of the bytecode analyzer. It loads classes from a given classpath.
+ *
+ * @author Stephane Bailliez
+ */
+public class ClassPathLoader
+{
+
+ public final static FileLoader NULL_LOADER = new NullLoader();
+
+ /**
+ * the list of files to look for
+ */
+ protected File[] files;
+
+ /**
+ * create a new instance with a given classpath. It must be urls separated
+ * by the platform specific path separator.
+ *
+ * @param classPath the classpath to load all the classes from.
+ */
+ public ClassPathLoader( String classPath )
+ {
+ StringTokenizer st = new StringTokenizer( classPath, File.pathSeparator );
+ Vector entries = new Vector();
+ while( st.hasMoreTokens() )
+ {
+ File file = new File( st.nextToken() );
+ entries.addElement( file );
+ }
+ files = new File[entries.size()];
+ entries.copyInto( files );
+ }
+
+ /**
+ * create a new instance with a given set of urls.
+ *
+ * @param entries valid file urls (either .jar, .zip or directory)
+ */
+ public ClassPathLoader( String[] entries )
+ {
+ files = new File[entries.length];
+ for( int i = 0; i < entries.length; i++ )
+ {
+ files[i] = new File( entries[i] );
+ }
+ }
+
+ /**
+ * create a new instance with a given set of urls
+ *
+ * @param entries file urls to look for classes (.jar, .zip or directory)
+ */
+ public ClassPathLoader( File[] entries )
+ {
+ files = entries;
+ }
+
+ /**
+ * useful methods to read the whole input stream in memory so that it can be
+ * accessed faster. Processing rt.jar and tools.jar from JDK 1.3.1 brings
+ * time from 50s to 7s.
+ *
+ * @param is Description of Parameter
+ * @return The CachedStream value
+ * @exception IOException Description of Exception
+ */
+ public static InputStream getCachedStream( InputStream is )
+ throws IOException
+ {
+ is = new BufferedInputStream( is );
+ byte[] buffer = new byte[8192];
+ ByteArrayOutputStream baos = new ByteArrayOutputStream( 2048 );
+ int n;
+ baos.reset();
+ while( ( n = is.read( buffer, 0, buffer.length ) ) != -1 )
+ {
+ baos.write( buffer, 0, n );
+ }
+ is.close();
+ return new ByteArrayInputStream( baos.toByteArray() );
+ }
+
+ /**
+ * return the whole set of classes in the classpath. Note that this method
+ * can be very resource demanding since it must load all bytecode from all
+ * classes in all resources in the classpath at a time. To process it in a
+ * less resource demanding way, it is maybe better to use the loaders()
+ * that will return loader one by one.
+ *
+ * @return the hashtable containing ALL classes that are found in the given
+ * classpath. Note that the first entry of a given classname will
+ * shadow classes with the same name (as a classloader does)
+ * @exception IOException Description of Exception
+ */
+ public Hashtable getClasses()
+ throws IOException
+ {
+ Hashtable map = new Hashtable();
+ Enumeration enum = loaders();
+ while( enum.hasMoreElements() )
+ {
+ FileLoader loader = ( FileLoader )enum.nextElement();
+ System.out.println( "Processing " + loader.getFile() );
+ long t0 = System.currentTimeMillis();
+ ClassFile[] classes = loader.getClasses();
+ long dt = System.currentTimeMillis() - t0;
+ System.out.println( "" + classes.length + " classes loaded in " + dt + "ms" );
+ for( int j = 0; j < classes.length; j++ )
+ {
+ String name = classes[j].getFullName();
+ // do not allow duplicates entries to preserve 'classpath' behavior
+ // first class in wins
+ if( !map.containsKey( name ) )
+ {
+ map.put( name, classes[j] );
+ }
+ }
+ }
+ return map;
+ }
+
+ /**
+ * @return the set of FileLoader loaders matching the given
+ * classpath.
+ */
+ public Enumeration loaders()
+ {
+ return new LoaderEnumeration();
+ }
+
+ /**
+ * the interface to implement to look up for specific resources
+ *
+ * @author RT
+ */
+ public interface FileLoader
+ {
+ /**
+ * the file url that is looked for .class files
+ *
+ * @return The File value
+ */
+ public File getFile();
+
+ /**
+ * return the set of classes found in the file
+ *
+ * @return The Classes value
+ * @exception IOException Description of Exception
+ */
+ public ClassFile[] getClasses()
+ throws IOException;
+ }
+
+ /**
+ * the loader enumeration that will return loaders
+ *
+ * @author RT
+ */
+ protected class LoaderEnumeration implements Enumeration
+ {
+ protected int index = 0;
+
+ public boolean hasMoreElements()
+ {
+ return index < files.length;
+ }
+
+ public Object nextElement()
+ {
+ if( index >= files.length )
+ {
+ throw new NoSuchElementException();
+ }
+ File file = files[index++];
+ if( !file.exists() )
+ {
+ return new NullLoader( file );
+ }
+ if( file.isDirectory() )
+ {
+ // it's a directory
+ return new DirectoryLoader( file );
+ }
+ else if( file.getName().endsWith( ".zip" ) || file.getName().endsWith( ".jar" ) )
+ {
+ // it's a jar/zip file
+ return new JarLoader( file );
+ }
+ return new NullLoader( file );
+ }
+ }
+}
+
+/**
+ * a null loader to return when the file is not valid
+ *
+ * @author RT
+ */
+class NullLoader implements ClassPathLoader.FileLoader
+{
+
+ private File file;
+
+ NullLoader()
+ {
+ this( null );
+ }
+
+ NullLoader( File file )
+ {
+ this.file = file;
+ }
+
+ public ClassFile[] getClasses()
+ throws IOException
+ {
+ return new ClassFile[0];
+ }
+
+ public File getFile()
+ {
+ return file;
+ }
+}
+
+/**
+ * jar loader specified in looking for classes in jar and zip
+ *
+ * @author RT
+ * @todo read the jar manifest in case there is a Class-Path entry.
+ */
+class JarLoader implements ClassPathLoader.FileLoader
+{
+
+ private File file;
+
+ JarLoader( File file )
+ {
+ this.file = file;
+ }
+
+ public ClassFile[] getClasses()
+ throws IOException
+ {
+ ZipFile zipFile = new ZipFile( file );
+ Vector v = new Vector();
+ Enumeration entries = zipFile.entries();
+ while( entries.hasMoreElements() )
+ {
+ ZipEntry entry = ( ZipEntry )entries.nextElement();
+ if( entry.getName().endsWith( ".class" ) )
+ {
+ InputStream is = ClassPathLoader.getCachedStream( zipFile.getInputStream( entry ) );
+ ClassFile classFile = new ClassFile( is );
+ is.close();
+ v.addElement( classFile );
+ }
+ }
+ ClassFile[] classes = new ClassFile[v.size()];
+ v.copyInto( classes );
+ return classes;
+ }
+
+ public File getFile()
+ {
+ return file;
+ }
+}
+
+/**
+ * directory loader that will look all classes recursively
+ *
+ * @author RT
+ * @todo should discard classes which package name does not match the directory
+ * ?
+ */
+class DirectoryLoader implements ClassPathLoader.FileLoader
+{
+
+ private File directory;
+
+ DirectoryLoader( File dir )
+ {
+ directory = dir;
+ }
+
+ /**
+ * List files that obeys to a specific filter recursively from a given base
+ * directory.
+ *
+ * @param directory the directory where to list the files from.
+ * @param filter the file filter to apply
+ * @param recurse tells whether or not the listing is recursive.
+ * @return the list of File objects that applies to the given
+ * filter.
+ */
+ public static Vector listFiles( File directory, FilenameFilter filter, boolean recurse )
+ {
+ if( !directory.isDirectory() )
+ {
+ throw new IllegalArgumentException( directory + " is not a directory" );
+ }
+ Vector list = new Vector();
+ listFilesTo( list, directory, filter, recurse );
+ return list;
+ }
+
+ /**
+ * List and add files to a given list. As a convenience it sends back the
+ * instance of the list given as a parameter.
+ *
+ * @param list the list of files where the filtered files should be added
+ * @param directory the directory where to list the files from.
+ * @param filter the file filter to apply
+ * @param recurse tells whether or not the listing is recursive.
+ * @return the list instance that was passed as the list argument.
+ */
+ private static Vector listFilesTo( Vector list, File directory, FilenameFilter filter, boolean recurse )
+ {
+ String[] files = directory.list( filter );
+ for( int i = 0; i < files.length; i++ )
+ {
+ list.addElement( new File( directory, files[i] ) );
+ }
+ files = null;// we don't need it anymore
+ if( recurse )
+ {
+ String[] subdirs = directory.list( new DirectoryFilter() );
+ for( int i = 0; i < subdirs.length; i++ )
+ {
+ listFilesTo( list, new File( directory, subdirs[i] ), filter, recurse );
+ }
+ }
+ return list;
+ }
+
+ public ClassFile[] getClasses()
+ throws IOException
+ {
+ Vector v = new Vector();
+ Vector files = listFiles( directory, new ClassFilter(), true );
+ for( int i = 0; i < files.size(); i++ )
+ {
+ File file = ( File )files.elementAt( i );
+ InputStream is = null;
+ try
+ {
+ is = ClassPathLoader.getCachedStream( new FileInputStream( file ) );
+ ClassFile classFile = new ClassFile( is );
+ is.close();
+ is = null;
+ v.addElement( classFile );
+ }
+ finally
+ {
+ if( is != null )
+ {
+ try
+ {
+ is.close();
+ }
+ catch( IOException ignored )
+ {}
+ }
+ }
+ }
+ ClassFile[] classes = new ClassFile[v.size()];
+ v.copyInto( classes );
+ return classes;
+ }
+
+ public File getFile()
+ {
+ return directory;
+ }
+
+}
+
+/**
+ * Convenient filter that accepts only directory File
+ *
+ * @author RT
+ */
+class DirectoryFilter implements FilenameFilter
+{
+
+ public boolean accept( File directory, String name )
+ {
+ File pathname = new File( directory, name );
+ return pathname.isDirectory();
+ }
+}
+
+/**
+ * convenient filter to accept only .class files
+ *
+ * @author RT
+ */
+class ClassFilter implements FilenameFilter
+{
+
+ public boolean accept( File dir, String name )
+ {
+ return name.endsWith( ".class" );
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/MethodInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/MethodInfo.java
new file mode 100644
index 000000000..5d335ae3b
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/MethodInfo.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.sitraka.bytecode;
+import java.io.DataInputStream;
+import java.io.IOException;
+import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPool;
+import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.attributes.AttributeInfo;
+
+/**
+ * Method info structure.
+ *
+ * @author Stephane Bailliez
+ * @todo give a more appropriate name to methods.
+ */
+public final class MethodInfo
+{
+ private int loc = -1;
+ private int access_flags;
+ private String descriptor;
+ private String name;
+
+ public MethodInfo() { }
+
+ public String getAccess()
+ {
+ return Utils.getMethodAccess( access_flags );
+ }
+
+ public int getAccessFlags()
+ {
+ return access_flags;
+ }
+
+ public String getDescriptor()
+ {
+ return descriptor;
+ }
+
+ public String getFullSignature()
+ {
+ return getReturnType() + " " + getShortSignature();
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public int getNumberOfLines()
+ {
+ return loc;
+ }
+
+ public String[] getParametersType()
+ {
+ return Utils.getMethodParams( getDescriptor() );
+ }
+
+ public String getReturnType()
+ {
+ return Utils.getMethodReturnType( getDescriptor() );
+ }
+
+ public String getShortSignature()
+ {
+ StringBuffer buf = new StringBuffer( getName() );
+ buf.append( "(" );
+ String[] params = getParametersType();
+ for( int i = 0; i < params.length; i++ )
+ {
+ buf.append( params[i] );
+ if( i != params.length - 1 )
+ {
+ buf.append( ", " );
+ }
+ }
+ buf.append( ")" );
+ return buf.toString();
+ }
+
+ public void read( ConstantPool constantPool, DataInputStream dis )
+ throws IOException
+ {
+ access_flags = dis.readShort();
+
+ int name_index = dis.readShort();
+ name = Utils.getUTF8Value( constantPool, name_index );
+
+ int descriptor_index = dis.readShort();
+ descriptor = Utils.getUTF8Value( constantPool, descriptor_index );
+
+ int attributes_count = dis.readUnsignedShort();
+ for( int i = 0; i < attributes_count; i++ )
+ {
+ int attr_id = dis.readShort();
+ String attr_name = Utils.getUTF8Value( constantPool, attr_id );
+ int len = dis.readInt();
+ if( AttributeInfo.CODE.equals( attr_name ) )
+ {
+ readCode( constantPool, dis );
+ }
+ else
+ {
+ dis.skipBytes( len );
+ }
+ }
+
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append( "Method: " ).append( getAccess() ).append( " " );
+ sb.append( getFullSignature() );
+ return sb.toString();
+ }
+
+ protected void readCode( ConstantPool constantPool, DataInputStream dis )
+ throws IOException
+ {
+ // skip max_stack (short), max_local (short)
+ dis.skipBytes( 2 * 2 );
+
+ // skip bytecode...
+ int bytecode_len = dis.readInt();
+ dis.skip( bytecode_len );
+
+ // skip exceptions... 1 exception = 4 short.
+ int exception_count = dis.readShort();
+ dis.skipBytes( exception_count * 4 * 2 );
+
+ // read attributes...
+ int attributes_count = dis.readUnsignedShort();
+ for( int i = 0; i < attributes_count; i++ )
+ {
+ int attr_id = dis.readShort();
+ String attr_name = Utils.getUTF8Value( constantPool, attr_id );
+ int len = dis.readInt();
+ if( AttributeInfo.LINE_NUMBER_TABLE.equals( attr_name ) )
+ {
+ // we're only interested in lines of code...
+ loc = dis.readShort();
+ // skip the table which is 2*loc*short
+ dis.skip( loc * 2 * 2 );
+ }
+ else
+ {
+ dis.skipBytes( len );
+ }
+ }
+ }
+}
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/Utils.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/Utils.java
new file mode 100644
index 000000000..74af7a06e
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/Utils.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sitraka.bytecode;
+import java.util.Vector;
+import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPool;
+import org.apache.tools.ant.taskdefs.optional.depend.constantpool.Utf8CPInfo;
+
+/**
+ * Utilities mostly to manipulate methods and access flags.
+ *
+ * @author Stephane Bailliez
+ */
+public class Utils
+{
+ /**
+ * public access flag
+ */
+ public final static short ACC_PUBLIC = 1;
+ /**
+ * private access flag
+ */
+ public final static short ACC_PRIVATE = 2;
+ /**
+ * protected access flag
+ */
+ public final static short ACC_PROTECTED = 4;
+ /**
+ * static access flag
+ */
+ public final static short ACC_STATIC = 8;
+ /**
+ * final access flag
+ */
+ public final static short ACC_FINAL = 16;
+ /**
+ * super access flag
+ */
+ public final static short ACC_SUPER = 32;
+ /**
+ * synchronized access flag
+ */
+ public final static short ACC_SYNCHRONIZED = 32;
+ /**
+ * volatile access flag
+ */
+ public final static short ACC_VOLATILE = 64;
+ /**
+ * transient access flag
+ */
+ public final static short ACC_TRANSIENT = 128;
+ /**
+ * native access flag
+ */
+ public final static short ACC_NATIVE = 256;
+ /**
+ * interface access flag
+ */
+ public final static short ACC_INTERFACE = 512;
+ /**
+ * abstract access flag
+ */
+ public final static short ACC_ABSTRACT = 1024;
+ /**
+ * strict access flag
+ */
+ public final static short ACC_STRICT = 2048;
+
+ /**
+ * private constructor
+ */
+ private Utils() { }
+
+ /**
+ * return the class access flag as java modifiers
+ *
+ * @param access_flags access flags
+ * @return the access flags as modifier strings
+ */
+ public static String getClassAccess( int access_flags )
+ {
+ StringBuffer sb = new StringBuffer();
+ if( isPublic( access_flags ) )
+ {
+ sb.append( "public " );
+ }
+ else if( isProtected( access_flags ) )
+ {
+ sb.append( "protected " );
+ }
+ else if( isPrivate( access_flags ) )
+ {
+ sb.append( "private " );
+ }
+ if( isFinal( access_flags ) )
+ {
+ sb.append( "final " );
+ }
+ if( isSuper( access_flags ) )
+ {
+ sb.append( "/*super*/ " );
+ }
+ if( isInterface( access_flags ) )
+ {
+ sb.append( "interface " );
+ }
+ if( isAbstract( access_flags ) )
+ {
+ sb.append( "abstract " );
+ }
+ if( isClass( access_flags ) )
+ {
+ sb.append( "class " );
+ }
+ return sb.toString().trim();
+ }
+
+ /**
+ * return the field access flag as java modifiers
+ *
+ * @param access_flags access flags
+ * @return the access flags as modifier strings
+ */
+ public static String getFieldAccess( int access_flags )
+ {
+ StringBuffer sb = new StringBuffer();
+ if( isPublic( access_flags ) )
+ {
+ sb.append( "public " );
+ }
+ else if( isPrivate( access_flags ) )
+ {
+ sb.append( "private " );
+ }
+ else if( isProtected( access_flags ) )
+ {
+ sb.append( "protected " );
+ }
+ if( isFinal( access_flags ) )
+ {
+ sb.append( "final " );
+ }
+ if( isStatic( access_flags ) )
+ {
+ sb.append( "static " );
+ }
+ if( isVolatile( access_flags ) )
+ {
+ sb.append( "volatile " );
+ }
+ if( isTransient( access_flags ) )
+ {
+ sb.append( "transient " );
+ }
+ return sb.toString().trim();
+ }
+
+ /**
+ * return the method access flag as java modifiers
+ *
+ * @param access_flags access flags
+ * @return the access flags as modifier strings
+ */
+ public static String getMethodAccess( int access_flags )
+ {
+ StringBuffer sb = new StringBuffer();
+ if( isPublic( access_flags ) )
+ {
+ sb.append( "public " );
+ }
+ else if( isPrivate( access_flags ) )
+ {
+ sb.append( "private " );
+ }
+ else if( isProtected( access_flags ) )
+ {
+ sb.append( "protected " );
+ }
+ if( isFinal( access_flags ) )
+ {
+ sb.append( "final " );
+ }
+ if( isStatic( access_flags ) )
+ {
+ sb.append( "static " );
+ }
+ if( isSynchronized( access_flags ) )
+ {
+ sb.append( "synchronized " );
+ }
+ if( isNative( access_flags ) )
+ {
+ sb.append( "native " );
+ }
+ if( isAbstract( access_flags ) )
+ {
+ sb.append( "abstract " );
+ }
+ return sb.toString().trim();
+ }
+
+ /**
+ * parse all parameters from a descritor into fields of java name.
+ *
+ * @param descriptor of a method.
+ * @return the parameter list of a given method descriptor. Each string
+ * represent a java object with its fully qualified classname or the
+ * primitive name such as int, long, ...
+ */
+ public static String[] getMethodParams( String descriptor )
+ {
+ int i = 0;
+ if( descriptor.charAt( i ) != '(' )
+ {
+ throw new IllegalArgumentException( "Method descriptor should start with a '('" );
+ }
+ Vector params = new Vector();
+ StringBuffer param = new StringBuffer();
+ i++;
+ while( ( i = descriptor2java( descriptor, i, param ) ) < descriptor.length() )
+ {
+ params.add( param.toString() );
+ param.setLength( 0 );// reset
+ if( descriptor.charAt( i ) == ')' )
+ {
+ i++;
+ break;
+ }
+ }
+ String[] array = new String[params.size()];
+ params.copyInto( array );
+ return array;
+ }
+
+ /**
+ * return the object type of a return type.
+ *
+ * @param descriptor
+ * @return get the return type objet of a given descriptor
+ */
+ public static String getMethodReturnType( String descriptor )
+ {
+ int pos = descriptor.indexOf( ')' );
+ StringBuffer rettype = new StringBuffer();
+ descriptor2java( descriptor, pos + 1, rettype );
+ return rettype.toString();
+ }
+
+ /**
+ * return an UTF8 value from the pool located a a specific index.
+ *
+ * @param pool the constant pool to look at
+ * @param index index of the UTF8 value in the constant pool
+ * @return the value of the string if it exists
+ * @throws ClassCastException if the index is not an UTF8 constant.
+ */
+ public static String getUTF8Value( ConstantPool pool, int index )
+ {
+ return ( ( Utf8CPInfo )pool.getEntry( index ) ).getValue();
+ }
+
+ /**
+ * check for abstract access
+ *
+ * @param access_flags access flags
+ * @return The Abstract value
+ */
+ public static boolean isAbstract( int access_flags )
+ {
+ return ( access_flags & ACC_ABSTRACT ) != 0;
+ }
+
+ /**
+ * check for class access
+ *
+ * @param access_flags access flags
+ * @return The Class value
+ */
+ public static boolean isClass( int access_flags )
+ {
+ return !isInterface( access_flags );
+ }
+
+ /**
+ * chck for final flag
+ *
+ * @param access_flags access flags
+ * @return The Final value
+ */
+ public static boolean isFinal( int access_flags )
+ {
+ return ( access_flags & ACC_FINAL ) != 0;
+ }
+
+ /**
+ * check for interface access
+ *
+ * @param access_flags access flags
+ * @return The Interface value
+ */
+ public static boolean isInterface( int access_flags )
+ {
+ return ( access_flags & ACC_INTERFACE ) != 0;
+ }
+
+ /**
+ * check for native access
+ *
+ * @param access_flags access flags
+ * @return The Native value
+ */
+ public static boolean isNative( int access_flags )
+ {
+ return ( access_flags & ACC_NATIVE ) != 0;
+ }
+
+ /**
+ * check for private access
+ *
+ * @param access_flags access flags
+ * @return The Private value
+ */
+ public static boolean isPrivate( int access_flags )
+ {
+ return ( access_flags & ACC_PRIVATE ) != 0;
+ }
+
+ /**
+ * check for protected flag
+ *
+ * @param access_flags access flags
+ * @return The Protected value
+ */
+ public static boolean isProtected( int access_flags )
+ {
+ return ( access_flags & ACC_PROTECTED ) != 0;
+ }
+
+ /**
+ * check for public access
+ *
+ * @param access_flags access flags
+ * @return The Public value
+ */
+ public static boolean isPublic( int access_flags )
+ {
+ return ( access_flags & ACC_PUBLIC ) != 0;
+ }
+
+ /**
+ * check for a static access
+ *
+ * @param access_flags access flags
+ * @return The Static value
+ */
+ public static boolean isStatic( int access_flags )
+ {
+ return ( access_flags & ACC_STATIC ) != 0;
+ }
+
+ /**
+ * check for strict access
+ *
+ * @param access_flags access flags
+ * @return The Strict value
+ */
+ public static boolean isStrict( int access_flags )
+ {
+ return ( access_flags & ACC_STRICT ) != 0;
+ }
+
+ /**
+ * check for super flag
+ *
+ * @param access_flags access flag
+ * @return The Super value
+ */
+ public static boolean isSuper( int access_flags )
+ {
+ return ( access_flags & ACC_SUPER ) != 0;
+ }
+
+ /**
+ * check for synchronized flag
+ *
+ * @param access_flags access flags
+ * @return The Synchronized value
+ */
+ public static boolean isSynchronized( int access_flags )
+ {
+ return ( access_flags & ACC_SYNCHRONIZED ) != 0;
+ }
+
+ /**
+ * check for transient flag
+ *
+ * @param access_flags access flags
+ * @return The Transient value
+ */
+ public static boolean isTransient( int access_flags )
+ {
+ return ( access_flags & ACC_TRANSIENT ) != 0;
+ }
+
+ /**
+ * check for volatile flag
+ *
+ * @param access_flags access flags
+ * @return The Volatile value
+ */
+ public static boolean isVolatile( int access_flags )
+ {
+ return ( access_flags & ACC_VOLATILE ) != 0;
+ }
+
+ /**
+ * Parse a single descriptor symbol and returns it java equivalent.
+ *
+ * @param descriptor the descriptor symbol.
+ * @param i the index to look at the symbol in the descriptor string
+ * @param sb the stringbuffer to return the java equivalent of the symbol
+ * @return the index after the descriptor symbol
+ */
+ public static int descriptor2java( String descriptor, int i, StringBuffer sb )
+ {
+ // get the dimension
+ StringBuffer dim = new StringBuffer();
+ for( ; descriptor.charAt( i ) == '['; i++ )
+ {
+ dim.append( "[]" );
+ }
+ // now get the type
+ switch ( descriptor.charAt( i ) )
+ {
+ case 'B':
+ sb.append( "byte" );
+ break;
+ case 'C':
+ sb.append( "char" );
+ break;
+ case 'D':
+ sb.append( "double" );
+ break;
+ case 'F':
+ sb.append( "float" );
+ break;
+ case 'I':
+ sb.append( "int" );
+ break;
+ case 'J':
+ sb.append( "long" );
+ break;
+ case 'S':
+ sb.append( "short" );
+ break;
+ case 'Z':
+ sb.append( "boolean" );
+ break;
+ case 'V':
+ sb.append( "void" );
+ break;
+ case 'L':
+ // it is a class
+ int pos = descriptor.indexOf( ';', i + 1 );
+ String classname = descriptor.substring( i + 1, pos ).replace( '/', '.' );
+ sb.append( classname );
+ i = pos;
+ break;
+ default:
+ //@todo, yeah this happens because I got things like:
+ // ()Ljava/lang/Object; and it will return and ) will be here
+ // think about it.
+
+ //ooooops should never happen
+ //throw new IllegalArgumentException("Invalid descriptor symbol: '" + i + "' in '" + descriptor + "'");
+ }
+ sb.append( dim.toString() );
+ return ++i;
+ }
+}
+
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/attributes/AttributeInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/attributes/AttributeInfo.java
new file mode 100644
index 000000000..3631b9748
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/attributes/AttributeInfo.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.taskdefs.optional.sitraka.bytecode.attributes;
+
+/**
+ * Attribute info structure that provides base methods
+ *
+ * @author Stephane Bailliez
+ */
+public interface AttributeInfo
+{
+
+ public final static String SOURCE_FILE = "SourceFile";
+
+ public final static String CONSTANT_VALUE = "ConstantValue";
+
+ public final static String CODE = "Code";
+
+ public final static String EXCEPTIONS = "Exceptions";
+
+ public final static String LINE_NUMBER_TABLE = "LineNumberTable";
+
+ public final static String LOCAL_VARIABLE_TABLE = "LocalVariableTable";
+
+ public final static String INNER_CLASSES = "InnerClasses";
+
+ public final static String SOURCE_DIR = "SourceDir";
+
+ public final static String SYNTHETIC = "Synthetic";
+
+ public final static String DEPRECATED = "Deprecated";
+
+ public final static String UNKNOWN = "Unknown";
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sound/AntSoundPlayer.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sound/AntSoundPlayer.java
new file mode 100644
index 000000000..64e1b8e88
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sound/AntSoundPlayer.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.sound;
+import java.io.File;
+import java.io.IOException;
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Clip;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.Line;
+import javax.sound.sampled.LineEvent;// imports for all the sound classes required
+// note: comes with jmf or jdk1.3 +
+// these can be obtained from http://java.sun.com/products/java-media/sound/
+import javax.sound.sampled.LineListener;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.UnsupportedAudioFileException;
+import org.apache.tools.ant.BuildEvent;// ant includes
+import org.apache.tools.ant.BuildListener;
+import org.apache.tools.ant.Project;
+
+
+/**
+ * This class is designed to be used by any AntTask that requires audio output.
+ * It implements the BuildListener interface to listen for BuildEvents and could
+ * be easily extended to provide audio output upon any specific build events
+ * occuring. I have only tested this with .WAV and .AIFF sound file formats.
+ * Both seem to work fine.
+ *
+ * @author Nick Pellow
+ * @version $Revision$, $Date$
+ */
+
+public class AntSoundPlayer implements LineListener, BuildListener
+{
+
+ private File fileSuccess = null;
+ private int loopsSuccess = 0;
+ private Long durationSuccess = null;
+
+ private File fileFail = null;
+ private int loopsFail = 0;
+ private Long durationFail = null;
+
+ public AntSoundPlayer() { }
+
+
+ /**
+ * @param fileFail The feature to be added to the BuildFailedSound attribute
+ * @param loopsFail The feature to be added to the BuildFailedSound
+ * attribute
+ * @param durationFail The feature to be added to the BuildFailedSound
+ * attribute
+ */
+ public void addBuildFailedSound( File fileFail, int loopsFail, Long durationFail )
+ {
+ this.fileFail = fileFail;
+ this.loopsFail = loopsFail;
+ this.durationFail = durationFail;
+ }
+
+ /**
+ * @param loops the number of times the file should be played when the build
+ * is successful
+ * @param duration the number of milliseconds the file should be played when
+ * the build is successful
+ * @param file The feature to be added to the BuildSuccessfulSound attribute
+ */
+ public void addBuildSuccessfulSound( File file, int loops, Long duration )
+ {
+ this.fileSuccess = file;
+ this.loopsSuccess = loops;
+ this.durationSuccess = duration;
+ }
+
+ /**
+ * 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 )
+ {
+ if( event.getException() == null && fileSuccess != null )
+ {
+ // build successfull!
+ play( event.getProject(), fileSuccess, loopsSuccess, durationSuccess );
+ }
+ else if( event.getException() != null && fileFail != null )
+ {
+ play( event.getProject(), fileFail, loopsFail, durationFail );
+ }
+ }
+
+
+ /**
+ * Fired before any targets are started.
+ *
+ * @param event Description of Parameter
+ */
+ public void buildStarted( BuildEvent event ) { }
+
+ /**
+ * Fired whenever a message is logged.
+ *
+ * @param event Description of Parameter
+ * @see BuildEvent#getMessage()
+ * @see BuildEvent#getPriority()
+ */
+ public void messageLogged( 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()
+ */
+ public void targetFinished( BuildEvent event ) { }
+
+ /**
+ * Fired when a target is started.
+ *
+ * @param event Description of Parameter
+ * @see BuildEvent#getTarget()
+ */
+ public void targetStarted( 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()
+ */
+ public void taskFinished( BuildEvent event ) { }
+
+ /**
+ * Fired when a task is started.
+ *
+ * @param event Description of Parameter
+ * @see BuildEvent#getTask()
+ */
+ public void taskStarted( BuildEvent event ) { }
+
+ /**
+ * This is implemented to listen for any line events and closes the clip if
+ * required.
+ *
+ * @param event Description of Parameter
+ */
+ public void update( LineEvent event )
+ {
+ if( event.getType().equals( LineEvent.Type.STOP ) )
+ {
+ Line line = event.getLine();
+ line.close();
+ }
+ else if( event.getType().equals( LineEvent.Type.CLOSE ) )
+ {
+ /*
+ * There is a bug in JavaSound 0.90 (jdk1.3beta).
+ * It prevents correct termination of the VM.
+ * So we have to exit ourselves.
+ */
+ //System.exit(0);
+ }
+ }
+
+ /**
+ * Plays the file for duration milliseconds or loops.
+ *
+ * @param project Description of Parameter
+ * @param file Description of Parameter
+ * @param loops Description of Parameter
+ * @param duration Description of Parameter
+ */
+ private void play( Project project, File file, int loops, Long duration )
+ {
+
+ Clip audioClip = null;
+
+ AudioInputStream audioInputStream = null;
+
+ try
+ {
+ audioInputStream = AudioSystem.getAudioInputStream( file );
+ }
+ catch( UnsupportedAudioFileException uafe )
+ {
+ project.log( "Audio format is not yet supported: " + uafe.getMessage() );
+ }
+ catch( IOException ioe )
+ {
+ ioe.printStackTrace();
+ }
+
+ if( audioInputStream != null )
+ {
+ AudioFormat format = audioInputStream.getFormat();
+ DataLine.Info info = new DataLine.Info( Clip.class, format,
+ AudioSystem.NOT_SPECIFIED );
+ try
+ {
+ audioClip = ( Clip )AudioSystem.getLine( info );
+ audioClip.addLineListener( this );
+ audioClip.open( audioInputStream );
+ }
+ catch( LineUnavailableException e )
+ {
+ project.log( "The sound device is currently unavailable" );
+ return;
+ }
+ catch( IOException e )
+ {
+ e.printStackTrace();
+ }
+
+ if( duration != null )
+ {
+ playClip( audioClip, duration.longValue() );
+ }
+ else
+ {
+ playClip( audioClip, loops );
+ }
+ audioClip.drain();
+ audioClip.close();
+ }
+ else
+ {
+ project.log( "Can't get data from file " + file.getName() );
+ }
+ }
+
+ private void playClip( Clip clip, int loops )
+ {
+
+ clip.loop( loops );
+ while( clip.isRunning() )
+ {
+ }
+ }
+
+ private void playClip( Clip clip, long duration )
+ {
+
+ long currentTime = System.currentTimeMillis();
+ clip.loop( Clip.LOOP_CONTINUOUSLY );
+ try
+ {
+ Thread.sleep( duration );
+ }
+ catch( InterruptedException e )
+ {
+ }
+ }
+}
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sound/SoundTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sound/SoundTask.java
new file mode 100644
index 000000000..8854c4c0d
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sound/SoundTask.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.sound;
+import java.io.File;
+import java.util.Random;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+
+/**
+ * This is an example of an AntTask that makes of use of the AntSoundPlayer.
+ * There are three attributes to be set: source: the location of
+ * the audio file to be played duration: play the sound file
+ * continuously until "duration" milliseconds has expired loops:
+ * the number of times the sound file should be played until stopped I have only
+ * tested this with .WAV and .AIFF sound file formats. Both seem to work fine.
+ * plans for the future: - use the midi api to define sounds (or drum beat etc)
+ * in xml and have Ant play them back
+ *
+ * @author Nick Pellow
+ * @version $Revision$, $Date$
+ */
+
+public class SoundTask extends Task
+{
+
+ private BuildAlert success = null;
+ private BuildAlert fail = null;
+
+ public SoundTask() { }
+
+ public BuildAlert createFail()
+ {
+ fail = new BuildAlert();
+ return fail;
+ }
+
+ public BuildAlert createSuccess()
+ {
+ success = new BuildAlert();
+ return success;
+ }
+
+ public void execute()
+ {
+
+ AntSoundPlayer soundPlayer = new AntSoundPlayer();
+
+ if( success == null )
+ {
+ log( "No nested success element found.", Project.MSG_WARN );
+ }
+ else
+ {
+ soundPlayer.addBuildSuccessfulSound( success.getSource(),
+ success.getLoops(), success.getDuration() );
+ }
+
+ if( fail == null )
+ {
+ log( "No nested failure element found.", Project.MSG_WARN );
+ }
+ else
+ {
+ soundPlayer.addBuildFailedSound( fail.getSource(),
+ fail.getLoops(), fail.getDuration() );
+ }
+
+ getProject().addBuildListener( soundPlayer );
+
+ }
+
+ public void init() { }
+
+ /**
+ * A class to be extended by any BuildAlert's that require the output of
+ * sound.
+ *
+ * @author RT
+ */
+ public class BuildAlert
+ {
+ private File source = null;
+ private int loops = 0;
+ private Long duration = null;
+
+ /**
+ * Sets the duration in milliseconds the file should be played.
+ *
+ * @param duration The new Duration value
+ */
+ public void setDuration( Long duration )
+ {
+ this.duration = duration;
+ }
+
+ /**
+ * Sets the number of times the source file should be played.
+ *
+ * @param loops the number of loops to play the source file
+ */
+ public void setLoops( int loops )
+ {
+ this.loops = loops;
+ }
+
+ /**
+ * Sets the location of the file to get the audio.
+ *
+ * @param source the name of a sound-file directory or of the audio file
+ */
+ public void setSource( File source )
+ {
+ this.source = source;
+ }
+
+ /**
+ * Gets the duration in milliseconds the file should be played.
+ *
+ * @return The Duration value
+ */
+ public Long getDuration()
+ {
+ return this.duration;
+ }
+
+ /**
+ * Sets the number of times the source file should be played.
+ *
+ * @return the number of loops to play the source file
+ */
+ public int getLoops()
+ {
+ return this.loops;
+ }
+
+ /**
+ * Gets the location of the file to get the audio.
+ *
+ * @return The Source value
+ */
+ public File getSource()
+ {
+ File nofile = null;
+ // Check if source is a directory
+ if( source.exists() )
+ {
+ if( source.isDirectory() )
+ {
+ // get the list of files in the dir
+ String[] entries = source.list();
+ Vector files = new Vector();
+ for( int i = 0; i < entries.length; i++ )
+ {
+ File f = new File( source, entries[i] );
+ if( f.isFile() )
+ {
+ files.addElement( f );
+ }
+ }
+ if( files.size() < 1 )
+ {
+ throw new BuildException( "No files found in directory " + source );
+ }
+ int numfiles = files.size();
+ // get a random number between 0 and the number of files
+ Random rn = new Random();
+ int x = rn.nextInt( numfiles );
+ // set the source to the file at that location
+ this.source = ( File )files.elementAt( x );
+ }
+ }
+ else
+ {
+ log( source + ": invalid path.", Project.MSG_WARN );
+ this.source = nofile;
+ }
+ return this.source;
+ }
+ }
+}
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSS.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSS.java
new file mode 100644
index 000000000..2bd290da5
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSS.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.optional.vss;
+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 java.io.IOException;
+
+/**
+ * A base class for creating tasks for executing commands on Visual SourceSafe.
+ *
+ *
+ * The class extends the 'exec' task as it operates by executing the ss.exe
+ * program supplied with SourceSafe. By default the task expects ss.exe to be in
+ * the path, you can override this be specifying the ssdir attribute.
+ *
+ * This class provides set and get methods for 'login' and 'vsspath' attributes.
+ * It also contains constants for the flags that can be passed to SS.
+ *
+ * @author Craig Cottingham
+ * @author Andrew Everitt
+ */
+public abstract class MSVSS extends Task
+{
+
+ /**
+ * Constant for the thing to execute
+ */
+ private final static String SS_EXE = "ss";
+ /**
+ */
+ public final static String PROJECT_PREFIX = "$";
+
+ /**
+ * The 'Get' command
+ */
+ public final static String COMMAND_GET = "Get";
+ /**
+ * The 'Checkout' command
+ */
+ public final static String COMMAND_CHECKOUT = "Checkout";
+ /**
+ * The 'Checkin' command
+ */
+ public final static String COMMAND_CHECKIN = "Checkin";
+ /**
+ * The 'Label' command
+ */
+ public final static String COMMAND_LABEL = "Label";
+ /**
+ * The 'History' command
+ */
+ public final static String COMMAND_HISTORY = "History";
+
+ /**
+ */
+ public final static String FLAG_LOGIN = "-Y";
+ /**
+ */
+ public final static String FLAG_OVERRIDE_WORKING_DIR = "-GL";
+ /**
+ */
+ public final static String FLAG_AUTORESPONSE_DEF = "-I-";
+ /**
+ */
+ public final static String FLAG_AUTORESPONSE_YES = "-I-Y";
+ /**
+ */
+ public final static String FLAG_AUTORESPONSE_NO = "-I-N";
+ /**
+ */
+ public final static String FLAG_RECURSION = "-R";
+ /**
+ */
+ public final static String FLAG_VERSION = "-V";
+ /**
+ */
+ public final static String FLAG_VERSION_DATE = "-Vd";
+ /**
+ */
+ public final static String FLAG_VERSION_LABEL = "-VL";
+ /**
+ */
+ public final static String FLAG_WRITABLE = "-W";
+ /**
+ */
+ public final static String VALUE_NO = "-N";
+ /**
+ */
+ public final static String VALUE_YES = "-Y";
+ /**
+ */
+ public final static String FLAG_QUIET = "-O-";
+
+ private String m_SSDir = "";
+ private String m_vssLogin = null;
+ private String m_vssPath = null;
+ private String m_serverPath = null;
+
+ /**
+ * Set the login to use when accessing vss.
+ *
+ * Should be formatted as username,password
+ *
+ * @param login the login string to use
+ */
+ public final void setLogin( String login )
+ {
+ m_vssLogin = login;
+ }
+
+ /**
+ * Set the path to the location of the ss.ini
+ *
+ * @param serverPath
+ */
+ public final void setServerpath( String serverPath )
+ {
+ m_serverPath = serverPath;
+ }
+
+ /**
+ * Set the directory where ss.exe is located
+ *
+ * @param dir the directory containing ss.exe
+ */
+ public final void setSsdir( String dir )
+ {
+ m_SSDir = project.translatePath( dir );
+ }
+
+ /**
+ * Set the path to the item in vss to operate on
+ *
+ * Ant can't cope with a '$' sign in an attribute so we have to add it here.
+ * Also we strip off any 'vss://' prefix which is an XMS special and should
+ * probably be removed!
+ *
+ * @param vssPath
+ */
+ public final void setVsspath( String vssPath )
+ {
+ if( vssPath.startsWith( "vss://" ) )
+ {
+ m_vssPath = PROJECT_PREFIX + vssPath.substring( 5 );
+ }
+ else
+ {
+ m_vssPath = PROJECT_PREFIX + vssPath;
+ }
+ }
+
+ /**
+ * Builds and returns the command string to execute ss.exe
+ *
+ * @return The SSCommand value
+ */
+ public final String getSSCommand()
+ {
+ String toReturn = m_SSDir;
+ if( !toReturn.equals( "" ) && !toReturn.endsWith( "\\" ) )
+ {
+ toReturn += "\\";
+ }
+ toReturn += SS_EXE;
+
+ return toReturn;
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getLoginCommand( Commandline cmd )
+ {
+ if( m_vssLogin == null )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_LOGIN + m_vssLogin );
+ }
+ }
+
+ /**
+ * @return m_vssPath
+ */
+ public String getVsspath()
+ {
+ return m_vssPath;
+ }
+
+ protected int run( Commandline cmd )
+ {
+ try
+ {
+ Execute exe = new Execute( new LogStreamHandler( this,
+ Project.MSG_INFO,
+ Project.MSG_WARN ) );
+
+ // If location of ss.ini is specified we need to set the
+ // environment-variable SSDIR to this value
+ if( m_serverPath != null )
+ {
+ String[] env = exe.getEnvironment();
+ if( env == null )
+ {
+ env = new String[0];
+ }
+ String[] newEnv = new String[env.length + 1];
+ for( int i = 0; i < env.length; i++ )
+ {
+ newEnv[i] = env[i];
+ }
+ newEnv[env.length] = "SSDIR=" + m_serverPath;
+
+ exe.setEnvironment( newEnv );
+ }
+
+ exe.setAntRun( project );
+ exe.setWorkingDirectory( project.getBaseDir() );
+ exe.setCommandline( cmd.getCommandline() );
+ return exe.execute();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( e );
+ }
+ }
+}
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKIN.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKIN.java
new file mode 100644
index 000000000..8e936b6d4
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKIN.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.vss;
+import java.io.File;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Task to perform CheckIn commands to Microsoft Visual Source Safe.
+ *
+ * @author Martin Poeschl
+ */
+public class MSVSSCHECKIN extends MSVSS
+{
+
+ private String m_LocalPath = null;
+ private boolean m_Recursive = false;
+ private boolean m_Writable = false;
+ private String m_AutoResponse = null;
+ private String m_Comment = "-";
+
+ /**
+ * Set behaviour, used in get command to make files that are 'got' writable
+ *
+ * @param argWritable The new Writable value
+ */
+ public final void setWritable( boolean argWritable )
+ {
+ m_Writable = argWritable;
+ }
+
+ public void setAutoresponse( String response )
+ {
+ if( response.equals( "" ) || response.equals( "null" ) )
+ {
+ m_AutoResponse = null;
+ }
+ else
+ {
+ m_AutoResponse = response;
+ }
+ }
+
+ /**
+ * Set the comment to apply in SourceSafe
+ *
+ * If this is null or empty, it will be replaced with "-" which is what
+ * SourceSafe uses for an empty comment.
+ *
+ * @param comment The new Comment value
+ */
+ public void setComment( String comment )
+ {
+ if( comment.equals( "" ) || comment.equals( "null" ) )
+ {
+ m_Comment = "-";
+ }
+ else
+ {
+ m_Comment = comment;
+ }
+ }
+
+ /**
+ * Set the local path.
+ *
+ * @param localPath The new Localpath value
+ */
+ public void setLocalpath( Path localPath )
+ {
+ m_LocalPath = localPath.toString();
+ }
+
+ /**
+ * Set behaviour recursive or non-recursive
+ *
+ * @param recursive The new Recursive value
+ */
+ public void setRecursive( boolean recursive )
+ {
+ m_Recursive = recursive;
+ }
+
+ /**
+ * Checks the value set for the autoResponse. if it equals "Y" then we
+ * return -I-Y if it equals "N" then we return -I-N otherwise we return -I
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getAutoresponse( Commandline cmd )
+ {
+
+ if( m_AutoResponse == null )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "Y" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_YES );
+
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "N" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_NO );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }// end of else
+
+ }
+
+ /**
+ * Gets the comment to be applied.
+ *
+ * @return the comment to be applied.
+ */
+ public String getComment()
+ {
+ return m_Comment;
+ }
+
+ /**
+ * Builds and returns the -GL flag command if required
+ *
+ * The localpath is created if it didn't exist
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getLocalpathCommand( Commandline cmd )
+ {
+ if( m_LocalPath == null )
+ {
+ return;
+ }
+ else
+ {
+ // make sure m_LocalDir exists, create it if it doesn't
+ File dir = project.resolveFile( m_LocalPath );
+ if( !dir.exists() )
+ {
+ boolean done = dir.mkdirs();
+ if( done == false )
+ {
+ String msg = "Directory " + m_LocalPath + " creation was not " +
+ "succesful for an unknown reason";
+ throw new BuildException( msg, location );
+ }
+ project.log( "Created dir: " + dir.getAbsolutePath() );
+ }
+
+ cmd.createArgument().setValue( FLAG_OVERRIDE_WORKING_DIR + m_LocalPath );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getRecursiveCommand( Commandline cmd )
+ {
+ if( !m_Recursive )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_RECURSION );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getWritableCommand( Commandline cmd )
+ {
+ if( !m_Writable )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_WRITABLE );
+ }
+ }
+
+ /**
+ * Executes the task.
+ *
+ * Builds a command line to execute ss 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();
+ int result = 0;
+
+ // first off, make sure that we've got a command and a vssdir ...
+ if( getVsspath() == null )
+ {
+ String msg = "vsspath attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+
+ // now look for illegal combinations of things ...
+
+ // build the command line from what we got the format is
+ // ss Checkin VSS items [-H] [-C] [-I-] [-N] [-O] [-R] [-W] [-Y] [-?]
+ // as specified in the SS.EXE help
+ commandLine.setExecutable( getSSCommand() );
+ commandLine.createArgument().setValue( COMMAND_CHECKIN );
+
+ // VSS items
+ commandLine.createArgument().setValue( getVsspath() );
+ // -GL
+ getLocalpathCommand( commandLine );
+ // -I- or -I-Y or -I-N
+ getAutoresponse( commandLine );
+ // -R
+ getRecursiveCommand( commandLine );
+ // -W
+ getWritableCommand( commandLine );
+ // -Y
+ getLoginCommand( commandLine );
+ // -C
+ commandLine.createArgument().setValue( "-C" + getComment() );
+
+ result = run( commandLine );
+ if( result != 0 )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKOUT.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKOUT.java
new file mode 100644
index 000000000..356bf1565
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKOUT.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.vss;
+import java.io.File;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Task to perform CheckOut commands to Microsoft Visual Source Safe.
+ *
+ * @author Martin Poeschl
+ */
+public class MSVSSCHECKOUT extends MSVSS
+{
+
+ private String m_LocalPath = null;
+ private boolean m_Recursive = false;
+ private String m_Version = null;
+ private String m_Date = null;
+ private String m_Label = null;
+ private String m_AutoResponse = null;
+
+ public void setAutoresponse( String response )
+ {
+ if( response.equals( "" ) || response.equals( "null" ) )
+ {
+ m_AutoResponse = null;
+ }
+ else
+ {
+ m_AutoResponse = response;
+ }
+ }
+
+ /**
+ * Set the stored date string
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g. date="${date}" when
+ * date has not been defined to ant!
+ *
+ * @param date The new Date value
+ */
+ public void setDate( String date )
+ {
+ if( date.equals( "" ) || date.equals( "null" ) )
+ {
+ m_Date = null;
+ }
+ else
+ {
+ m_Date = date;
+ }
+ }
+
+ /**
+ * Set the labeled version to operate on in SourceSafe
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * label="${label_server}" when label_server has not been defined to ant!
+ *
+ * @param label The new Label value
+ */
+ public void setLabel( String label )
+ {
+ if( label.equals( "" ) || label.equals( "null" ) )
+ {
+ m_Label = null;
+ }
+ else
+ {
+ m_Label = label;
+ }
+ }
+
+ /**
+ * Set the local path.
+ *
+ * @param localPath The new Localpath value
+ */
+ public void setLocalpath( Path localPath )
+ {
+ m_LocalPath = localPath.toString();
+ }
+
+ /**
+ * Set behaviour recursive or non-recursive
+ *
+ * @param recursive The new Recursive value
+ */
+ public void setRecursive( boolean recursive )
+ {
+ m_Recursive = recursive;
+ }
+
+ /**
+ * Set the stored version string
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * version="${ver_server}" when ver_server has not been defined to ant!
+ *
+ * @param version The new Version value
+ */
+ public void setVersion( String version )
+ {
+ if( version.equals( "" ) || version.equals( "null" ) )
+ {
+ m_Version = null;
+ }
+ else
+ {
+ m_Version = version;
+ }
+ }
+
+ /**
+ * Checks the value set for the autoResponse. if it equals "Y" then we
+ * return -I-Y if it equals "N" then we return -I-N otherwise we return -I
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getAutoresponse( Commandline cmd )
+ {
+
+ if( m_AutoResponse == null )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "Y" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_YES );
+
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "N" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_NO );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }// end of else
+
+ }
+
+ /**
+ * Builds and returns the -GL flag command if required
+ *
+ * The localpath is created if it didn't exist
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getLocalpathCommand( Commandline cmd )
+ {
+ if( m_LocalPath == null )
+ {
+ return;
+ }
+ else
+ {
+ // make sure m_LocalDir exists, create it if it doesn't
+ File dir = project.resolveFile( m_LocalPath );
+ if( !dir.exists() )
+ {
+ boolean done = dir.mkdirs();
+ if( done == false )
+ {
+ String msg = "Directory " + m_LocalPath + " creation was not " +
+ "succesful for an unknown reason";
+ throw new BuildException( msg, location );
+ }
+ project.log( "Created dir: " + dir.getAbsolutePath() );
+ }
+
+ cmd.createArgument().setValue( FLAG_OVERRIDE_WORKING_DIR + m_LocalPath );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getRecursiveCommand( Commandline cmd )
+ {
+ if( !m_Recursive )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_RECURSION );
+ }
+ }
+
+ /**
+ * Simple order of priority. Returns the first specified of version, date,
+ * label If none of these was specified returns ""
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getVersionCommand( Commandline cmd )
+ {
+
+ if( m_Version != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION + m_Version );
+ }
+ else if( m_Date != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + m_Date );
+ }
+ else if( m_Label != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_LABEL + m_Label );
+ }
+ }
+
+ /**
+ * Executes the task.
+ *
+ * Builds a command line to execute ss 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();
+ int result = 0;
+
+ // first off, make sure that we've got a command and a vssdir ...
+ if( getVsspath() == null )
+ {
+ String msg = "vsspath attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+
+ // now look for illegal combinations of things ...
+
+ // build the command line from what we got the format is
+ // ss Checkout VSS items [-G] [-C] [-H] [-I-] [-N] [-O] [-R] [-V] [-Y] [-?]
+ // as specified in the SS.EXE help
+ commandLine.setExecutable( getSSCommand() );
+ commandLine.createArgument().setValue( COMMAND_CHECKOUT );
+
+ // VSS items
+ commandLine.createArgument().setValue( getVsspath() );
+ // -GL
+ getLocalpathCommand( commandLine );
+ // -I- or -I-Y or -I-N
+ getAutoresponse( commandLine );
+ // -R
+ getRecursiveCommand( commandLine );
+ // -V
+ getVersionCommand( commandLine );
+ // -Y
+ getLoginCommand( commandLine );
+
+ result = run( commandLine );
+ if( result != 0 )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+ }
+
+}
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSGET.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSGET.java
new file mode 100644
index 000000000..17bcc222c
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSGET.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.vss;
+import java.io.File;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Task to perform GET commands to Microsoft Visual Source Safe.
+ *
+ * The following attributes are interpretted:
+ *
+ *
+ *
+ *
+ *
+ * Attribute
+ *
+ *
+ *
+ * Values
+ *
+ *
+ *
+ * Required
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * login
+ *
+ *
+ *
+ * username,password
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * vsspath
+ *
+ *
+ *
+ * SourceSafe path
+ *
+ *
+ *
+ * Yes
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * localpath
+ *
+ *
+ *
+ * Override the working directory and get to the specified path
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * writable
+ *
+ *
+ *
+ * true or false
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * recursive
+ *
+ *
+ *
+ * true or false
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * version
+ *
+ *
+ *
+ * a version number to get
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * date
+ *
+ *
+ *
+ * a date stamp to get at
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * label
+ *
+ *
+ *
+ * a label to get for
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * quiet
+ *
+ *
+ *
+ * suppress output (off by default)
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * autoresponse
+ *
+ *
+ *
+ * What to respond with (sets the -I option). By default, -I- is used;
+ * values of Y or N will be appended to this.
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Note that only one of version, date or label should be specified
+ *
+ * @author Craig Cottingham
+ * @author Andrew Everitt
+ */
+public class MSVSSGET extends MSVSS
+{
+
+ private String m_LocalPath = null;
+ private boolean m_Recursive = false;
+ private boolean m_Writable = false;
+ private String m_Version = null;
+ private String m_Date = null;
+ private String m_Label = null;
+ private String m_AutoResponse = null;
+ private boolean m_Quiet = false;
+
+ /**
+ * Sets/clears quiet mode
+ *
+ * @param quiet The new Quiet value
+ */
+ public final void setQuiet( boolean quiet )
+ {
+ this.m_Quiet = quiet;
+ }
+
+ /**
+ * Set behaviour, used in get command to make files that are 'got' writable
+ *
+ * @param argWritable The new Writable value
+ */
+ public final void setWritable( boolean argWritable )
+ {
+ m_Writable = argWritable;
+ }
+
+ public void setAutoresponse( String response )
+ {
+ if( response.equals( "" ) || response.equals( "null" ) )
+ {
+ m_AutoResponse = null;
+ }
+ else
+ {
+ m_AutoResponse = response;
+ }
+ }
+
+ /**
+ * Set the stored date string
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g. date="${date}" when
+ * date has not been defined to ant!
+ *
+ * @param date The new Date value
+ */
+ public void setDate( String date )
+ {
+ if( date.equals( "" ) || date.equals( "null" ) )
+ {
+ m_Date = null;
+ }
+ else
+ {
+ m_Date = date;
+ }
+ }
+
+ /**
+ * Set the labeled version to operate on in SourceSafe
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * label="${label_server}" when label_server has not been defined to ant!
+ *
+ * @param label The new Label value
+ */
+ public void setLabel( String label )
+ {
+ if( label.equals( "" ) || label.equals( "null" ) )
+ {
+ m_Label = null;
+ }
+ else
+ {
+ m_Label = label;
+ }
+ }
+
+ /**
+ * Set the local path.
+ *
+ * @param localPath The new Localpath value
+ */
+ public void setLocalpath( Path localPath )
+ {
+ m_LocalPath = localPath.toString();
+ }
+
+ /**
+ * Set behaviour recursive or non-recursive
+ *
+ * @param recursive The new Recursive value
+ */
+ public void setRecursive( boolean recursive )
+ {
+ m_Recursive = recursive;
+ }
+
+ /**
+ * Set the stored version string
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * version="${ver_server}" when ver_server has not been defined to ant!
+ *
+ * @param version The new Version value
+ */
+ public void setVersion( String version )
+ {
+ if( version.equals( "" ) || version.equals( "null" ) )
+ {
+ m_Version = null;
+ }
+ else
+ {
+ m_Version = version;
+ }
+ }
+
+ /**
+ * Checks the value set for the autoResponse. if it equals "Y" then we
+ * return -I-Y if it equals "N" then we return -I-N otherwise we return -I
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getAutoresponse( Commandline cmd )
+ {
+
+ if( m_AutoResponse == null )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "Y" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_YES );
+
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "N" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_NO );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }// end of else
+
+ }
+
+ /**
+ * Builds and returns the -GL flag command if required
+ *
+ * The localpath is created if it didn't exist
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getLocalpathCommand( Commandline cmd )
+ {
+ if( m_LocalPath == null )
+ {
+ return;
+ }
+ else
+ {
+ // make sure m_LocalDir exists, create it if it doesn't
+ File dir = project.resolveFile( m_LocalPath );
+ if( !dir.exists() )
+ {
+ boolean done = dir.mkdirs();
+ if( done == false )
+ {
+ String msg = "Directory " + m_LocalPath + " creation was not " +
+ "successful for an unknown reason";
+ throw new BuildException( msg, location );
+ }
+ project.log( "Created dir: " + dir.getAbsolutePath() );
+ }
+
+ cmd.createArgument().setValue( FLAG_OVERRIDE_WORKING_DIR + m_LocalPath );
+ }
+ }
+
+ public void getQuietCommand( Commandline cmd )
+ {
+ if( m_Quiet )
+ {
+ cmd.createArgument().setValue( FLAG_QUIET );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getRecursiveCommand( Commandline cmd )
+ {
+ if( !m_Recursive )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_RECURSION );
+ }
+ }
+
+ /**
+ * Simple order of priority. Returns the first specified of version, date,
+ * label If none of these was specified returns ""
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getVersionCommand( Commandline cmd )
+ {
+
+ if( m_Version != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION + m_Version );
+ }
+ else if( m_Date != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + m_Date );
+ }
+ else if( m_Label != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_LABEL + m_Label );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ public void getWritableCommand( Commandline cmd )
+ {
+ if( !m_Writable )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_WRITABLE );
+ }
+ }
+
+ /**
+ * Executes the task.
+ *
+ * Builds a command line to execute ss 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();
+ int result = 0;
+
+ // first off, make sure that we've got a command and a vssdir ...
+ if( getVsspath() == null )
+ {
+ String msg = "vsspath attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+
+ // now look for illegal combinations of things ...
+
+ // build the command line from what we got the format is
+ // ss Get VSS items [-G] [-H] [-I-] [-N] [-O] [-R] [-V] [-W] [-Y] [-?]
+ // as specified in the SS.EXE help
+ commandLine.setExecutable( getSSCommand() );
+ commandLine.createArgument().setValue( COMMAND_GET );
+
+ // VSS items
+ commandLine.createArgument().setValue( getVsspath() );
+ // -GL
+ getLocalpathCommand( commandLine );
+ // -I- or -I-Y or -I-N
+ getAutoresponse( commandLine );
+ // -O-
+ getQuietCommand( commandLine );
+ // -R
+ getRecursiveCommand( commandLine );
+ // -V
+ getVersionCommand( commandLine );
+ // -W
+ getWritableCommand( commandLine );
+ // -Y
+ getLoginCommand( commandLine );
+
+ result = run( commandLine );
+ if( result != 0 )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+ }
+
+}
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSHISTORY.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSHISTORY.java
new file mode 100644
index 000000000..583e2249a
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSHISTORY.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.ant.taskdefs.optional.vss;
+import java.io.File;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+
+/**
+ * Task to perform HISTORY commands to Microsoft Visual Source Safe.
+ *
+ * @author Balazs Fejes 2
+ * @author Glenn_Twiggs@bmc.com
+ */
+
+public class MSVSSHISTORY extends MSVSS
+{
+
+ public final static String VALUE_FROMDATE = "~d";
+ public final static String VALUE_FROMLABEL = "~L";
+
+ public final static String FLAG_OUTPUT = "-O";
+ public final static String FLAG_USER = "-U";
+
+ private String m_FromDate = null;
+ private String m_ToDate = null;
+ private DateFormat m_DateFormat =
+ DateFormat.getDateInstance( DateFormat.SHORT );
+
+ private String m_FromLabel = null;
+ private String m_ToLabel = null;
+ private String m_OutputFileName = null;
+ private String m_User = null;
+ private int m_NumDays = Integer.MIN_VALUE;
+ private String m_Style = "";
+ private boolean m_Recursive = false;
+
+ /**
+ * Set the Start Date for the Comparison of two versions in SourceSafe
+ * History
+ *
+ * @param dateFormat The new DateFormat value
+ */
+ public void setDateFormat( String dateFormat )
+ {
+ if( !( dateFormat.equals( "" ) || dateFormat == null ) )
+ {
+ m_DateFormat = new SimpleDateFormat( dateFormat );
+ }
+ }
+
+ /**
+ * Set the Start Date for the Comparison of two versions in SourceSafe
+ * History
+ *
+ * @param fromDate The new FromDate value
+ */
+ public void setFromDate( String fromDate )
+ {
+ if( fromDate.equals( "" ) || fromDate == null )
+ {
+ m_FromDate = null;
+ }
+ else
+ {
+ m_FromDate = fromDate;
+ }
+ }
+
+ /**
+ * Set the Start Label
+ *
+ * @param fromLabel The new FromLabel value
+ */
+ public void setFromLabel( String fromLabel )
+ {
+ if( fromLabel.equals( "" ) || fromLabel == null )
+ {
+ m_FromLabel = null;
+ }
+ else
+ {
+ m_FromLabel = fromLabel;
+ }
+ }
+
+ /**
+ * Set the number of days to go back for Comparison
+ *
+ * The default value is 2 days.
+ *
+ * @param numd The new Numdays value
+ */
+ public void setNumdays( int numd )
+ {
+ m_NumDays = numd;
+ }
+
+ /**
+ * Set the output file name for SourceSafe output
+ *
+ * @param outfile The new Output value
+ */
+ public void setOutput( File outfile )
+ {
+ if( outfile == null )
+ {
+ m_OutputFileName = null;
+ }
+ else
+ {
+ m_OutputFileName = outfile.getAbsolutePath();
+ }
+ }
+
+ /**
+ * Set behaviour recursive or non-recursive
+ *
+ * @param recursive The new Recursive value
+ */
+ public void setRecursive( boolean recursive )
+ {
+ m_Recursive = recursive;
+ }
+
+ /**
+ * Specify the detail of output
+ *
+ * @param attr The new Style value
+ */
+ public void setStyle( BriefCodediffNofile attr )
+ {
+ String option = attr.getValue();
+ if( option.equals( "brief" ) )
+ {
+ m_Style = "-B";
+ }
+ else if( option.equals( "codediff" ) )
+ {
+ m_Style = "-D";
+ }
+ else if( option.equals( "default" ) )
+ {
+ m_Style = "";
+ }
+ else if( option.equals( "nofile" ) )
+ {
+ m_Style = "-F-";
+ }
+ else
+ {
+ throw new BuildException( "Style " + attr + " unknown." );
+ }
+ }
+
+ /**
+ * Set the End Date for the Comparison of two versions in SourceSafe History
+ *
+ * @param toDate The new ToDate value
+ */
+ public void setToDate( String toDate )
+ {
+ if( toDate.equals( "" ) || toDate == null )
+ {
+ m_ToDate = null;
+ }
+ else
+ {
+ m_ToDate = toDate;
+ }
+ }
+
+ /**
+ * Set the End Label
+ *
+ * @param toLabel The new ToLabel value
+ */
+ public void setToLabel( String toLabel )
+ {
+ if( toLabel.equals( "" ) || toLabel == null )
+ {
+ m_ToLabel = null;
+ }
+ else
+ {
+ m_ToLabel = toLabel;
+ }
+ }
+
+ /**
+ * Set the Username of the user whose changes we would like to see.
+ *
+ * @param user The new User value
+ */
+ public void setUser( String user )
+ {
+ m_User = user;
+ }
+
+ /**
+ * Executes the task.
+ *
+ * Builds a command line to execute ss 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();
+ int result = 0;
+
+ // first off, make sure that we've got a command and a vssdir and a label ...
+ if( getVsspath() == null )
+ {
+ String msg = "vsspath attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+
+ // now look for illegal combinations of things ...
+
+ // build the command line from what we got the format is
+ // ss History elements [-H] [-L] [-N] [-O] [-V] [-Y] [-#] [-?]
+ // as specified in the SS.EXE help
+ commandLine.setExecutable( getSSCommand() );
+ commandLine.createArgument().setValue( COMMAND_HISTORY );
+
+ // VSS items
+ commandLine.createArgument().setValue( getVsspath() );
+
+ // -I-
+ commandLine.createArgument().setValue( "-I-" );// ignore all errors
+
+ // -V
+ // Label an existing file or project version
+ getVersionDateCommand( commandLine );
+ getVersionLabelCommand( commandLine );
+
+ // -R
+ if( m_Recursive )
+ {
+ commandLine.createArgument().setValue( FLAG_RECURSION );
+ }
+
+ // -B / -D / -F-
+ if( m_Style.length() > 0 )
+ {
+ commandLine.createArgument().setValue( m_Style );
+ }
+
+ // -Y
+ getLoginCommand( commandLine );
+
+ // -O
+ getOutputCommand( commandLine );
+
+ System.out.println( "***: " + commandLine );
+
+ result = run( commandLine );
+ if( result != 0 )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+
+ }
+
+ /**
+ * Builds the version date command.
+ *
+ * @param cmd the commandline the command is to be added to
+ */
+ private void getOutputCommand( Commandline cmd )
+ {
+ if( m_OutputFileName != null )
+ {
+ cmd.createArgument().setValue( FLAG_OUTPUT + m_OutputFileName );
+ }
+ }
+
+ /**
+ * @param cmd Description of Parameter
+ */
+ private void getRecursiveCommand( Commandline cmd )
+ {
+ if( !m_Recursive )
+ {
+ return;
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_RECURSION );
+ }
+ }
+
+ /**
+ * Builds the User command.
+ *
+ * @param cmd the commandline the command is to be added to
+ */
+ private void getUserCommand( Commandline cmd )
+ {
+ if( m_User != null )
+ {
+ cmd.createArgument().setValue( FLAG_USER + m_User );
+ }
+ }
+
+ /**
+ * Builds the version date command.
+ *
+ * @param cmd the commandline the command is to be added to
+ * @exception BuildException Description of Exception
+ */
+ private void getVersionDateCommand( Commandline cmd )
+ throws BuildException
+ {
+ if( m_FromDate == null && m_ToDate == null && m_NumDays == Integer.MIN_VALUE )
+ {
+ return;
+ }
+
+ if( m_FromDate != null && m_ToDate != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + m_ToDate + VALUE_FROMDATE + m_FromDate );
+ }
+ else if( m_ToDate != null && m_NumDays != Integer.MIN_VALUE )
+ {
+ String startDate = null;
+ try
+ {
+ startDate = calcDate( m_ToDate, m_NumDays );
+ }
+ catch( ParseException ex )
+ {
+ String msg = "Error parsing date: " + m_ToDate;
+ throw new BuildException( msg, location );
+ }
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + m_ToDate + VALUE_FROMDATE + startDate );
+ }
+ else if( m_FromDate != null && m_NumDays != Integer.MIN_VALUE )
+ {
+ String endDate = null;
+ try
+ {
+ endDate = calcDate( m_FromDate, m_NumDays );
+ }
+ catch( ParseException ex )
+ {
+ String msg = "Error parsing date: " + m_FromDate;
+ throw new BuildException( msg, location );
+ }
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + endDate + VALUE_FROMDATE + m_FromDate );
+ }
+ else
+ {
+ if( m_FromDate != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION + VALUE_FROMDATE + m_FromDate );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_DATE + m_ToDate );
+ }
+ }
+ }
+
+ /**
+ * Builds the version date command.
+ *
+ * @param cmd the commandline the command is to be added to
+ * @exception BuildException Description of Exception
+ */
+ private void getVersionLabelCommand( Commandline cmd )
+ throws BuildException
+ {
+ if( m_FromLabel == null && m_ToLabel == null )
+ {
+ return;
+ }
+
+ if( m_FromLabel != null && m_ToLabel != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_LABEL + m_ToLabel + VALUE_FROMLABEL + m_FromLabel );
+ }
+ else if( m_FromLabel != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION + VALUE_FROMLABEL + m_FromLabel );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_VERSION_LABEL + m_ToLabel );
+ }
+ }
+
+ /**
+ * Calculates the start date for version comparison.
+ *
+ * Calculates the date numDay days earlier than startdate.
+ *
+ * @param fromDate Description of Parameter
+ * @param numDays Description of Parameter
+ * @return Description of the Returned Value
+ * @exception ParseException Description of Exception
+ */
+ private String calcDate( String fromDate, int numDays )
+ throws ParseException
+ {
+ String toDate = null;
+ Date currdate = new Date();
+ Calendar calend = new GregorianCalendar();
+ currdate = m_DateFormat.parse( fromDate );
+ calend.setTime( currdate );
+ calend.add( Calendar.DATE, numDays );
+ toDate = m_DateFormat.format( calend.getTime() );
+ return toDate;
+ }
+
+ public static class BriefCodediffNofile extends EnumeratedAttribute
+ {
+ public String[] getValues()
+ {
+ return new String[]{"brief", "codediff", "nofile", "default"};
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSLABEL.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSLABEL.java
new file mode 100644
index 000000000..d113a2f03
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSLABEL.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software 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.vss;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Commandline;
+
+/**
+ * Task to perform LABEL commands to Microsoft Visual Source Safe.
+ *
+ * The following attributes are interpreted:
+ *
+ *
+ *
+ *
+ *
+ * Attribute
+ *
+ *
+ *
+ * Values
+ *
+ *
+ *
+ * Required
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * login
+ *
+ *
+ *
+ * username,password
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * vsspath
+ *
+ *
+ *
+ * SourceSafe path
+ *
+ *
+ *
+ * Yes
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ssdir
+ *
+ *
+ *
+ * directory where ss.exe resides. By default the task
+ * expects it to be in the PATH.
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * label
+ *
+ *
+ *
+ * A label to apply to the hierarchy
+ *
+ *
+ *
+ * Yes
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * version
+ *
+ *
+ *
+ * An existing file or project version to label
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * autoresponse
+ *
+ *
+ *
+ * What to respond with (sets the -I option). By default, -I- is used;
+ * values of Y or N will be appended to this.
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * comment
+ *
+ *
+ *
+ * The comment to use for this label. Empty or '-' for no comment.
+ *
+ *
+ *
+ * No
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @author Phillip Wells
+ */
+public class MSVSSLABEL extends MSVSS
+{
+
+ public final static String FLAG_LABEL = "-L";
+ private String m_AutoResponse = null;
+ private String m_Label = null;
+ private String m_Version = null;
+ private String m_Comment = "-";
+
+ public void setAutoresponse( String response )
+ {
+ if( response.equals( "" ) || response.equals( "null" ) )
+ {
+ m_AutoResponse = null;
+ }
+ else
+ {
+ m_AutoResponse = response;
+ }
+ }
+
+ /**
+ * Set the comment to apply in SourceSafe
+ *
+ * If this is null or empty, it will be replaced with "-" which is what
+ * SourceSafe uses for an empty comment.
+ *
+ * @param comment The new Comment value
+ */
+ public void setComment( String comment )
+ {
+ if( comment.equals( "" ) || comment.equals( "null" ) )
+ {
+ m_Comment = "-";
+ }
+ else
+ {
+ m_Comment = comment;
+ }
+ }
+
+ /**
+ * Set the label to apply in SourceSafe
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * label="${label_server}" when label_server has not been defined to ant!
+ *
+ * @param label The new Label value
+ */
+ public void setLabel( String label )
+ {
+ if( label.equals( "" ) || label.equals( "null" ) )
+ {
+ m_Label = null;
+ }
+ else
+ {
+ m_Label = label;
+ }
+ }
+
+ /**
+ * Set the stored version string
+ *
+ * Note we assume that if the supplied string has the value "null" that
+ * something went wrong and that the string value got populated from a null
+ * object. This happens if a ant variable is used e.g.
+ * version="${ver_server}" when ver_server has not been defined to ant!
+ *
+ * @param version The new Version value
+ */
+ public void setVersion( String version )
+ {
+ if( version.equals( "" ) || version.equals( "null" ) )
+ {
+ m_Version = null;
+ }
+ else
+ {
+ m_Version = version;
+ }
+ }
+
+ /**
+ * Checks the value set for the autoResponse. if it equals "Y" then we
+ * return -I-Y if it equals "N" then we return -I-N otherwise we return -I
+ *
+ * @param cmd Description of Parameter
+ */
+ public void getAutoresponse( Commandline cmd )
+ {
+
+ if( m_AutoResponse == null )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "Y" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_YES );
+
+ }
+ else if( m_AutoResponse.equalsIgnoreCase( "N" ) )
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_NO );
+ }
+ else
+ {
+ cmd.createArgument().setValue( FLAG_AUTORESPONSE_DEF );
+ }// end of else
+
+ }
+
+ /**
+ * Gets the comment to be applied.
+ *
+ * @return the comment to be applied.
+ */
+ public String getComment()
+ {
+ return m_Comment;
+ }
+
+ /**
+ * Gets the label to be applied.
+ *
+ * @return the label to be applied.
+ */
+ public String getLabel()
+ {
+ return m_Label;
+ }
+
+ /**
+ * Builds the label command.
+ *
+ * @param cmd the commandline the command is to be added to
+ */
+ public void getLabelCommand( Commandline cmd )
+ {
+ if( m_Label != null )
+ {
+ cmd.createArgument().setValue( FLAG_LABEL + m_Label );
+ }
+ }
+
+ /**
+ * Builds the version command.
+ *
+ * @param cmd the commandline the command is to be added to
+ */
+ public void getVersionCommand( Commandline cmd )
+ {
+ if( m_Version != null )
+ {
+ cmd.createArgument().setValue( FLAG_VERSION + m_Version );
+ }
+ }
+
+ /**
+ * Executes the task.
+ *
+ * Builds a command line to execute ss 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();
+ int result = 0;
+
+ // first off, make sure that we've got a command and a vssdir and a label ...
+ if( getVsspath() == null )
+ {
+ String msg = "vsspath attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+ if( getLabel() == null )
+ {
+ String msg = "label attribute must be set!";
+ throw new BuildException( msg, location );
+ }
+
+ // now look for illegal combinations of things ...
+
+ // build the command line from what we got the format is
+ // ss Label VSS items [-C] [-H] [-I-] [-Llabel] [-N] [-O] [-V] [-Y] [-?]
+ // as specified in the SS.EXE help
+ commandLine.setExecutable( getSSCommand() );
+ commandLine.createArgument().setValue( COMMAND_LABEL );
+
+ // VSS items
+ commandLine.createArgument().setValue( getVsspath() );
+
+ // -C
+ commandLine.createArgument().setValue( "-C" + getComment() );
+
+ // -I- or -I-Y or -I-N
+ getAutoresponse( commandLine );
+
+ // -L
+ // Specify the new label on the command line (instead of being prompted)
+ getLabelCommand( commandLine );
+
+ // -V
+ // Label an existing file or project version
+ getVersionCommand( commandLine );
+
+ // -Y
+ getLoginCommand( commandLine );
+
+ result = run( commandLine );
+ if( result != 0 )
+ {
+ String msg = "Failed executing: " + commandLine.toString();
+ throw new BuildException( msg, location );
+ }
+
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/DefaultRmicAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/DefaultRmicAdapter.java
new file mode 100644
index 000000000..934d8e199
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/DefaultRmicAdapter.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.taskdefs.rmic;
+import java.io.File;
+import java.util.Random;
+import java.util.Vector;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Rmic;
+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.FileNameMapper;
+
+/**
+ * This is the default implementation for the RmicAdapter interface. Currently,
+ * this is a cut-and-paste of the original rmic task and
+ * DefaultCopmpilerAdapter.
+ *
+ * @author duncan@x180.com
+ * @author ludovic.claude@websitewatchers.co.uk
+ * @author David Maclean david@cm.co.za
+ * @author Stefan Bodewig
+ * @author Takashi Okamoto
+ */
+public abstract class DefaultRmicAdapter implements RmicAdapter
+{
+
+ private final static Random rand = new Random();
+
+ private Rmic attributes;
+ private FileNameMapper mapper;
+
+ public DefaultRmicAdapter() { }
+
+ public void setRmic( Rmic attributes )
+ {
+ this.attributes = attributes;
+ mapper = new RmicFileNameMapper();
+ }
+
+ /**
+ * The CLASSPATH this rmic process will use.
+ *
+ * @return The Classpath value
+ */
+ public Path getClasspath()
+ {
+ return getCompileClasspath();
+ }
+
+ /**
+ * This implementation maps *.class to *getStubClassSuffix().class and - if
+ * stubversion is not 1.2 - to *getSkelClassSuffix().class.
+ *
+ * @return The Mapper value
+ */
+ public FileNameMapper getMapper()
+ {
+ return mapper;
+ }
+
+ public Rmic getRmic()
+ {
+ return attributes;
+ }
+
+ /**
+ * setup rmic argument for rmic.
+ *
+ * @return Description of the Returned Value
+ */
+ protected Commandline setupRmicCommand()
+ {
+ return setupRmicCommand( null );
+ }
+
+ /**
+ * setup rmic argument for rmic.
+ *
+ * @param options additional parameters needed by a specific implementation.
+ * @return Description of the Returned Value
+ */
+ protected Commandline setupRmicCommand( String[] options )
+ {
+ Commandline cmd = new Commandline();
+
+ if( options != null )
+ {
+ for( int i = 0; i < options.length; i++ )
+ {
+ cmd.createArgument().setValue( options[i] );
+ }
+ }
+
+ Path classpath = getCompileClasspath();
+
+ cmd.createArgument().setValue( "-d" );
+ cmd.createArgument().setFile( attributes.getBase() );
+
+ if( attributes.getExtdirs() != null )
+ {
+ if( Project.getJavaVersion().startsWith( "1.1" ) )
+ {
+ /*
+ * XXX - This doesn't mix very well with build.systemclasspath,
+ */
+ classpath.addExtdirs( attributes.getExtdirs() );
+ }
+ else
+ {
+ cmd.createArgument().setValue( "-extdirs" );
+ cmd.createArgument().setPath( attributes.getExtdirs() );
+ }
+ }
+
+ cmd.createArgument().setValue( "-classpath" );
+ cmd.createArgument().setPath( classpath );
+
+ String stubVersion = attributes.getStubVersion();
+ if( null != stubVersion )
+ {
+ if( "1.1".equals( stubVersion ) )
+ cmd.createArgument().setValue( "-v1.1" );
+ else if( "1.2".equals( stubVersion ) )
+ cmd.createArgument().setValue( "-v1.2" );
+ else
+ cmd.createArgument().setValue( "-vcompat" );
+ }
+
+ if( null != attributes.getSourceBase() )
+ {
+ cmd.createArgument().setValue( "-keepgenerated" );
+ }
+
+ if( attributes.getIiop() )
+ {
+ attributes.log( "IIOP has been turned on.", Project.MSG_INFO );
+ cmd.createArgument().setValue( "-iiop" );
+ if( attributes.getIiopopts() != null )
+ {
+ attributes.log( "IIOP Options: " + attributes.getIiopopts(),
+ Project.MSG_INFO );
+ cmd.createArgument().setValue( attributes.getIiopopts() );
+ }
+ }
+
+ if( attributes.getIdl() )
+ {
+ cmd.createArgument().setValue( "-idl" );
+ attributes.log( "IDL has been turned on.", Project.MSG_INFO );
+ if( attributes.getIdlopts() != null )
+ {
+ cmd.createArgument().setValue( attributes.getIdlopts() );
+ attributes.log( "IDL Options: " + attributes.getIdlopts(),
+ Project.MSG_INFO );
+ }
+ }
+
+ if( attributes.getDebug() )
+ {
+ cmd.createArgument().setValue( "-g" );
+ }
+
+ logAndAddFilesToCompile( cmd );
+ return cmd;
+ }
+
+ /**
+ * Builds the compilation classpath.
+ *
+ * @return The CompileClasspath value
+ */
+ protected Path getCompileClasspath()
+ {
+ // add dest dir to classpath so that previously compiled and
+ // untouched classes are on classpath
+ Path classpath = new Path( attributes.getProject() );
+ classpath.setLocation( attributes.getBase() );
+
+ // Combine the build classpath with the system classpath, in an
+ // order determined by the value of build.classpath
+
+ if( attributes.getClasspath() == null )
+ {
+ if( attributes.getIncludeantruntime() )
+ {
+ classpath.addExisting( Path.systemClasspath );
+ }
+ }
+ else
+ {
+ if( attributes.getIncludeantruntime() )
+ {
+ classpath.addExisting( attributes.getClasspath().concatSystemClasspath( "last" ) );
+ }
+ else
+ {
+ classpath.addExisting( attributes.getClasspath().concatSystemClasspath( "ignore" ) );
+ }
+ }
+
+ if( attributes.getIncludejavaruntime() )
+ {
+ classpath.addJavaRuntime();
+ }
+ return classpath;
+ }
+
+ protected String getSkelClassSuffix()
+ {
+ return "_Skel";
+ }
+
+ protected String getStubClassSuffix()
+ {
+ return "_Stub";
+ }
+
+ protected String getTieClassSuffix()
+ {
+ return "_Tie";
+ }
+
+ /**
+ * Logs the compilation parameters, adds the files to compile and logs the
+ * &qout;niceSourceList"
+ *
+ * @param cmd Description of Parameter
+ */
+ protected void logAndAddFilesToCompile( Commandline cmd )
+ {
+ Vector compileList = attributes.getCompileList();
+
+ attributes.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:" );
+
+ for( int i = 0; i < compileList.size(); i++ )
+ {
+ String arg = ( String )compileList.elementAt( i );
+ cmd.createArgument().setValue( arg );
+ niceSourceList.append( " " + arg );
+ }
+
+ attributes.log( niceSourceList.toString(), Project.MSG_VERBOSE );
+ }
+
+ /**
+ * Mapper that possibly returns two file names, *_Stub and *_Skel.
+ *
+ * @author RT
+ */
+ private class RmicFileNameMapper implements FileNameMapper
+ {
+
+ RmicFileNameMapper() { }
+
+ /**
+ * Empty implementation.
+ *
+ * @param s The new From value
+ */
+ public void setFrom( String s ) { }
+
+ /**
+ * Empty implementation.
+ *
+ * @param s The new To value
+ */
+ public void setTo( String s ) { }
+
+ public String[] mapFileName( String name )
+ {
+ if( name == null
+ || !name.endsWith( ".class" )
+ || name.endsWith( getStubClassSuffix() + ".class" )
+ || name.endsWith( getSkelClassSuffix() + ".class" )
+ || name.endsWith( getTieClassSuffix() + ".class" ) )
+ {
+ // Not a .class file or the one we'd generate
+ return null;
+ }
+
+ String base = name.substring( 0, name.indexOf( ".class" ) );
+ String classname = base.replace( File.separatorChar, '.' );
+ if( attributes.getVerify() &&
+ !attributes.isValidRmiRemote( classname ) )
+ {
+ return null;
+ }
+
+ /*
+ * fallback in case we have trouble loading the class or
+ * don't know how to handle it (there is no easy way to
+ * know what IDL mode would generate.
+ *
+ * This is supposed to make Ant always recompile the
+ * class, as a file of that name should not exist.
+ */
+ String[] target = new String[]{name + ".tmp." + rand.nextLong()};
+
+ if( !attributes.getIiop() && !attributes.getIdl() )
+ {
+ // JRMP with simple naming convention
+ if( "1.2".equals( attributes.getStubVersion() ) )
+ {
+ target = new String[]{
+ base + getStubClassSuffix() + ".class"
+ };
+ }
+ else
+ {
+ target = new String[]{
+ base + getStubClassSuffix() + ".class",
+ base + getSkelClassSuffix() + ".class",
+ };
+ }
+ }
+ else if( !attributes.getIdl() )
+ {
+ int lastSlash = base.lastIndexOf( File.separatorChar );
+
+ String dirname = "";
+ /*
+ * I know, this is not necessary, but I prefer it explicit (SB)
+ */
+ int index = -1;
+ if( lastSlash == -1 )
+ {
+ // no package
+ index = 0;
+ }
+ else
+ {
+ index = lastSlash + 1;
+ dirname = base.substring( 0, index );
+ }
+
+ String filename = base.substring( index );
+
+ try
+ {
+ Class c = attributes.getLoader().loadClass( classname );
+
+ if( c.isInterface() )
+ {
+ // only stub, no tie
+ target = new String[]{
+ dirname + "_" + filename + getStubClassSuffix()
+ + ".class"
+ };
+ }
+ else
+ {
+ /*
+ * stub is derived from implementation,
+ * tie from interface name.
+ */
+ Class interf = attributes.getRemoteInterface( c );
+ String iName = interf.getName();
+ String iDir = "";
+ int iIndex = -1;
+ int lastDot = iName.lastIndexOf( "." );
+ if( lastDot == -1 )
+ {
+ // no package
+ iIndex = 0;
+ }
+ else
+ {
+ iIndex = lastDot + 1;
+ iDir = iName.substring( 0, iIndex );
+ iDir = iDir.replace( '.', File.separatorChar );
+ }
+
+ target = new String[]{
+ dirname + "_" + filename + getTieClassSuffix()
+ + ".class",
+ iDir + "_" + iName.substring( iIndex )
+ + getStubClassSuffix() + ".class"
+ };
+ }
+ }
+ catch( ClassNotFoundException e )
+ {
+ attributes.log( "Unable to verify class " + classname
+ + ". It could not be found.",
+ Project.MSG_WARN );
+ }
+ catch( NoClassDefFoundError e )
+ {
+ attributes.log( "Unable to verify class " + classname
+ + ". It is not defined.", Project.MSG_WARN );
+ }
+ catch( Throwable t )
+ {
+ attributes.log( "Unable to verify class " + classname
+ + ". Loading caused Exception: "
+ + t.getMessage(), Project.MSG_WARN );
+ }
+ }
+ return target;
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/KaffeRmic.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/KaffeRmic.java
new file mode 100644
index 000000000..a7a76813e
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/KaffeRmic.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.taskdefs.rmic;
+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.types.Commandline;
+
+/**
+ * The implementation of the rmic for Kaffe
+ *
+ * @author Takashi Okamoto
+ */
+public class KaffeRmic extends DefaultRmicAdapter
+{
+
+ public boolean execute()
+ throws BuildException
+ {
+ getRmic().log( "Using Kaffe rmic", Project.MSG_VERBOSE );
+ Commandline cmd = setupRmicCommand();
+
+ try
+ {
+
+ Class c = Class.forName( "kaffe.rmi.rmic.RMIC" );
+ Constructor cons = c.getConstructor( new Class[]{String[].class} );
+ Object rmic = cons.newInstance( new Object[]{cmd.getArguments()} );
+ Method doRmic = c.getMethod( "run", null );
+ String str[] = cmd.getArguments();
+ Boolean ok = ( Boolean )doRmic.invoke( rmic, null );
+
+ return ok.booleanValue();
+ }
+ catch( ClassNotFoundException ex )
+ {
+ throw new BuildException( "Cannot use Kaffe rmic, as it is not available" +
+ " A common solution is to set the environment variable" +
+ " JAVA_HOME or CLASSPATH.", getRmic().getLocation() );
+ }
+ catch( Exception ex )
+ {
+ if( ex instanceof BuildException )
+ {
+ throw ( BuildException )ex;
+ }
+ else
+ {
+ throw new BuildException( "Error starting Kaffe rmic: ", ex, getRmic().getLocation() );
+ }
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/RmicAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/RmicAdapter.java
new file mode 100644
index 000000000..34491f984
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/RmicAdapter.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.rmic;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.taskdefs.Rmic;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.util.FileNameMapper;
+
+/**
+ * The interface that all rmic adapters must adher to.
+ *
+ * A rmic adapter is an adapter that interprets the rmic's parameters in
+ * preperation to be passed off to the compiler this adapter represents. As all
+ * the necessary values are stored in the Rmic task itself, the only thing all
+ * adapters need is the rmic task, the execute command and a parameterless
+ * constructor (for reflection).
+ *
+ * @author Takashi Okamoto
+ * @author Stefan Bodewig
+ */
+
+public interface RmicAdapter
+{
+
+ /**
+ * Sets the rmic attributes, which are stored in the Rmic task.
+ *
+ * @param attributes The new Rmic value
+ */
+ void setRmic( Rmic attributes );
+
+ /**
+ * Executes the task.
+ *
+ * @return has the compilation been successful
+ * @exception BuildException Description of Exception
+ */
+ boolean execute()
+ throws BuildException;
+
+ /**
+ * Maps source class files to the files generated by this rmic
+ * implementation.
+ *
+ * @return The Mapper value
+ */
+ FileNameMapper getMapper();
+
+ /**
+ * The CLASSPATH this rmic process will use.
+ *
+ * @return The Classpath value
+ */
+ Path getClasspath();
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/RmicAdapterFactory.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/RmicAdapterFactory.java
new file mode 100644
index 000000000..a8b21d048
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/RmicAdapterFactory.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.rmic;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+
+/**
+ * Creates the necessary rmic adapter, given basic criteria.
+ *
+ * @author Takashi Okamoto
+ * @author J D Glanville
+ */
+public class RmicAdapterFactory
+{
+
+ /**
+ * This is a singlton -- can't create instances!!
+ */
+ private RmicAdapterFactory() { }
+
+ /**
+ * Based on the parameter passed in, this method creates the necessary
+ * factory desired. The current mapping for rmic names are as follows:
+ *
+ * sun = SUN's rmic
+ * kaffe = Kaffe's rmic
+ * a fully quallified classname = the name of a rmic adapter
+ *
+ *
+ *
+ *
+ * @param rmicType either the name of the desired rmic, or the full
+ * classname of the rmic's adapter.
+ * @param task a task to log through.
+ * @return The Rmic value
+ * @throws BuildException if the rmic type could not be resolved into a rmic
+ * adapter.
+ */
+ public static RmicAdapter getRmic( String rmicType, Task task )
+ throws BuildException
+ {
+ if( rmicType == null )
+ {
+ /*
+ * When not specified rmicType, search SUN's rmic and
+ * Kaffe's rmic.
+ */
+ try
+ {
+ Class.forName( "sun.rmi.rmic.Main" );
+ rmicType = "sun";
+ }
+ catch( ClassNotFoundException cnfe )
+ {
+ try
+ {
+ Class.forName( "kaffe.rmi.rmic.RMIC" );
+ Class.forName( "kaffe.tools.compiler.Compiler" );
+ rmicType = "kaffe";
+ }
+ catch( ClassNotFoundException cnfk )
+ {
+ throw new BuildException( "Couldn\'t guess rmic implementation" );
+ }
+ }
+ }
+
+ if( rmicType.equalsIgnoreCase( "sun" ) )
+ {
+ return new SunRmic();
+ }
+ else if( rmicType.equalsIgnoreCase( "kaffe" ) )
+ {
+ return new KaffeRmic();
+ }
+ else if( rmicType.equalsIgnoreCase( "weblogic" ) )
+ {
+ return new WLRmic();
+ }
+ return resolveClassName( rmicType );
+ }
+
+ /**
+ * Tries to resolve the given classname into a rmic 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 RmicAdapter.
+ */
+ private static RmicAdapter resolveClassName( String className )
+ throws BuildException
+ {
+ try
+ {
+ Class c = Class.forName( className );
+ Object o = c.newInstance();
+ return ( RmicAdapter )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 rmic 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/rmic/SunRmic.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/SunRmic.java
new file mode 100644
index 000000000..6b375df25
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/SunRmic.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.rmic;
+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 rmic for SUN's JDK.
+ *
+ * @author Takashi Okamoto
+ */
+public class SunRmic extends DefaultRmicAdapter
+{
+
+ public boolean execute()
+ throws BuildException
+ {
+ getRmic().log( "Using SUN rmic compiler", Project.MSG_VERBOSE );
+ Commandline cmd = setupRmicCommand();
+
+ // Create an instance of the rmic, redirecting output to
+ // the project log
+ LogOutputStream logstr = new LogOutputStream( getRmic(), Project.MSG_WARN );
+
+ try
+ {
+ Class c = Class.forName( "sun.rmi.rmic.Main" );
+ Constructor cons = c.getConstructor( new Class[]
+ {OutputStream.class, String.class} );
+ Object rmic = cons.newInstance( new Object[]{logstr, "rmic"} );
+
+ Method doRmic = c.getMethod( "compile",
+ new Class[]{String[].class} );
+ Boolean ok = ( Boolean )doRmic.invoke( rmic,
+ ( new Object[]{cmd.getArguments()} ) );
+ return ok.booleanValue();
+ }
+ catch( ClassNotFoundException ex )
+ {
+ throw new BuildException( "Cannot use SUN rmic, as it is not available" +
+ " A common solution is to set the environment variable" +
+ " JAVA_HOME or CLASSPATH.", getRmic().getLocation() );
+ }
+ catch( Exception ex )
+ {
+ if( ex instanceof BuildException )
+ {
+ throw ( BuildException )ex;
+ }
+ else
+ {
+ throw new BuildException( "Error starting SUN rmic: ", ex, getRmic().getLocation() );
+ }
+ }
+ finally
+ {
+ try
+ {
+ logstr.close();
+ }
+ catch( IOException e )
+ {
+ throw new BuildException( e );
+ }
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/WLRmic.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/WLRmic.java
new file mode 100644
index 000000000..67fc6fcf4
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/WLRmic.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.taskdefs.rmic;
+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 rmic for WebLogic
+ *
+ * @author Takashi Okamoto
+ */
+public class WLRmic extends DefaultRmicAdapter
+{
+
+ /**
+ * Get the suffix for the rmic skeleton classes
+ *
+ * @return The SkelClassSuffix value
+ */
+ public String getSkelClassSuffix()
+ {
+ return "_WLSkel";
+ }
+
+ /**
+ * Get the suffix for the rmic stub classes
+ *
+ * @return The StubClassSuffix value
+ */
+ public String getStubClassSuffix()
+ {
+ return "_WLStub";
+ }
+
+ public boolean execute()
+ throws BuildException
+ {
+ getRmic().log( "Using WebLogic rmic", Project.MSG_VERBOSE );
+ Commandline cmd = setupRmicCommand( new String[]{"-noexit"} );
+
+ try
+ {
+ // Create an instance of the rmic
+ Class c = Class.forName( "weblogic.rmic" );
+ Method doRmic = c.getMethod( "main",
+ new Class[]{String[].class} );
+ doRmic.invoke( null, new Object[]{cmd.getArguments()} );
+ return true;
+ }
+ catch( ClassNotFoundException ex )
+ {
+ throw new BuildException( "Cannot use WebLogic rmic, as it is not available" +
+ " A common solution is to set the environment variable" +
+ " CLASSPATH.", getRmic().getLocation() );
+ }
+ catch( Exception ex )
+ {
+ if( ex instanceof BuildException )
+ {
+ throw ( BuildException )ex;
+ }
+ else
+ {
+ throw new BuildException( "Error starting WebLogic rmic: ", ex, getRmic().getLocation() );
+ }
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Commandline.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Commandline.java
new file mode 100644
index 000000000..b9b221d84
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Commandline.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.File;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+
+
+/**
+ * Commandline objects help handling command lines specifying processes to
+ * execute. The class can be used to define a command line as nested elements or
+ * as a helper to define a command line by an application.
+ *
+ *
+ * <someelement>
+ * <acommandline executable="/executable/to/run">
+ * <argument value="argument 1" />
+ * <argument line="argument_1 argument_2 argument_3"
+ * />
+ * <argument value="argument 4" />
+ * </acommandline>
+ * </someelement>
+ * The element someelement must provide a method createAcommandline
+ * which returns an instance of this class.
+ *
+ * @author thomas.haas@softwired-inc.com
+ * @author Stefan Bodewig
+ */
+public class Commandline implements Cloneable
+{
+
+ private Vector arguments = new Vector();
+ private String executable = null;
+
+ public Commandline( String to_process )
+ {
+ super();
+ String[] tmp = translateCommandline( to_process );
+ if( tmp != null && tmp.length > 0 )
+ {
+ setExecutable( tmp[0] );
+ for( int i = 1; i < tmp.length; i++ )
+ {
+ createArgument().setValue( tmp[i] );
+ }
+ }
+ }
+
+ public Commandline()
+ {
+ super();
+ }
+
+ /**
+ * Put quotes around the given String if necessary.
+ *
+ * If the argument doesn't include spaces or quotes, return it as is. If it
+ * contains double quotes, use single quotes - else surround the argument by
+ * double quotes.
+ *
+ * @param argument Description of Parameter
+ * @return Description of the Returned Value
+ */
+ public static String quoteArgument( String argument )
+ {
+ if( argument.indexOf( "\"" ) > -1 )
+ {
+ if( argument.indexOf( "\'" ) > -1 )
+ {
+ throw new BuildException( "Can\'t handle single and double quotes in same argument" );
+ }
+ else
+ {
+ return '\'' + argument + '\'';
+ }
+ }
+ else if( argument.indexOf( "\'" ) > -1 || argument.indexOf( " " ) > -1 )
+ {
+ return '\"' + argument + '\"';
+ }
+ else
+ {
+ return argument;
+ }
+ }
+
+ public static String toString( String[] line )
+ {
+ // empty path return empty string
+ if( line == null || line.length == 0 )
+ return "";
+
+ // path containing one or more elements
+ final StringBuffer result = new StringBuffer();
+ for( int i = 0; i < line.length; i++ )
+ {
+ if( i > 0 )
+ {
+ result.append( ' ' );
+ }
+ result.append( quoteArgument( line[i] ) );
+ }
+ return result.toString();
+ }
+
+ public static String[] translateCommandline( String to_process )
+ {
+ if( to_process == null || to_process.length() == 0 )
+ {
+ return new String[0];
+ }
+
+ // parse with a simple finite state machine
+
+ final int normal = 0;
+ final int inQuote = 1;
+ final int inDoubleQuote = 2;
+ int state = normal;
+ StringTokenizer tok = new StringTokenizer( to_process, "\"\' ", true );
+ Vector v = new Vector();
+ StringBuffer current = new StringBuffer();
+
+ while( tok.hasMoreTokens() )
+ {
+ String nextTok = tok.nextToken();
+ switch ( state )
+ {
+ case inQuote:
+ if( "\'".equals( nextTok ) )
+ {
+ state = normal;
+ }
+ else
+ {
+ current.append( nextTok );
+ }
+ break;
+ case inDoubleQuote:
+ if( "\"".equals( nextTok ) )
+ {
+ state = normal;
+ }
+ else
+ {
+ current.append( nextTok );
+ }
+ break;
+ default:
+ if( "\'".equals( nextTok ) )
+ {
+ state = inQuote;
+ }
+ else if( "\"".equals( nextTok ) )
+ {
+ state = inDoubleQuote;
+ }
+ else if( " ".equals( nextTok ) )
+ {
+ if( current.length() != 0 )
+ {
+ v.addElement( current.toString() );
+ current.setLength( 0 );
+ }
+ }
+ else
+ {
+ current.append( nextTok );
+ }
+ break;
+ }
+ }
+
+ if( current.length() != 0 )
+ {
+ v.addElement( current.toString() );
+ }
+
+ if( state == inQuote || state == inDoubleQuote )
+ {
+ throw new BuildException( "unbalanced quotes in " + to_process );
+ }
+
+ String[] args = new String[v.size()];
+ v.copyInto( args );
+ return args;
+ }
+
+
+ /**
+ * Sets the executable to run.
+ *
+ * @param executable The new Executable value
+ */
+ public void setExecutable( String executable )
+ {
+ if( executable == null || executable.length() == 0 )
+ return;
+ this.executable = executable.replace( '/', File.separatorChar )
+ .replace( '\\', File.separatorChar );
+ }
+
+
+ /**
+ * Returns all arguments defined by addLine, addValue
+ * or the argument object.
+ *
+ * @return The Arguments value
+ */
+ public String[] getArguments()
+ {
+ Vector result = new Vector( arguments.size() * 2 );
+ for( int i = 0; i < arguments.size(); i++ )
+ {
+ Argument arg = ( Argument )arguments.elementAt( i );
+ String[] s = arg.getParts();
+ for( int j = 0; j < s.length; j++ )
+ {
+ result.addElement( s[j] );
+ }
+ }
+
+ String[] res = new String[result.size()];
+ result.copyInto( res );
+ return res;
+ }
+
+ /**
+ * Returns the executable and all defined arguments.
+ *
+ * @return The Commandline value
+ */
+ public String[] getCommandline()
+ {
+ final String[] args = getArguments();
+ if( executable == null )
+ return args;
+ final String[] result = new String[args.length + 1];
+ result[0] = executable;
+ System.arraycopy( args, 0, result, 1, args.length );
+ return result;
+ }
+
+
+ public String getExecutable()
+ {
+ return executable;
+ }
+
+
+ public void addArguments( String[] line )
+ {
+ for( int i = 0; i < line.length; i++ )
+ {
+ createArgument().setValue( line[i] );
+ }
+ }
+
+ /**
+ * Clear out the whole command line.
+ */
+ public void clear()
+ {
+ executable = null;
+ arguments.removeAllElements();
+ }
+
+ /**
+ * Clear out the arguments but leave the executable in place for another
+ * operation.
+ */
+ public void clearArgs()
+ {
+ arguments.removeAllElements();
+ }
+
+ public Object clone()
+ {
+ Commandline c = new Commandline();
+ c.setExecutable( executable );
+ c.addArguments( getArguments() );
+ return c;
+ }
+
+ /**
+ * Creates an argument object. Each commandline object has at most one
+ * instance of the argument class.
+ *
+ * @return the argument object.
+ */
+ public Argument createArgument()
+ {
+ Argument argument = new Argument();
+ arguments.addElement( argument );
+ return argument;
+ }
+
+ /**
+ * Return a marker.
+ *
+ * This marker can be used to locate a position on the commandline - to
+ * insert something for example - when all parameters have been set.
+ *
+ * @return Description of the Returned Value
+ */
+ public Marker createMarker()
+ {
+ return new Marker( arguments.size() );
+ }
+
+ public int size()
+ {
+ return getCommandline().length;
+ }
+
+
+ public String toString()
+ {
+ return toString( getCommandline() );
+ }
+
+ /**
+ * Used for nested xml command line definitions.
+ *
+ * @author RT
+ */
+ public static class Argument
+ {
+
+ private String[] parts;
+
+ /**
+ * Sets a single commandline argument to the absolute filename of the
+ * given file.
+ *
+ * @param value a single commandline argument.
+ */
+ public void setFile( File value )
+ {
+ parts = new String[]{value.getAbsolutePath()};
+ }
+
+ /**
+ * Line to split into several commandline arguments.
+ *
+ * @param line line to split into several commandline arguments
+ */
+ public void setLine( String line )
+ {
+ parts = translateCommandline( line );
+ }
+
+ /**
+ * Sets a single commandline argument and treats it like a PATH -
+ * ensures the right separator for the local platform is used.
+ *
+ * @param value a single commandline argument.
+ */
+ public void setPath( Path value )
+ {
+ parts = new String[]{value.toString()};
+ }
+
+ /**
+ * Sets a single commandline argument.
+ *
+ * @param value a single commandline argument.
+ */
+ public void setValue( String value )
+ {
+ parts = new String[]{value};
+ }
+
+ /**
+ * Returns the parts this Argument consists of.
+ *
+ * @return The Parts value
+ */
+ public String[] getParts()
+ {
+ return parts;
+ }
+ }
+
+ /**
+ * Class to keep track of the position of an Argument.
+ *
+ * @author RT
+ */
+ // This class is there to support the srcfile and targetfile
+ // elements of <execon> and <transform> - don't know
+ // whether there might be additional use cases.
--SB
+ public class Marker
+ {
+ private int realPos = -1;
+
+ private int position;
+
+ Marker( int position )
+ {
+ this.position = position;
+ }
+
+ /**
+ * Return the number of arguments that preceeded this marker.
+ *
+ * The name of the executable - if set - is counted as the very first
+ * argument.
+ *
+ * @return The Position value
+ */
+ public int getPosition()
+ {
+ if( realPos == -1 )
+ {
+ realPos = ( executable == null ? 0 : 1 );
+ for( int i = 0; i < position; i++ )
+ {
+ Argument arg = ( Argument )arguments.elementAt( i );
+ realPos += arg.getParts().length;
+ }
+ }
+ return realPos;
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/CommandlineJava.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/CommandlineJava.java
new file mode 100644
index 000000000..489edcdc0
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/CommandlineJava.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.myrmidon.framework.Os;
+
+/**
+ * A representation of a Java command line that is nothing more than a composite
+ * of 2 Commandline . 1 for the vm/options and 1 for the
+ * classname/arguments. It provides specific methods for a java command line.
+ *
+ * @author thomas.haas@softwired-inc.com
+ * @author Stephane Bailliez
+ */
+public class CommandlineJava implements Cloneable
+{
+
+ private Commandline vmCommand = new Commandline();
+ private Commandline javaCommand = new Commandline();
+ private SysProperties sysProperties = new SysProperties();
+ private Path classpath = null;
+ private String maxMemory = null;
+
+ /**
+ * Indicate whether it will execute a jar file or not, in this case the
+ * first vm option must be a -jar and the 'executable' is a jar file.
+ */
+ private boolean executeJar = false;
+ private String vmVersion;
+
+ public CommandlineJava()
+ {
+ setVm( getJavaExecutableName() );
+ setVmversion( Project.getJavaVersion() );
+ }
+
+ /**
+ * set the classname to execute
+ *
+ * @param classname the fully qualified classname.
+ */
+ public void setClassname( String classname )
+ {
+ javaCommand.setExecutable( classname );
+ executeJar = false;
+ }
+
+ /**
+ * set a jar file to execute via the -jar option.
+ *
+ * @param jarpathname The new Jar value
+ */
+ public void setJar( String jarpathname )
+ {
+ javaCommand.setExecutable( jarpathname );
+ executeJar = true;
+ }
+
+ /**
+ * -mx or -Xmx depending on VM version
+ *
+ * @param max The new Maxmemory value
+ */
+ public void setMaxmemory( String max )
+ {
+ this.maxMemory = max;
+ }
+
+ public void setSystemProperties()
+ throws BuildException
+ {
+ sysProperties.setSystem();
+ }
+
+ public void setVm( String vm )
+ {
+ vmCommand.setExecutable( vm );
+ }
+
+ public void setVmversion( String value )
+ {
+ vmVersion = value;
+ }
+
+ /**
+ * @return the name of the class to run or null if there is no
+ * class.
+ * @see #getJar()
+ */
+ public String getClassname()
+ {
+ if( !executeJar )
+ {
+ return javaCommand.getExecutable();
+ }
+ return null;
+ }
+
+ public Path getClasspath()
+ {
+ return classpath;
+ }
+
+ /**
+ * get the command line to run a java vm.
+ *
+ * @return the list of all arguments necessary to run the vm.
+ */
+ public String[] getCommandline()
+ {
+ String[] result = new String[size()];
+ int pos = 0;
+ String[] vmArgs = getActualVMCommand().getCommandline();
+ // first argument is the java.exe path...
+ result[pos++] = vmArgs[0];
+
+ // -jar must be the first option in the command line.
+ if( executeJar )
+ {
+ result[pos++] = "-jar";
+ }
+ // next follows the vm options
+ System.arraycopy( vmArgs, 1, result, pos, vmArgs.length - 1 );
+ pos += vmArgs.length - 1;
+ // properties are part of the vm options...
+ if( sysProperties.size() > 0 )
+ {
+ System.arraycopy( sysProperties.getVariables(), 0,
+ result, pos, sysProperties.size() );
+ pos += sysProperties.size();
+ }
+ // classpath is a vm option too..
+ Path fullClasspath = classpath != null ? classpath.concatSystemClasspath( "ignore" ) : null;
+ if( fullClasspath != null && fullClasspath.toString().trim().length() > 0 )
+ {
+ result[pos++] = "-classpath";
+ result[pos++] = fullClasspath.toString();
+ }
+ // this is the classname to run as well as its arguments.
+ // in case of 'executeJar', the executable is a jar file.
+ System.arraycopy( javaCommand.getCommandline(), 0,
+ result, pos, javaCommand.size() );
+ return result;
+ }
+
+ /**
+ * @return the pathname of the jar file to run via -jar option or null
+ * if there is no jar to run.
+ * @see #getClassname()
+ */
+ public String getJar()
+ {
+ if( executeJar )
+ {
+ return javaCommand.getExecutable();
+ }
+ return null;
+ }
+
+ public Commandline getJavaCommand()
+ {
+ return javaCommand;
+ }
+
+ public SysProperties getSystemProperties()
+ {
+ return sysProperties;
+ }
+
+ public Commandline getVmCommand()
+ {
+ return getActualVMCommand();
+ }
+
+ public String getVmversion()
+ {
+ return vmVersion;
+ }
+
+ public void addSysproperty( Environment.Variable sysp )
+ {
+ sysProperties.addVariable( sysp );
+ }
+
+ /**
+ * Clear out the java arguments.
+ */
+ public void clearJavaArgs()
+ {
+ javaCommand.clearArgs();
+ }
+
+ public Object clone()
+ {
+ CommandlineJava c = new CommandlineJava();
+ c.vmCommand = ( Commandline )vmCommand.clone();
+ c.javaCommand = ( Commandline )javaCommand.clone();
+ c.sysProperties = ( SysProperties )sysProperties.clone();
+ c.maxMemory = maxMemory;
+ if( classpath != null )
+ {
+ c.classpath = ( Path )classpath.clone();
+ }
+ c.vmVersion = vmVersion;
+ c.executeJar = executeJar;
+ return c;
+ }
+
+ public Commandline.Argument createArgument()
+ {
+ return javaCommand.createArgument();
+ }
+
+ public Path createClasspath( Project p )
+ {
+ if( classpath == null )
+ {
+ classpath = new Path( p );
+ }
+ return classpath;
+ }
+
+ public Commandline.Argument createVmArgument()
+ {
+ return vmCommand.createArgument();
+ }
+
+ public void restoreSystemProperties()
+ throws BuildException
+ {
+ sysProperties.restoreSystem();
+ }
+
+ /**
+ * The size of the java command line.
+ *
+ * @return the total number of arguments in the java command line.
+ * @see #getCommandline()
+ */
+ public int size()
+ {
+ int size = getActualVMCommand().size() + javaCommand.size() + sysProperties.size();
+ // classpath is "-classpath " -> 2 args
+ Path fullClasspath = classpath != null ? classpath.concatSystemClasspath( "ignore" ) : null;
+ if( fullClasspath != null && fullClasspath.toString().trim().length() > 0 )
+ {
+ size += 2;
+ }
+ // jar execution requires an additional -jar option
+ if( executeJar )
+ {
+ size++;
+ }
+ return size;
+ }
+
+
+ public String toString()
+ {
+ return Commandline.toString( getCommandline() );
+ }
+
+ private Commandline getActualVMCommand()
+ {
+ Commandline actualVMCommand = ( Commandline )vmCommand.clone();
+ if( maxMemory != null )
+ {
+ if( vmVersion.startsWith( "1.1" ) )
+ {
+ actualVMCommand.createArgument().setValue( "-mx" + maxMemory );
+ }
+ else
+ {
+ actualVMCommand.createArgument().setValue( "-Xmx" + maxMemory );
+ }
+ }
+ return actualVMCommand;
+ }
+
+ private String getJavaExecutableName()
+ {
+ // 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/java" + extension );
+
+ if( jExecutable.exists() && !Os.isFamily( "netware" ) )
+ {
+ // NetWare may have a "java" in that directory, but 99% of
+ // the time, you don't want to execute it -- Jeff Tulley
+ //
+ return jExecutable.getAbsolutePath();
+ }
+ else
+ {
+ return "java";
+ }
+ }
+
+ /**
+ * Specialized Environment class for System properties
+ *
+ * @author RT
+ */
+ public static class SysProperties extends Environment implements Cloneable
+ {
+ Properties sys = null;
+
+ public void setSystem()
+ throws BuildException
+ {
+ try
+ {
+ Properties p = new Properties( sys = System.getProperties() );
+
+ for( Enumeration e = variables.elements(); e.hasMoreElements(); )
+ {
+ Environment.Variable v = ( Environment.Variable )e.nextElement();
+ p.put( v.getKey(), v.getValue() );
+ }
+ System.setProperties( p );
+ }
+ catch( SecurityException e )
+ {
+ throw new BuildException( "Cannot modify system properties", e );
+ }
+ }
+
+ public String[] getVariables()
+ throws BuildException
+ {
+ String props[] = super.getVariables();
+
+ if( props == null )
+ return null;
+
+ for( int i = 0; i < props.length; i++ )
+ {
+ props[i] = "-D" + props[i];
+ }
+ return props;
+ }
+
+ public Object clone()
+ {
+ try
+ {
+ SysProperties c = ( SysProperties )super.clone();
+ c.variables = ( Vector )variables.clone();
+ return c;
+ }
+ catch( CloneNotSupportedException e )
+ {
+ return null;
+ }
+ }
+
+ public void restoreSystem()
+ throws BuildException
+ {
+ if( sys == null )
+ throw new BuildException( "Unbalanced nesting of SysProperties" );
+
+ try
+ {
+ System.setProperties( sys );
+ sys = null;
+ }
+ catch( SecurityException e )
+ {
+ throw new BuildException( "Cannot modify system properties", e );
+ }
+ }
+
+ public int size()
+ {
+ return variables.size();
+ }
+
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/DataType.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/DataType.java
new file mode 100644
index 000000000..6bc7085f0
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/DataType.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.util.Stack;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.ProjectComponent;
+
+/**
+ * Base class for those classes that can appear inside the build file as stand
+ * alone data types.
+ *
+ * This class handles the common description attribute and provides a default
+ * implementation for reference handling and checking for circular references
+ * that is appropriate for types that can not be nested inside elements of the
+ * same type (i.e. <patternset> but not <path>).
+ *
+ * @author Stefan Bodewig
+ */
+public abstract class DataType extends ProjectComponent
+{
+ /**
+ * The descriptin the user has set.
+ */
+ protected String description = null;
+ /**
+ * Value to the refid attribute.
+ */
+ protected Reference ref = null;
+ /**
+ * Are we sure we don't hold circular references?
+ *
+ * Subclasses are responsible for setting this value to false if we'd need
+ * to investigate this condition (usually because a child element has been
+ * added that is a subclass of DataType).
+ */
+ protected boolean checked = true;
+
+ /**
+ * Sets a description of the current data type. It will be useful in
+ * commenting what we are doing.
+ *
+ * @param desc The new Description value
+ */
+ public void setDescription( String desc )
+ {
+ description = desc;
+ }
+
+ /**
+ * Set the value of the refid attribute.
+ *
+ * Subclasses may need to check whether any other attributes have been set
+ * as well or child elements have been created and thus override this
+ * method. if they do the must call super.setRefid.
+ *
+ * @param ref The new Refid value
+ */
+ public void setRefid( Reference ref )
+ {
+ this.ref = ref;
+ checked = false;
+ }
+
+ /**
+ * Return the description for the current data type.
+ *
+ * @return The Description value
+ */
+ public String getDescription()
+ {
+ return description;
+ }
+
+ /**
+ * Has the refid attribute of this element been set?
+ *
+ * @return The Reference value
+ */
+ public boolean isReference()
+ {
+ return ref != null;
+ }
+
+ /**
+ * Performs the check for circular references and returns the referenced
+ * object.
+ *
+ * @param requiredClass Description of Parameter
+ * @param dataTypeName Description of Parameter
+ * @return The CheckedRef value
+ */
+ protected Object getCheckedRef( Class requiredClass, String dataTypeName )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, getProject() );
+ }
+
+ Object o = ref.getReferencedObject( getProject() );
+ if( !( requiredClass.isAssignableFrom( o.getClass() ) ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a " + dataTypeName;
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return o;
+ }
+ }
+
+ /**
+ * Creates an exception that indicates the user has generated a loop of data
+ * types referencing each other.
+ *
+ * @return Description of the Returned Value
+ */
+ protected BuildException circularReference()
+ {
+ return new BuildException( "This data type contains a circular reference." );
+ }
+
+ /**
+ * Check to see whether any DataType we hold references to is included in
+ * the Stack (which holds all DataType instances that directly or indirectly
+ * reference this instance, including this instance itself).
+ *
+ * If one is included, throw a BuildException created by {@link
+ * #circularReference circularReference}.
+ *
+ * This implementation is appropriate only for a DataType that cannot hold
+ * other DataTypes as children.
+ *
+ * The general contract of this method is that it shouldn't do anything if
+ * {@link #checked checked} is true and set it to true on exit.
+ *
+ *
+ * @param stk Description of Parameter
+ * @param p Description of Parameter
+ * @exception BuildException Description of Exception
+ */
+ protected void dieOnCircularReference( Stack stk, Project p )
+ throws BuildException
+ {
+
+ if( checked || !isReference() )
+ {
+ return;
+ }
+ Object o = ref.getReferencedObject( p );
+
+ if( o instanceof DataType )
+ {
+ if( stk.contains( o ) )
+ {
+ throw circularReference();
+ }
+ else
+ {
+ stk.push( o );
+ ( ( DataType )o ).dieOnCircularReference( stk, p );
+ stk.pop();
+ }
+ }
+ checked = true;
+ }
+
+ /**
+ * 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
+ */
+ protected BuildException noChildrenAllowed()
+ {
+ return new BuildException( "You must not specify nested elements when using refid" );
+ }
+
+ /**
+ * Creates an exception that indicates that refid has to be the only
+ * attribute if it is set.
+ *
+ * @return Description of the Returned Value
+ */
+ protected BuildException tooManyAttributes()
+ {
+ return new BuildException( "You must not specify more than one attribute" +
+ " when using refid" );
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Description.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Description.java
new file mode 100644
index 000000000..861006aac
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Description.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.types;
+
+
+/**
+ * Description is used to provide a project-wide description element (that is, a
+ * description that applies to a buildfile as a whole). If present, the
+ * <description> element is printed out before the target descriptions.
+ * Description has no attributes, only text. There can only be one project
+ * description per project. A second description element will overwrite the
+ * first.
+ *
+ * @author Craeg Strong
+ * @version $Revision$ $Date$
+ */
+public class Description extends DataType
+{
+
+ /**
+ * Adds descriptive text to the project.
+ *
+ * @param text The feature to be added to the Text attribute
+ */
+ public void addText( String text )
+ {
+ String currentDescription = project.getDescription();
+ if( currentDescription == null )
+ {
+ project.setDescription( text );
+ }
+ else
+ {
+ project.setDescription( currentDescription + text );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/EnumeratedAttribute.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/EnumeratedAttribute.java
new file mode 100644
index 000000000..24bd08bd3
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/EnumeratedAttribute.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.types;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Helper class for attributes that can only take one of a fixed list of values.
+ *
+ *
+ * See {@link org.apache.tools.ant.taskdefs.FixCRLF FixCRLF} for an example.
+ *
+ * @author Stefan Bodewig
+ */
+public abstract class EnumeratedAttribute
+{
+
+ protected String value;
+
+ public EnumeratedAttribute() { }
+
+ /**
+ * Invoked by {@link org.apache.tools.ant.IntrospectionHelper
+ * IntrospectionHelper}.
+ *
+ * @param value The new Value value
+ * @exception BuildException Description of Exception
+ */
+ public final void setValue( String value )
+ throws BuildException
+ {
+ if( !containsValue( value ) )
+ {
+ throw new BuildException( value + " is not a legal value for this attribute" );
+ }
+ this.value = value;
+ }
+
+ /**
+ * Retrieves the value.
+ *
+ * @return The Value value
+ */
+ public final String getValue()
+ {
+ return value;
+ }
+
+ /**
+ * This is the only method a subclass needs to implement.
+ *
+ * @return an array holding all possible values of the enumeration.
+ */
+ public abstract String[] getValues();
+
+ /**
+ * Is this value included in the enumeration?
+ *
+ * @param value Description of Parameter
+ * @return Description of the Returned Value
+ */
+ public final boolean containsValue( String value )
+ {
+ String[] values = getValues();
+ if( values == null || value == null )
+ {
+ return false;
+ }
+
+ for( int i = 0; i < values.length; i++ )
+ {
+ if( value.equals( values[i] ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Environment.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Environment.java
new file mode 100644
index 000000000..ca2cd0260
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Environment.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.types;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Wrapper for environment variables.
+ *
+ * @author Stefan Bodewig
+ */
+public class Environment
+{
+
+ protected Vector variables;
+
+ public Environment()
+ {
+ variables = new Vector();
+ }
+
+ public String[] getVariables()
+ throws BuildException
+ {
+ if( variables.size() == 0 )
+ {
+ return null;
+ }
+ String[] result = new String[variables.size()];
+ for( int i = 0; i < result.length; i++ )
+ {
+ result[i] = ( ( Variable )variables.elementAt( i ) ).getContent();
+ }
+ return result;
+ }
+
+ public void addVariable( Variable var )
+ {
+ variables.addElement( var );
+ }
+
+ public static class Variable
+ {
+ private String key, value;
+
+ public Variable()
+ {
+ super();
+ }
+
+ public void setFile( java.io.File file )
+ {
+ this.value = file.getAbsolutePath();
+ }
+
+ public void setKey( String key )
+ {
+ this.key = key;
+ }
+
+ public void setPath( Path path )
+ {
+ this.value = path.toString();
+ }
+
+ public void setValue( String value )
+ {
+ this.value = value;
+ }
+
+ public String getContent()
+ throws BuildException
+ {
+ if( key == null || value == null )
+ {
+ throw new BuildException( "key and value must be specified for environment variables." );
+ }
+ StringBuffer sb = new StringBuffer( key.trim() );
+ sb.append( "=" ).append( value.trim() );
+ return sb.toString();
+ }
+
+ public String getKey()
+ {
+ return this.key;
+ }
+
+ public String getValue()
+ {
+ return this.value;
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FileList.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FileList.java
new file mode 100644
index 000000000..1d369eb8a
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FileList.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.File;
+import java.util.Stack;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * FileList represents an explicitly named list of files. FileLists are useful
+ * when you want to capture a list of files regardless of whether they currently
+ * exist. By contrast, FileSet operates as a filter, only returning the name of
+ * a matched file if it currently exists in the file system.
+ *
+ * @author Craeg Strong
+ * @version $Revision$ $Date$
+ */
+public class FileList extends DataType
+{
+
+ private Vector filenames = new Vector();
+ private File dir;
+
+ public FileList()
+ {
+ super();
+ }
+
+ protected FileList( FileList filelist )
+ {
+ this.dir = filelist.dir;
+ this.filenames = filelist.filenames;
+ setProject( filelist.getProject() );
+ }
+
+ public void setDir( File dir )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ this.dir = dir;
+ }
+
+ public void setFiles( String filenames )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( filenames != null && filenames.length() > 0 )
+ {
+ StringTokenizer tok = new StringTokenizer( filenames, ", \t\n\r\f", false );
+ while( tok.hasMoreTokens() )
+ {
+ this.filenames.addElement( tok.nextToken() );
+ }
+ }
+ }
+
+ /**
+ * Makes this instance in effect a reference to another FileList instance.
+ *
+ *
+ * You must not set another attribute or nest elements inside this element
+ * if you make it a reference.
+ *
+ * @param r The new Refid value
+ * @exception BuildException Description of Exception
+ */
+ public void setRefid( Reference r )
+ throws BuildException
+ {
+ if( ( dir != null ) || ( filenames.size() != 0 ) )
+ {
+ throw tooManyAttributes();
+ }
+ super.setRefid( r );
+ }
+
+ public File getDir( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getDir( p );
+ }
+ return dir;
+ }
+
+ /**
+ * Returns the list of files represented by this FileList.
+ *
+ * @param p Description of Parameter
+ * @return The Files value
+ */
+ public String[] getFiles( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getFiles( p );
+ }
+
+ if( dir == null )
+ {
+ throw new BuildException( "No directory specified for filelist." );
+ }
+
+ if( filenames.size() == 0 )
+ {
+ throw new BuildException( "No files specified for filelist." );
+ }
+
+ String result[] = new String[filenames.size()];
+ filenames.copyInto( result );
+ return result;
+ }
+
+ /**
+ * Performs the check for circular references and returns the referenced
+ * FileList.
+ *
+ * @param p Description of Parameter
+ * @return The Ref value
+ */
+ protected FileList getRef( Project p )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, p );
+ }
+
+ Object o = ref.getReferencedObject( p );
+ if( !( o instanceof FileList ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a filelist";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( FileList )o;
+ }
+ }
+
+}//-- FileList.java
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FileSet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FileSet.java
new file mode 100644
index 000000000..5961775ab
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FileSet.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.File;
+import java.util.Stack;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.FileScanner;
+import org.apache.tools.ant.Project;
+
+/**
+ * Moved out of MatchingTask to make it a standalone object that could be
+ * referenced (by scripts for example).
+ *
+ * @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
+ * @author Magesh Umasankar
+ */
+public class FileSet extends DataType implements Cloneable
+{
+
+ private PatternSet defaultPatterns = new PatternSet();
+ private Vector additionalPatterns = new Vector();
+ private boolean useDefaultExcludes = true;
+ private boolean isCaseSensitive = true;
+
+ private File dir;
+
+ public FileSet()
+ {
+ super();
+ }
+
+ protected FileSet( FileSet fileset )
+ {
+ this.dir = fileset.dir;
+ this.defaultPatterns = fileset.defaultPatterns;
+ this.additionalPatterns = fileset.additionalPatterns;
+ this.useDefaultExcludes = fileset.useDefaultExcludes;
+ this.isCaseSensitive = fileset.isCaseSensitive;
+ setProject( getProject() );
+ }
+
+ /**
+ * Sets case sensitivity of the file system
+ *
+ * @param isCaseSensitive "true"|"on"|"yes" if file system is case
+ * sensitive, "false"|"off"|"no" when not.
+ */
+ public void setCaseSensitive( boolean isCaseSensitive )
+ {
+ this.isCaseSensitive = isCaseSensitive;
+ }
+
+ /**
+ * 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 )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ this.useDefaultExcludes = useDefaultExcludes;
+ }
+
+ public void setDir( File dir )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ 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 )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ defaultPatterns.setExcludes( excludes );
+ }
+
+ /**
+ * Sets the name of the file containing the includes patterns.
+ *
+ * @param excl The file to fetch the exclude patterns from.
+ * @exception BuildException Description of Exception
+ */
+ public void setExcludesfile( File excl )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ defaultPatterns.setExcludesfile( excl );
+ }
+
+ /**
+ * 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 )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ defaultPatterns.setIncludes( includes );
+ }
+
+ /**
+ * Sets the name of the file containing the includes patterns.
+ *
+ * @param incl The file to fetch the include patterns from.
+ * @exception BuildException Description of Exception
+ */
+ public void setIncludesfile( File incl )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ defaultPatterns.setIncludesfile( incl );
+ }
+
+
+ /**
+ * Makes this instance in effect a reference to another PatternSet instance.
+ *
+ *
+ * You must not set another attribute or nest elements inside this element
+ * if you make it a reference.
+ *
+ * @param r The new Refid value
+ * @exception BuildException Description of Exception
+ */
+ public void setRefid( Reference r )
+ throws BuildException
+ {
+ if( dir != null || defaultPatterns.hasPatterns() )
+ {
+ throw tooManyAttributes();
+ }
+ if( !additionalPatterns.isEmpty() )
+ {
+ throw noChildrenAllowed();
+ }
+ super.setRefid( r );
+ }
+
+ public void setupDirectoryScanner( FileScanner ds, Project p )
+ {
+ if( ds == null )
+ {
+ throw new IllegalArgumentException( "ds cannot be null" );
+ }
+
+ ds.setBasedir( dir );
+
+ for( int i = 0; i < additionalPatterns.size(); i++ )
+ {
+ Object o = additionalPatterns.elementAt( i );
+ defaultPatterns.append( ( PatternSet )o, p );
+ }
+
+ p.log( "FileSet: Setup file scanner in dir " + dir +
+ " with " + defaultPatterns, p.MSG_DEBUG );
+
+ ds.setIncludes( defaultPatterns.getIncludePatterns( p ) );
+ ds.setExcludes( defaultPatterns.getExcludePatterns( p ) );
+ if( useDefaultExcludes )
+ ds.addDefaultExcludes();
+ ds.setCaseSensitive( isCaseSensitive );
+ }
+
+ public File getDir( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getDir( p );
+ }
+ return dir;
+ }
+
+ /**
+ * Returns the directory scanner needed to access the files to process.
+ *
+ * @param p Description of Parameter
+ * @return The DirectoryScanner value
+ */
+ public DirectoryScanner getDirectoryScanner( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getDirectoryScanner( p );
+ }
+
+ if( dir == null )
+ {
+ throw new BuildException( "No directory specified for fileset." );
+ }
+
+ if( !dir.exists() )
+ {
+ throw new BuildException( dir.getAbsolutePath() + " not found." );
+ }
+ if( !dir.isDirectory() )
+ {
+ throw new BuildException( dir.getAbsolutePath() + " is not a directory." );
+ }
+
+ DirectoryScanner ds = new DirectoryScanner();
+ setupDirectoryScanner( ds, p );
+ ds.scan();
+ return ds;
+ }
+
+ /**
+ * Return a FileSet that has the same basedir and same patternsets as this
+ * one.
+ *
+ * @return Description of the Returned Value
+ */
+ public Object clone()
+ {
+ if( isReference() )
+ {
+ return new FileSet( getRef( getProject() ) );
+ }
+ else
+ {
+ return new FileSet( this );
+ }
+ }
+
+ /**
+ * add a name entry on the exclude list
+ *
+ * @return Description of the Returned Value
+ */
+ public PatternSet.NameEntry createExclude()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return defaultPatterns.createExclude();
+ }
+
+ /**
+ * add a name entry on the include files list
+ *
+ * @return Description of the Returned Value
+ */
+ public PatternSet.NameEntry createExcludesFile()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return defaultPatterns.createExcludesFile();
+ }
+
+ /**
+ * add a name entry on the include list
+ *
+ * @return Description of the Returned Value
+ */
+ public PatternSet.NameEntry createInclude()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return defaultPatterns.createInclude();
+ }
+
+ /**
+ * add a name entry on the include files list
+ *
+ * @return Description of the Returned Value
+ */
+ public PatternSet.NameEntry createIncludesFile()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return defaultPatterns.createIncludesFile();
+ }
+
+ public PatternSet createPatternSet()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ PatternSet patterns = new PatternSet();
+ additionalPatterns.addElement( patterns );
+ return patterns;
+ }
+
+ /**
+ * Performs the check for circular references and returns the referenced
+ * FileSet.
+ *
+ * @param p Description of Parameter
+ * @return The Ref value
+ */
+ protected FileSet getRef( Project p )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, p );
+ }
+
+ Object o = ref.getReferencedObject( p );
+ if( !( o instanceof FileSet ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a fileset";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( FileSet )o;
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FilterSet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FilterSet.java
new file mode 100644
index 000000000..927114f4a
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FilterSet.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;// java io classes
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;// java util classes
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;// ant classes
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+
+/**
+ * A set of filters to be applied to something. A filter set may have begintoken
+ * and endtokens defined.
+ *
+ * @author Michael McCallum
+ * @created 14 March 2001
+ */
+public class FilterSet extends DataType implements Cloneable
+{
+
+ /**
+ * The default token start string
+ */
+ public final static String DEFAULT_TOKEN_START = "@";
+
+ /**
+ * The default token end string
+ */
+ public final static String DEFAULT_TOKEN_END = "@";
+
+ private String startOfToken = DEFAULT_TOKEN_START;
+ private String endOfToken = DEFAULT_TOKEN_END;
+
+ /**
+ * List of ordered filters and filter files.
+ */
+ private Vector filters = new Vector();
+
+ public FilterSet() { }
+
+ /**
+ * Create a Filterset from another filterset
+ *
+ * @param filterset the filterset upon which this filterset will be based.
+ */
+ protected FilterSet( FilterSet filterset )
+ {
+ super();
+ this.filters = ( Vector )filterset.getFilters().clone();
+ }
+
+ /**
+ * The string used to id the beginning of a token.
+ *
+ * @param startOfToken The new Begintoken value
+ */
+ public void setBeginToken( String startOfToken )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( startOfToken == null || "".equals( startOfToken ) )
+ {
+ throw new BuildException( "beginToken must not be empty" );
+ }
+ this.startOfToken = startOfToken;
+ }
+
+
+ /**
+ * The string used to id the end of a token.
+ *
+ * @param endOfToken The new Endtoken value
+ */
+ public void setEndToken( String endOfToken )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( endOfToken == null || "".equals( endOfToken ) )
+ {
+ throw new BuildException( "endToken must not be empty" );
+ }
+ this.endOfToken = endOfToken;
+ }
+
+ /**
+ * set the file containing the filters for this filterset.
+ *
+ * @param filtersFile sets the filter fil to read filters for this filter
+ * set from.
+ * @exception BuildException if there is a problem reading the filters
+ */
+ public void setFiltersfile( File filtersFile )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ readFiltersFromFile( filtersFile );
+ }
+
+ public String getBeginToken()
+ {
+ if( isReference() )
+ {
+ return getRef().getBeginToken();
+ }
+ return startOfToken;
+ }
+
+ public String getEndToken()
+ {
+ if( isReference() )
+ {
+ return getRef().getEndToken();
+ }
+ return endOfToken;
+ }
+
+ /**
+ * Gets the filter hash of the FilterSet.
+ *
+ * @return The hash of the tokens and values for quick lookup.
+ */
+ public Hashtable getFilterHash()
+ {
+ int filterSize = getFilters().size();
+ Hashtable filterHash = new Hashtable( filterSize );
+ for( Enumeration e = getFilters().elements(); e.hasMoreElements(); )
+ {
+ Filter filter = ( Filter )e.nextElement();
+ filterHash.put( filter.getToken(), filter.getValue() );
+ }
+ return filterHash;
+ }
+
+ /**
+ * Create a new filter
+ *
+ * @param filter The feature to be added to the Filter attribute
+ */
+ public void addFilter( Filter filter )
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ filters.addElement( filter );
+ }
+
+ /**
+ * Add a new filter made from the given token and value.
+ *
+ * @param token The token for the new filter.
+ * @param value The value for the new filter.
+ */
+ public void addFilter( String token, String value )
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ filters.addElement( new Filter( token, value ) );
+ }
+
+ /**
+ * Add a Filterset to this filter set
+ *
+ * @param filterSet the filterset to be added to this filterset
+ */
+ public void addFilterSet( FilterSet filterSet )
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ for( Enumeration e = filterSet.getFilters().elements(); e.hasMoreElements(); )
+ {
+ filters.addElement( ( Filter )e.nextElement() );
+ }
+ }
+
+ public Object clone()
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ return new FilterSet( getRef() );
+ }
+ else
+ {
+ return new FilterSet( this );
+ }
+ }
+
+ /**
+ * Create a new FiltersFile
+ *
+ * @return The filter that was created.
+ */
+ public FiltersFile createFiltersfile()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return new FiltersFile();
+ }
+
+ /**
+ * Test to see if this filter set it empty.
+ *
+ * @return Return true if there are filter in this set otherwise false.
+ */
+ public boolean hasFilters()
+ {
+ return getFilters().size() > 0;
+ }
+
+
+ /**
+ * Read the filters from the given file.
+ *
+ * @param filtersFile the file from which filters are read
+ * @exception BuildException Throw a build exception when unable to read the
+ * file.
+ */
+ public void readFiltersFromFile( File filtersFile )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+
+ if( filtersFile.isFile() )
+ {
+ log( "Reading filters from " + filtersFile, Project.MSG_VERBOSE );
+ FileInputStream in = null;
+ try
+ {
+ Properties props = new Properties();
+ in = new FileInputStream( filtersFile );
+ props.load( in );
+
+ Enumeration enum = props.propertyNames();
+ Vector filters = getFilters();
+ while( enum.hasMoreElements() )
+ {
+ String strPropName = ( String )enum.nextElement();
+ String strValue = props.getProperty( strPropName );
+ filters.addElement( new Filter( strPropName, strValue ) );
+ }
+ }
+ catch( Exception e )
+ {
+ throw new BuildException( "Could not read filters from file: " + filtersFile );
+ }
+ finally
+ {
+ if( in != null )
+ {
+ try
+ {
+ in.close();
+ }
+ catch( IOException ioex )
+ {
+ }
+ }
+ }
+ }
+ else
+ {
+ throw new BuildException( "Must specify a file not a directory in the filtersfile attribute:" + filtersFile );
+ }
+ }
+
+ /**
+ * Does replacement on the given string with token matching. This uses the
+ * defined begintoken and endtoken values which default to @ for both.
+ *
+ * @param line The line to process the tokens in.
+ * @return The string with the tokens replaced.
+ */
+ public String replaceTokens( String line )
+ {
+ String beginToken = getBeginToken();
+ String endToken = getEndToken();
+ int index = line.indexOf( beginToken );
+
+ if( index > -1 )
+ {
+ Hashtable tokens = getFilterHash();
+ try
+ {
+ StringBuffer b = new StringBuffer();
+ int i = 0;
+ String token = null;
+ String value = null;
+
+ do
+ {
+ int endIndex = line.indexOf( endToken, index + beginToken.length() + 1 );
+ if( endIndex == -1 )
+ {
+ break;
+ }
+ token = line.substring( index + beginToken.length(), endIndex );
+ b.append( line.substring( i, index ) );
+ if( tokens.containsKey( token ) )
+ {
+ value = ( String )tokens.get( token );
+ log( "Replacing: " + beginToken + token + endToken + " -> " + value, Project.MSG_VERBOSE );
+ b.append( value );
+ i = index + beginToken.length() + token.length() + endToken.length();
+ }
+ else
+ {
+ // just append beginToken and search further
+ b.append( beginToken );
+ i = index + beginToken.length();
+ }
+ }while ( ( index = line.indexOf( beginToken, i ) ) > -1 );
+
+ b.append( line.substring( i ) );
+ return b.toString();
+ }
+ catch( StringIndexOutOfBoundsException e )
+ {
+ return line;
+ }
+ }
+ else
+ {
+ return line;
+ }
+ }
+
+ protected Vector getFilters()
+ {
+ if( isReference() )
+ {
+ return getRef().getFilters();
+ }
+ return filters;
+ }
+
+ protected FilterSet getRef()
+ {
+ return ( FilterSet )getCheckedRef( FilterSet.class, "filterset" );
+ }
+
+ /**
+ * Individual filter component of filterset
+ *
+ * @author Michael McCallum
+ * @created 14 March 2001
+ */
+ public static class Filter
+ {
+ /**
+ * Token which will be replaced in the filter operation
+ */
+ String token;
+
+ /**
+ * The value which will replace the token in the filtering operation
+ */
+ String value;
+
+ /**
+ * Constructor for the Filter object
+ *
+ * @param token The token which will be replaced when filtering
+ * @param value The value which will replace the token when filtering
+ */
+ public Filter( String token, String value )
+ {
+ this.token = token;
+ this.value = value;
+ }
+
+ /**
+ * No argument conmstructor
+ */
+ public Filter() { }
+
+ /**
+ * Sets the Token attribute of the Filter object
+ *
+ * @param token The new Token value
+ */
+ public void setToken( String token )
+ {
+ this.token = token;
+ }
+
+ /**
+ * Sets the Value attribute of the Filter object
+ *
+ * @param value The new Value value
+ */
+ public void setValue( String value )
+ {
+ this.value = value;
+ }
+
+ /**
+ * Gets the Token attribute of the Filter object
+ *
+ * @return The Token value
+ */
+ public String getToken()
+ {
+ return token;
+ }
+
+ /**
+ * Gets the Value attribute of the Filter object
+ *
+ * @return The Value value
+ */
+ public String getValue()
+ {
+ return value;
+ }
+ }
+
+ /**
+ * The filtersfile nested element.
+ *
+ * @author Michael McCallum
+ * @created Thursday, April 19, 2001
+ */
+ public class FiltersFile
+ {
+
+ /**
+ * Constructor for the Filter object
+ */
+ public FiltersFile() { }
+
+ /**
+ * Sets the file from which filters will be read.
+ *
+ * @param file the file from which filters will be read.
+ */
+ public void setFile( File file )
+ {
+ readFiltersFromFile( file );
+ }
+ }
+
+}
+
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FilterSetCollection.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FilterSetCollection.java
new file mode 100644
index 000000000..9bc5162a9
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/FilterSetCollection.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.types;// java io classes
+// java util classes
+import java.util.Enumeration;
+import java.util.Vector;
+
+// ant classes
+
+
+
+/**
+ * A FilterSetCollection is a collection of filtersets each of which may have a
+ * different start/end token settings.
+ *
+ * @author Conor MacNeill
+ */
+public class FilterSetCollection
+{
+
+ private Vector filterSets = new Vector();
+
+ public FilterSetCollection() { }
+
+ public FilterSetCollection( FilterSet filterSet )
+ {
+ addFilterSet( filterSet );
+ }
+
+
+ public void addFilterSet( FilterSet filterSet )
+ {
+ filterSets.addElement( filterSet );
+ }
+
+ /**
+ * Test to see if this filter set it empty.
+ *
+ * @return Return true if there are filter in this set otherwise false.
+ */
+ public boolean hasFilters()
+ {
+ for( Enumeration e = filterSets.elements(); e.hasMoreElements(); )
+ {
+ FilterSet filterSet = ( FilterSet )e.nextElement();
+ if( filterSet.hasFilters() )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Does replacement on the given string with token matching. This uses the
+ * defined begintoken and endtoken values which default to @ for both.
+ *
+ * @param line The line to process the tokens in.
+ * @return The string with the tokens replaced.
+ */
+ public String replaceTokens( String line )
+ {
+ String replacedLine = line;
+ for( Enumeration e = filterSets.elements(); e.hasMoreElements(); )
+ {
+ FilterSet filterSet = ( FilterSet )e.nextElement();
+ replacedLine = filterSet.replaceTokens( replacedLine );
+ }
+ return replacedLine;
+ }
+}
+
+
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Mapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Mapper.java
new file mode 100644
index 000000000..b0df6dc91
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Mapper.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.util.Properties;
+import java.util.Stack;
+import org.apache.tools.ant.AntClassLoader;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.util.FileNameMapper;
+
+/**
+ * Element to define a FileNameMapper.
+ *
+ * @author Stefan Bodewig
+ */
+public class Mapper extends DataType implements Cloneable
+{
+
+ protected MapperType type = null;
+
+ protected String classname = null;
+
+ protected Path classpath = null;
+
+ protected String from = null;
+
+ protected String to = null;
+
+ public Mapper( Project p )
+ {
+ setProject( p );
+ }
+
+ /**
+ * Set the class name of the FileNameMapper to use.
+ *
+ * @param classname The new Classname value
+ */
+ public void setClassname( String classname )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ this.classname = classname;
+ }
+
+ /**
+ * Set the classpath to load the FileNameMapper through (attribute).
+ *
+ * @param classpath The new Classpath value
+ */
+ public void setClasspath( Path classpath )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( this.classpath == null )
+ {
+ this.classpath = classpath;
+ }
+ else
+ {
+ this.classpath.append( classpath );
+ }
+ }
+
+ /**
+ * Set the classpath to load the FileNameMapper through via reference
+ * (attribute).
+ *
+ * @param r The new ClasspathRef value
+ */
+ public void setClasspathRef( Reference r )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ createClasspath().setRefid( r );
+ }
+
+ /**
+ * Set the argument to FileNameMapper.setFrom
+ *
+ * @param from The new From value
+ */
+ public void setFrom( String from )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ this.from = from;
+ }
+
+ /**
+ * Make this Mapper instance a reference to another Mapper.
+ *
+ * You must not set any other attribute if you make it a reference.
+ *
+ * @param r The new Refid value
+ * @exception BuildException Description of Exception
+ */
+ public void setRefid( Reference r )
+ throws BuildException
+ {
+ if( type != null || from != null || to != null )
+ {
+ throw tooManyAttributes();
+ }
+ super.setRefid( r );
+ }
+
+ /**
+ * Set the argument to FileNameMapper.setTo
+ *
+ * @param to The new To value
+ */
+ public void setTo( String to )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ this.to = to;
+ }
+
+ /**
+ * Set the type of FileNameMapper to use.
+ *
+ * @param type The new Type value
+ */
+ public void setType( MapperType type )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ this.type = type;
+ }
+
+ /**
+ * Returns a fully configured FileNameMapper implementation.
+ *
+ * @return The Implementation value
+ * @exception BuildException Description of Exception
+ */
+ public FileNameMapper getImplementation()
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ return getRef().getImplementation();
+ }
+
+ if( type == null && classname == null )
+ {
+ throw new BuildException( "one of the attributes type or classname is required" );
+ }
+
+ if( type != null && classname != null )
+ {
+ throw new BuildException( "must not specify both type and classname attribute" );
+ }
+
+ try
+ {
+ if( type != null )
+ {
+ classname = type.getImplementation();
+ }
+
+ Class c = null;
+ if( classpath == null )
+ {
+ c = Class.forName( classname );
+ }
+ else
+ {
+ AntClassLoader al = new AntClassLoader( getProject(),
+ classpath );
+ c = al.loadClass( classname );
+ AntClassLoader.initializeClass( c );
+ }
+
+ FileNameMapper m = ( FileNameMapper )c.newInstance();
+ m.setFrom( from );
+ m.setTo( to );
+ return m;
+ }
+ catch( BuildException be )
+ {
+ throw be;
+ }
+ catch( Throwable t )
+ {
+ throw new BuildException( t );
+ }
+ finally
+ {
+ if( type != null )
+ {
+ classname = null;
+ }
+ }
+ }
+
+ /**
+ * Set the classpath to load the FileNameMapper through (nested element).
+ *
+ * @return Description of the Returned Value
+ */
+ public Path createClasspath()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ if( this.classpath == null )
+ {
+ this.classpath = new Path( getProject() );
+ }
+ return this.classpath.createPath();
+ }
+
+ /**
+ * Performs the check for circular references and returns the referenced
+ * Mapper.
+ *
+ * @return The Ref value
+ */
+ protected Mapper getRef()
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, getProject() );
+ }
+
+ Object o = ref.getReferencedObject( getProject() );
+ if( !( o instanceof Mapper ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a mapper";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( Mapper )o;
+ }
+ }
+
+ /**
+ * Class as Argument to FileNameMapper.setType.
+ *
+ * @author RT
+ */
+ public static class MapperType extends EnumeratedAttribute
+ {
+ private Properties implementations;
+
+ public MapperType()
+ {
+ implementations = new Properties();
+ implementations.put( "identity",
+ "org.apache.tools.ant.util.IdentityMapper" );
+ implementations.put( "flatten",
+ "org.apache.tools.ant.util.FlatFileNameMapper" );
+ implementations.put( "glob",
+ "org.apache.tools.ant.util.GlobPatternMapper" );
+ implementations.put( "merge",
+ "org.apache.tools.ant.util.MergingMapper" );
+ implementations.put( "regexp",
+ "org.apache.tools.ant.util.RegexpPatternMapper" );
+ }
+
+ public String getImplementation()
+ {
+ return implementations.getProperty( getValue() );
+ }
+
+ public String[] getValues()
+ {
+ return new String[]{"identity", "flatten", "glob", "merge", "regexp"};
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Path.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Path.java
new file mode 100644
index 000000000..729dd96ae
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Path.java
@@ -0,0 +1,706 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Stack;
+import java.util.Vector;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.PathTokenizer;
+import org.apache.tools.ant.Project;
+
+
+/**
+ * This object represents a path as used by CLASSPATH or PATH environment
+ * variable.
+ *
+ *
+ * <sometask>
+ * <somepath>
+ * <pathelement location="/path/to/file.jar" />
+ *
+ * <pathelement
+ * path="/path/to/file2.jar:/path/to/class2;/path/to/class3" />
+ * <pathelement location="/path/to/file3.jar" />
+ *
+ * <pathelement location="/path/to/file4.jar" />
+ *
+ * </somepath>
+ * </sometask>
+ *
+ *
+ * The object implemention sometask must provide a method called
+ * createSomepath which returns an instance of Path.
+ * Nested path definitions are handled by the Path object and must be labeled
+ * pathelement.
+ *
+ * The path element takes a parameter path which will be parsed and
+ * split into single elements. It will usually be used to define a path from an
+ * environment variable.
+ *
+ * @author Thomas.Haas@softwired-inc.com
+ * @author Stefan Bodewig
+ */
+
+public class Path extends DataType implements Cloneable
+{
+
+ public static Path systemClasspath =
+ new Path( null, System.getProperty( "java.class.path" ) );
+
+ private Vector elements;
+
+ /**
+ * Invoked by IntrospectionHelper for setXXX(Path p) attribute
+ * setters.
+ *
+ * @param p Description of Parameter
+ * @param path Description of Parameter
+ */
+ public Path( Project p, String path )
+ {
+ this( p );
+ createPathElement().setPath( path );
+ }
+
+ public Path( Project project )
+ {
+ setProject( project );
+ elements = new Vector();
+ }
+
+ /**
+ * Returns its argument with all file separator characters replaced so that
+ * they match the local OS conventions.
+ *
+ * @param source Description of Parameter
+ * @return Description of the Returned Value
+ */
+ public static String translateFile( String source )
+ {
+ if( source == null )
+ return "";
+
+ final StringBuffer result = new StringBuffer( source );
+ for( int i = 0; i < result.length(); i++ )
+ {
+ translateFileSep( result, i );
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * Splits a PATH (with : or ; as separators) into its parts.
+ *
+ * @param project Description of Parameter
+ * @param source Description of Parameter
+ * @return Description of the Returned Value
+ */
+ public static String[] translatePath( Project project, String source )
+ {
+ final Vector result = new Vector();
+ if( source == null )
+ return new String[0];
+
+ PathTokenizer tok = new PathTokenizer( source );
+ StringBuffer element = new StringBuffer();
+ while( tok.hasMoreTokens() )
+ {
+ element.setLength( 0 );
+ String pathElement = tok.nextToken();
+ try
+ {
+ element.append( resolveFile( project, pathElement ) );
+ }
+ catch( BuildException e )
+ {
+ project.log( "Dropping path element " + pathElement + " as it is not valid relative to the project",
+ Project.MSG_VERBOSE );
+ }
+ for( int i = 0; i < element.length(); i++ )
+ {
+ translateFileSep( element, i );
+ }
+ result.addElement( element.toString() );
+ }
+ String[] res = new String[result.size()];
+ result.copyInto( res );
+ return res;
+ }
+
+ /**
+ * Translates all occurrences of / or \ to correct separator of the current
+ * platform and returns whether it had to do any replacements.
+ *
+ * @param buffer Description of Parameter
+ * @param pos Description of Parameter
+ * @return Description of the Returned Value
+ */
+ protected static boolean translateFileSep( StringBuffer buffer, int pos )
+ {
+ if( buffer.charAt( pos ) == '/' || buffer.charAt( pos ) == '\\' )
+ {
+ buffer.setCharAt( pos, File.separatorChar );
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds a String to the Vector if it isn't already included.
+ *
+ * @param v The feature to be added to the UnlessPresent attribute
+ * @param s The feature to be added to the UnlessPresent attribute
+ */
+ private static void addUnlessPresent( Vector v, String s )
+ {
+ if( v.indexOf( s ) == -1 )
+ {
+ v.addElement( s );
+ }
+ }
+
+ /**
+ * Resolve a filename with Project's help - if we know one that is.
+ *
+ * Assume the filename is absolute if project is null.
+ *
+ * @param project Description of Parameter
+ * @param relativeName Description of Parameter
+ * @return Description of the Returned Value
+ */
+ private static String resolveFile( Project project, String relativeName )
+ {
+ if( project != null )
+ {
+ File f = project.resolveFile( relativeName );
+ return f.getAbsolutePath();
+ }
+ return relativeName;
+ }
+
+ /**
+ * Adds a element definition to the path.
+ *
+ * @param location the location of the element to add (must not be null
+ * nor empty.
+ * @exception BuildException Description of Exception
+ */
+ public void setLocation( File location )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ createPathElement().setLocation( location );
+ }
+
+
+ /**
+ * Parses a path definition and creates single PathElements.
+ *
+ * @param path the path definition.
+ * @exception BuildException Description of Exception
+ */
+ public void setPath( String path )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ createPathElement().setPath( path );
+ }
+
+ /**
+ * Makes this instance in effect a reference to another Path instance.
+ *
+ * You must not set another attribute or nest elements inside this element
+ * if you make it a reference.
+ *
+ * @param r The new Refid value
+ * @exception BuildException Description of Exception
+ */
+ public void setRefid( Reference r )
+ throws BuildException
+ {
+ if( !elements.isEmpty() )
+ {
+ throw tooManyAttributes();
+ }
+ elements.addElement( r );
+ super.setRefid( r );
+ }
+
+ /**
+ * Adds the components on the given path which exist to this Path.
+ * Components that don't exist, aren't added.
+ *
+ * @param source - source path whose components are examined for existence
+ */
+ public void addExisting( Path source )
+ {
+ String[] list = source.list();
+ for( int i = 0; i < list.length; i++ )
+ {
+ File f = null;
+ if( getProject() != null )
+ {
+ f = getProject().resolveFile( list[i] );
+ }
+ else
+ {
+ f = new File( list[i] );
+ }
+
+ if( f.exists() )
+ {
+ setLocation( f );
+ }
+ }
+ }
+
+ /**
+ * Emulation of extdirs feature in java >= 1.2. This method adds all files
+ * in the given directories (but not in sub-directories!) to the classpath,
+ * so that you don't have to specify them all one by one.
+ *
+ * @param extdirs The feature to be added to the Extdirs attribute
+ */
+ public void addExtdirs( Path extdirs )
+ {
+ if( extdirs == null )
+ {
+ String extProp = System.getProperty( "java.ext.dirs" );
+ if( extProp != null )
+ {
+ extdirs = new Path( getProject(), extProp );
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ String[] dirs = extdirs.list();
+ for( int i = 0; i < dirs.length; i++ )
+ {
+ File dir = getProject().resolveFile( dirs[i] );
+ if( dir.exists() && dir.isDirectory() )
+ {
+ FileSet fs = new FileSet();
+ fs.setDir( dir );
+ fs.setIncludes( "*" );
+ addFileset( fs );
+ }
+ }
+ }
+
+ /**
+ * Adds a nested <fileset> element.
+ *
+ * @param fs The feature to be added to the Fileset attribute
+ * @exception BuildException Description of Exception
+ */
+ public void addFileset( FileSet fs )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ elements.addElement( fs );
+ checked = false;
+ }
+
+ /**
+ * Add the Java Runtime classes to this Path instance.
+ */
+ public void addJavaRuntime()
+ {
+ if( System.getProperty( "java.vendor" ).toLowerCase( Locale.US ).indexOf( "microsoft" ) >= 0 )
+ {
+ // Pull in *.zip from packages directory
+ FileSet msZipFiles = new FileSet();
+ msZipFiles.setDir( new File( System.getProperty( "java.home" ) + File.separator + "Packages" ) );
+ msZipFiles.setIncludes( "*.ZIP" );
+ addFileset( msZipFiles );
+ }
+ else if( "Kaffe".equals( System.getProperty( "java.vm.name" ) ) )
+ {
+ FileSet kaffeJarFiles = new FileSet();
+ kaffeJarFiles.setDir( new File( System.getProperty( "java.home" )
+ + File.separator + "share"
+ + File.separator + "kaffe" ) );
+
+ kaffeJarFiles.setIncludes( "*.jar" );
+ addFileset( kaffeJarFiles );
+ }
+ else if( Project.getJavaVersion() == Project.JAVA_1_1 )
+ {
+ addExisting( new Path( null,
+ System.getProperty( "java.home" )
+ + File.separator + "lib"
+ + File.separator
+ + "classes.zip" ) );
+ }
+ else
+ {
+ // JDK > 1.1 seems to set java.home to the JRE directory.
+ addExisting( new Path( null,
+ System.getProperty( "java.home" )
+ + File.separator + "lib"
+ + File.separator + "rt.jar" ) );
+ // Just keep the old version as well and let addExisting
+ // sort it out.
+ addExisting( new Path( null,
+ System.getProperty( "java.home" )
+ + File.separator + "jre"
+ + File.separator + "lib"
+ + File.separator + "rt.jar" ) );
+
+ // Added for MacOS X
+ addExisting( new Path( null,
+ System.getProperty( "java.home" )
+ + File.separator + ".."
+ + File.separator + "Classes"
+ + File.separator + "classes.jar" ) );
+ addExisting( new Path( null,
+ System.getProperty( "java.home" )
+ + File.separator + ".."
+ + File.separator + "Classes"
+ + File.separator + "ui.jar" ) );
+ }
+ }
+
+ /**
+ * Append the contents of the other Path instance to this.
+ *
+ * @param other Description of Parameter
+ */
+ public void append( Path other )
+ {
+ if( other == null )
+ return;
+ String[] l = other.list();
+ for( int i = 0; i < l.length; i++ )
+ {
+ if( elements.indexOf( l[i] ) == -1 )
+ {
+ elements.addElement( l[i] );
+ }
+ }
+ }
+
+ /**
+ * Return a Path that holds the same elements as this instance.
+ *
+ * @return Description of the Returned Value
+ */
+ public Object clone()
+ {
+ Path p = new Path( getProject() );
+ p.append( this );
+ return p;
+ }
+
+ /**
+ * Concatenates the system class path in the order specified by the
+ * ${build.sysclasspath} property - using "last" as default value.
+ *
+ * @return Description of the Returned Value
+ */
+ public Path concatSystemClasspath()
+ {
+ return concatSystemClasspath( "last" );
+ }
+
+ /**
+ * Concatenates the system class path in the order specified by the
+ * ${build.sysclasspath} property - using the supplied value if
+ * ${build.sysclasspath} has not been set.
+ *
+ * @param defValue Description of Parameter
+ * @return Description of the Returned Value
+ */
+ public Path concatSystemClasspath( String defValue )
+ {
+
+ Path result = new Path( getProject() );
+
+ String order = defValue;
+ if( getProject() != null )
+ {
+ String o = getProject().getProperty( "build.sysclasspath" );
+ if( o != null )
+ {
+ order = o;
+ }
+ }
+
+ if( order.equals( "only" ) )
+ {
+ // only: the developer knows what (s)he is doing
+ result.addExisting( Path.systemClasspath );
+
+ }
+ else if( order.equals( "first" ) )
+ {
+ // first: developer could use a little help
+ result.addExisting( Path.systemClasspath );
+ result.addExisting( this );
+
+ }
+ else if( order.equals( "ignore" ) )
+ {
+ // ignore: don't trust anyone
+ result.addExisting( this );
+
+ }
+ else
+ {
+ // last: don't trust the developer
+ if( !order.equals( "last" ) )
+ {
+ log( "invalid value for build.sysclasspath: " + order,
+ Project.MSG_WARN );
+ }
+
+ result.addExisting( this );
+ result.addExisting( Path.systemClasspath );
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates a nested <path> element.
+ *
+ * @return Description of the Returned Value
+ * @exception BuildException Description of Exception
+ */
+ public Path createPath()
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ Path p = new Path( getProject() );
+ elements.addElement( p );
+ checked = false;
+ return p;
+ }
+
+ /**
+ * Creates the nested <pathelement> element.
+ *
+ * @return Description of the Returned Value
+ * @exception BuildException Description of Exception
+ */
+ public PathElement createPathElement()
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ PathElement pe = new PathElement();
+ elements.addElement( pe );
+ return pe;
+ }
+
+ /**
+ * Returns all path elements defined by this and nested path objects.
+ *
+ * @return list of path elements.
+ */
+ public String[] list()
+ {
+ if( !checked )
+ {
+ // make sure we don't have a circular reference here
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, getProject() );
+ }
+
+ Vector result = new Vector( 2 * elements.size() );
+ for( int i = 0; i < elements.size(); i++ )
+ {
+ Object o = elements.elementAt( i );
+ if( o instanceof Reference )
+ {
+ Reference r = ( Reference )o;
+ o = r.getReferencedObject( getProject() );
+ // we only support references to paths right now
+ if( !( o instanceof Path ) )
+ {
+ String msg = r.getRefId() + " doesn\'t denote a path";
+ throw new BuildException( msg );
+ }
+ }
+
+ if( o instanceof String )
+ {
+ // obtained via append
+ addUnlessPresent( result, ( String )o );
+ }
+ else if( o instanceof PathElement )
+ {
+ String[] parts = ( ( PathElement )o ).getParts();
+ if( parts == null )
+ {
+ throw new BuildException( "You must either set location or path on " );
+ }
+ for( int j = 0; j < parts.length; j++ )
+ {
+ addUnlessPresent( result, parts[j] );
+ }
+ }
+ else if( o instanceof Path )
+ {
+ Path p = ( Path )o;
+ if( p.getProject() == null )
+ {
+ p.setProject( getProject() );
+ }
+ String[] parts = p.list();
+ for( int j = 0; j < parts.length; j++ )
+ {
+ addUnlessPresent( result, parts[j] );
+ }
+ }
+ else if( o instanceof FileSet )
+ {
+ FileSet fs = ( FileSet )o;
+ DirectoryScanner ds = fs.getDirectoryScanner( getProject() );
+ String[] s = ds.getIncludedFiles();
+ File dir = fs.getDir( getProject() );
+ for( int j = 0; j < s.length; j++ )
+ {
+ File f = new File( dir, s[j] );
+ String absolutePath = f.getAbsolutePath();
+ addUnlessPresent( result, translateFile( absolutePath ) );
+ }
+ }
+ }
+ String[] res = new String[result.size()];
+ result.copyInto( res );
+ return res;
+ }
+
+ /**
+ * How many parts does this Path instance consist of.
+ *
+ * @return Description of the Returned Value
+ */
+ public int size()
+ {
+ return list().length;
+ }
+
+
+ /**
+ * Returns a textual representation of the path, which can be used as
+ * CLASSPATH or PATH environment variable definition.
+ *
+ * @return a textual representation of the path.
+ */
+ public String toString()
+ {
+ final String[] list = list();
+
+ // empty path return empty string
+ if( list.length == 0 )
+ return "";
+
+ // path containing one or more elements
+ final StringBuffer result = new StringBuffer( list[0].toString() );
+ for( int i = 1; i < list.length; i++ )
+ {
+ result.append( File.pathSeparatorChar );
+ result.append( list[i] );
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * Overrides the version of DataType to recurse on all DataType child
+ * elements that may have been added.
+ *
+ * @param stk Description of Parameter
+ * @param p Description of Parameter
+ * @exception BuildException Description of Exception
+ */
+ protected void dieOnCircularReference( Stack stk, Project p )
+ throws BuildException
+ {
+
+ if( checked )
+ {
+ return;
+ }
+
+ Enumeration enum = elements.elements();
+ while( enum.hasMoreElements() )
+ {
+ Object o = enum.nextElement();
+ if( o instanceof Reference )
+ {
+ o = ( ( Reference )o ).getReferencedObject( p );
+ }
+
+ if( o instanceof DataType )
+ {
+ if( stk.contains( o ) )
+ {
+ throw circularReference();
+ }
+ else
+ {
+ stk.push( o );
+ ( ( DataType )o ).dieOnCircularReference( stk, p );
+ stk.pop();
+ }
+ }
+ }
+ checked = true;
+ }
+
+
+ /**
+ * Helper class, holds the nested <pathelement> values.
+ *
+ * @author RT
+ */
+ public class PathElement
+ {
+ private String[] parts;
+
+ public void setLocation( File loc )
+ {
+ parts = new String[]{translateFile( loc.getAbsolutePath() )};
+ }
+
+ public void setPath( String path )
+ {
+ parts = Path.translatePath( getProject(), path );
+ }
+
+ public String[] getParts()
+ {
+ return parts;
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/PatternSet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/PatternSet.java
new file mode 100644
index 000000000..b4200922f
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/PatternSet.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Stack;
+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.ProjectHelper;
+
+/**
+ * Named collection of include/exclude tags.
+ *
+ * Moved out of MatchingTask to make it a standalone object that could be
+ * referenced (by scripts for example).
+ *
+ * @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 class PatternSet extends DataType
+{
+ private Vector includeList = new Vector();
+ private Vector excludeList = new Vector();
+ private Vector includesFileList = new Vector();
+ private Vector excludesFileList = new Vector();
+
+ public PatternSet()
+ {
+ super();
+ }
+
+ /**
+ * 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 )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( excludes != null && excludes.length() > 0 )
+ {
+ StringTokenizer tok = new StringTokenizer( excludes, ", ", false );
+ while( tok.hasMoreTokens() )
+ {
+ createExclude().setName( tok.nextToken() );
+ }
+ }
+ }
+
+ /**
+ * Sets the name of the file containing the excludes patterns.
+ *
+ * @param excludesFile The file to fetch the exclude patterns from.
+ * @exception BuildException Description of Exception
+ */
+ public void setExcludesfile( File excludesFile )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ createExcludesFile().setName( excludesFile.getAbsolutePath() );
+ }
+
+ /**
+ * 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 )
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ if( includes != null && includes.length() > 0 )
+ {
+ StringTokenizer tok = new StringTokenizer( includes, ", ", false );
+ while( tok.hasMoreTokens() )
+ {
+ createInclude().setName( tok.nextToken() );
+ }
+ }
+ }
+
+ /**
+ * Sets the name of the file containing the includes patterns.
+ *
+ * @param includesFile The file to fetch the include patterns from.
+ * @exception BuildException Description of Exception
+ */
+ public void setIncludesfile( File includesFile )
+ throws BuildException
+ {
+ if( isReference() )
+ {
+ throw tooManyAttributes();
+ }
+ createIncludesFile().setName( includesFile.getAbsolutePath() );
+ }
+
+ /**
+ * Makes this instance in effect a reference to another PatternSet instance.
+ *
+ *
+ * You must not set another attribute or nest elements inside this element
+ * if you make it a reference.
+ *
+ * @param r The new Refid value
+ * @exception BuildException Description of Exception
+ */
+ public void setRefid( Reference r )
+ throws BuildException
+ {
+ if( !includeList.isEmpty() || !excludeList.isEmpty() )
+ {
+ throw tooManyAttributes();
+ }
+ super.setRefid( r );
+ }
+
+ /**
+ * Returns the filtered include patterns.
+ *
+ * @param p Description of Parameter
+ * @return The ExcludePatterns value
+ */
+ public String[] getExcludePatterns( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getExcludePatterns( p );
+ }
+ else
+ {
+ readFiles( p );
+ return makeArray( excludeList, p );
+ }
+ }
+
+ /**
+ * Returns the filtered include patterns.
+ *
+ * @param p Description of Parameter
+ * @return The IncludePatterns value
+ */
+ public String[] getIncludePatterns( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getIncludePatterns( p );
+ }
+ else
+ {
+ readFiles( p );
+ return makeArray( includeList, p );
+ }
+ }
+
+ /**
+ * Adds the patterns of the other instance to this set.
+ *
+ * @param other Description of Parameter
+ * @param p Description of Parameter
+ */
+ public void append( PatternSet other, Project p )
+ {
+ if( isReference() )
+ {
+ throw new BuildException( "Cannot append to a reference" );
+ }
+
+ String[] incl = other.getIncludePatterns( p );
+ if( incl != null )
+ {
+ for( int i = 0; i < incl.length; i++ )
+ {
+ createInclude().setName( incl[i] );
+ }
+ }
+
+ String[] excl = other.getExcludePatterns( p );
+ if( excl != null )
+ {
+ for( int i = 0; i < excl.length; i++ )
+ {
+ createExclude().setName( excl[i] );
+ }
+ }
+ }
+
+ /**
+ * add a name entry on the exclude list
+ *
+ * @return Description of the Returned Value
+ */
+ public NameEntry createExclude()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return addPatternToList( excludeList );
+ }
+
+ /**
+ * add a name entry on the exclude files list
+ *
+ * @return Description of the Returned Value
+ */
+ public NameEntry createExcludesFile()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return addPatternToList( excludesFileList );
+ }
+
+ /**
+ * add a name entry on the include list
+ *
+ * @return Description of the Returned Value
+ */
+ public NameEntry createInclude()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return addPatternToList( includeList );
+ }
+
+ /**
+ * add a name entry on the include files list
+ *
+ * @return Description of the Returned Value
+ */
+ public NameEntry createIncludesFile()
+ {
+ if( isReference() )
+ {
+ throw noChildrenAllowed();
+ }
+ return addPatternToList( includesFileList );
+ }
+
+ public String toString()
+ {
+ return "patternSet{ includes: " + includeList +
+ " excludes: " + excludeList + " }";
+ }
+
+ /**
+ * helper for FileSet.
+ *
+ * @return Description of the Returned Value
+ */
+ boolean hasPatterns()
+ {
+ return includesFileList.size() > 0 || excludesFileList.size() > 0
+ || includeList.size() > 0 || excludeList.size() > 0;
+ }
+
+ /**
+ * Performs the check for circular references and returns the referenced
+ * PatternSet.
+ *
+ * @param p Description of Parameter
+ * @return The Ref value
+ */
+ private PatternSet getRef( Project p )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, p );
+ }
+
+ Object o = ref.getReferencedObject( p );
+ if( !( o instanceof PatternSet ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a patternset";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( PatternSet )o;
+ }
+ }
+
+ /**
+ * add a name entry to the given list
+ *
+ * @param list The feature to be added to the PatternToList attribute
+ * @return Description of the Returned Value
+ */
+ private NameEntry addPatternToList( Vector list )
+ {
+ NameEntry result = new NameEntry();
+ list.addElement( result );
+ return result;
+ }
+
+ /**
+ * Convert a vector of NameEntry elements into an array of Strings.
+ *
+ * @param list Description of Parameter
+ * @param p Description of Parameter
+ * @return Description of the Returned Value
+ */
+ private String[] makeArray( Vector list, Project p )
+ {
+ if( list.size() == 0 )
+ return null;
+
+ Vector tmpNames = new Vector();
+ for( Enumeration e = list.elements(); e.hasMoreElements(); )
+ {
+ NameEntry ne = ( NameEntry )e.nextElement();
+ String pattern = ne.evalName( p );
+ if( pattern != null && pattern.length() > 0 )
+ {
+ tmpNames.addElement( pattern );
+ }
+ }
+
+ String result[] = new String[tmpNames.size()];
+ tmpNames.copyInto( result );
+ return result;
+ }
+
+ /**
+ * Read includesfile ot excludesfile if not already done so.
+ *
+ * @param p Description of Parameter
+ */
+ private void readFiles( Project p )
+ {
+ if( includesFileList.size() > 0 )
+ {
+ Enumeration e = includesFileList.elements();
+ while( e.hasMoreElements() )
+ {
+ NameEntry ne = ( NameEntry )e.nextElement();
+ String fileName = ne.evalName( p );
+ if( fileName != null )
+ {
+ File inclFile = p.resolveFile( fileName );
+ if( !inclFile.exists() )
+ throw new BuildException( "Includesfile "
+ + inclFile.getAbsolutePath()
+ + " not found." );
+ readPatterns( inclFile, includeList, p );
+ }
+ }
+ includesFileList.removeAllElements();
+ }
+
+ if( excludesFileList.size() > 0 )
+ {
+ Enumeration e = excludesFileList.elements();
+ while( e.hasMoreElements() )
+ {
+ NameEntry ne = ( NameEntry )e.nextElement();
+ String fileName = ne.evalName( p );
+ if( fileName != null )
+ {
+ File exclFile = p.resolveFile( fileName );
+ if( !exclFile.exists() )
+ throw new BuildException( "Excludesfile "
+ + exclFile.getAbsolutePath()
+ + " not found." );
+ readPatterns( exclFile, excludeList, p );
+ }
+ }
+ excludesFileList.removeAllElements();
+ }
+ }
+
+ /**
+ * Reads path matching patterns from a file and adds them to the includes or
+ * excludes list (as appropriate).
+ *
+ * @param patternfile Description of Parameter
+ * @param patternlist Description of Parameter
+ * @param p Description of Parameter
+ * @exception BuildException Description of Exception
+ */
+ private void readPatterns( File patternfile, Vector patternlist, Project p )
+ throws BuildException
+ {
+
+ BufferedReader patternReader = null;
+ try
+ {
+ // Get a FileReader
+ patternReader =
+ new BufferedReader( new FileReader( patternfile ) );
+
+ // Create one NameEntry in the appropriate pattern list for each
+ // line in the file.
+ String line = patternReader.readLine();
+ while( line != null )
+ {
+ if( line.length() > 0 )
+ {
+ line = p.replaceProperties( line );
+ addPatternToList( patternlist ).setName( line );
+ }
+ line = patternReader.readLine();
+ }
+ }
+ catch( IOException ioe )
+ {
+ String msg = "An error occured while reading from pattern file: "
+ + patternfile;
+ throw new BuildException( msg, ioe );
+ }
+ finally
+ {
+ if( null != patternReader )
+ {
+ try
+ {
+ patternReader.close();
+ }
+ catch( IOException ioe )
+ {
+ //Ignore exception
+ }
+ }
+ }
+ }
+
+ /**
+ * inner class to hold a name on list. "If" and "Unless" attributes may be
+ * used to invalidate the entry based on the existence of a property
+ * (typically set thru the use of the Available task).
+ *
+ * @author RT
+ */
+ public class NameEntry
+ {
+ private String ifCond;
+ private String name;
+ private String unlessCond;
+
+ public void setIf( String cond )
+ {
+ ifCond = cond;
+ }
+
+ public void setName( String name )
+ {
+ this.name = name;
+ }
+
+ public void setUnless( String cond )
+ {
+ unlessCond = cond;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public String evalName( Project p )
+ {
+ return valid( p ) ? name : null;
+ }
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer( name );
+ if( ( ifCond != null ) || ( unlessCond != null ) )
+ {
+ buf.append( ":" );
+ String connector = "";
+
+ if( ifCond != null )
+ {
+ buf.append( "if->" );
+ buf.append( ifCond );
+ connector = ";";
+ }
+ if( unlessCond != null )
+ {
+ buf.append( connector );
+ buf.append( "unless->" );
+ buf.append( unlessCond );
+ }
+ }
+
+ return buf.toString();
+ }
+
+ private boolean valid( Project p )
+ {
+ if( ifCond != null && p.getProperty( ifCond ) == null )
+ {
+ return false;
+ }
+ else if( unlessCond != null && p.getProperty( unlessCond ) != null )
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Reference.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Reference.java
new file mode 100644
index 000000000..11cb33915
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Reference.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.types;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * Class to hold a reference to another object in the project.
+ *
+ * @author Stefan Bodewig
+ */
+public class Reference
+{
+
+ private String refid;
+
+ public Reference()
+ {
+ super();
+ }
+
+ public Reference( String id )
+ {
+ this();
+ setRefId( id );
+ }
+
+ public void setRefId( String id )
+ {
+ refid = id;
+ }
+
+ public String getRefId()
+ {
+ return refid;
+ }
+
+ public Object getReferencedObject( Project project )
+ throws BuildException
+ {
+ if( refid == null )
+ {
+ throw new BuildException( "No reference specified" );
+ }
+
+ Object o = project.getReference( refid );
+ if( o == null )
+ {
+ throw new BuildException( "Reference " + refid + " not found." );
+ }
+ return o;
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/RegularExpression.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/RegularExpression.java
new file mode 100644
index 000000000..6f0d31451
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/RegularExpression.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.types;
+import java.util.Stack;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.util.regexp.Regexp;
+import org.apache.tools.ant.util.regexp.RegexpFactory;
+
+/**
+ * A regular expression datatype. Keeps an instance of the compiled expression
+ * for speed purposes. This compiled expression is lazily evaluated (it is
+ * compiled the first time it is needed). The syntax is the dependent on which
+ * regular expression type you are using. The system property
+ * "ant.regexp.regexpimpl" will be the classname of the implementation that will
+ * be used.
+ * For jdk <= 1.3, there are two available implementations:
+ * org.apache.tools.ant.util.regexp.JakartaOroRegexp (the default)
+ * Based on the jakarta-oro package
+ *
+ * org.apache.tools.ant.util.regexp.JakartaRegexpRegexp
+ * Based on the jakarta-regexp package
+ *
+ * For jdk >= 1.4 an additional implementation is available:
+ * org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp
+ * Based on the jdk 1.4 built in regular expression package.
+ *
+ * <regularexpression [ [id="id"] pattern="expression" | refid="id" ]
+ * />
+ *
+ *
+ * @author Matthew Inger
+ * mattinger@mindless.com
+ * @see org.apache.oro.regex.Perl5Compiler
+ * @see org.apache.regexp.RE
+ * @see java.util.regex.Pattern
+ * @see org.apache.tools.ant.util.regexp.Regexp
+ */
+public class RegularExpression extends DataType
+{
+ public final static String DATA_TYPE_NAME = "regularexpression";
+
+ // The regular expression factory
+ private final static RegexpFactory factory = new RegexpFactory();
+
+ private Regexp regexp;
+
+ public RegularExpression()
+ {
+ this.regexp = factory.newRegexp();
+ }
+
+ public void setPattern( String pattern )
+ {
+ this.regexp.setPattern( pattern );
+ }
+
+ /**
+ * Gets the pattern string for this RegularExpression in the given project.
+ *
+ * @param p Description of Parameter
+ * @return The Pattern value
+ */
+ public String getPattern( Project p )
+ {
+ if( isReference() )
+ return getRef( p ).getPattern( p );
+
+ return regexp.getPattern();
+ }
+
+ /**
+ * Get the RegularExpression this reference refers to in the given project.
+ * Check for circular references too
+ *
+ * @param p Description of Parameter
+ * @return The Ref value
+ */
+ public RegularExpression getRef( Project p )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, p );
+ }
+
+ Object o = ref.getReferencedObject( p );
+ if( !( o instanceof RegularExpression ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a regularexpression";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( RegularExpression )o;
+ }
+ }
+
+ public Regexp getRegexp( Project p )
+ {
+ if( isReference() )
+ return getRef( p ).getRegexp( p );
+ return this.regexp;
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Substitution.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Substitution.java
new file mode 100644
index 000000000..c640c0466
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/Substitution.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.types;
+import java.util.Stack;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.types.DataType;
+
+/**
+ * A regular expression substitution datatype. It is an expression that is meant
+ * to replace a regular expression.
+ * <substitition [ [id="id"] expression="expression" | refid="id" ]
+ * />
+ *
+ *
+ * @author Matthew Inger
+ * mattinger@mindless.com
+ * @see org.apache.oro.text.regex.Perl5Substitition
+ */
+public class Substitution extends DataType
+{
+ public final static String DATA_TYPE_NAME = "substitition";
+
+ private String expression;
+
+ public Substitution()
+ {
+ this.expression = null;
+ }
+
+ public void setExpression( String expression )
+ {
+ this.expression = expression;
+ }
+
+ /**
+ * Gets the pattern string for this RegularExpression in the given project.
+ *
+ * @param p Description of Parameter
+ * @return The Expression value
+ */
+ public String getExpression( Project p )
+ {
+ if( isReference() )
+ return getRef( p ).getExpression( p );
+
+ return expression;
+ }
+
+ /**
+ * Get the RegularExpression this reference refers to in the given project.
+ * Check for circular references too
+ *
+ * @param p Description of Parameter
+ * @return The Ref value
+ */
+ public Substitution getRef( Project p )
+ {
+ if( !checked )
+ {
+ Stack stk = new Stack();
+ stk.push( this );
+ dieOnCircularReference( stk, p );
+ }
+
+ Object o = ref.getReferencedObject( p );
+ if( !( o instanceof Substitution ) )
+ {
+ String msg = ref.getRefId() + " doesn\'t denote a substitution";
+ throw new BuildException( msg );
+ }
+ else
+ {
+ return ( Substitution )o;
+ }
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/ZipFileSet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/ZipFileSet.java
new file mode 100644
index 000000000..c98d28ac7
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/ZipFileSet.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types;
+import java.io.File;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;
+
+/**
+ * A ZipFileSet is a FileSet with extra attributes useful in the context of
+ * Zip/Jar tasks. A ZipFileSet extends FileSets with the ability to extract a
+ * subset of the entries of a Zip file for inclusion in another Zip file. It
+ * also includes a prefix attribute which is prepended to each entry in the
+ * output Zip file. At present, ZipFileSets are not surfaced in the public API.
+ * FileSets nested in a Zip task are instantiated as ZipFileSets, and their
+ * attributes are only recognized in the context of the the Zip task. It is not
+ * possible to define a ZipFileSet outside of the Zip task and refer to it via a
+ * refid. However a standard FileSet may be included by reference in the Zip
+ * task, and attributes in the refering ZipFileSet can augment FileSet
+ * definition.
+ *
+ * @author Don Ferguson don@bea.com
+ */
+public class ZipFileSet extends FileSet
+{
+
+ private File srcFile = null;
+ private String prefix = "";
+ private String fullpath = "";
+ private boolean hasDir = false;
+
+ /**
+ * Set the directory for the fileset. Prevents both "dir" and "src" from
+ * being specified.
+ *
+ * @param dir The new Dir value
+ * @exception BuildException Description of Exception
+ */
+ public void setDir( File dir )
+ throws BuildException
+ {
+ if( srcFile != null )
+ {
+ throw new BuildException( "Cannot set both dir and src attributes" );
+ }
+ else
+ {
+ super.setDir( dir );
+ hasDir = true;
+ }
+ }
+
+ /**
+ * Set the full pathname of the single entry in this fileset.
+ *
+ * @param fullpath The new Fullpath value
+ */
+ public void setFullpath( String fullpath )
+ {
+ this.fullpath = fullpath;
+ }
+
+ /**
+ * Prepend this prefix to the path for each zip entry. Does not perform
+ * reference test; the referenced file set can be augmented with a prefix.
+ *
+ * @param prefix The prefix to prepend to entries in the zip file.
+ */
+ public void setPrefix( String prefix )
+ {
+ this.prefix = prefix;
+ }
+
+ /**
+ * Set the source Zip file for the zipfileset. Prevents both "dir" and "src"
+ * from being specified.
+ *
+ * @param srcFile The zip file from which to extract entries.
+ */
+ public void setSrc( File srcFile )
+ {
+ if( hasDir )
+ {
+ throw new BuildException( "Cannot set both dir and src attributes" );
+ }
+ this.srcFile = srcFile;
+ }
+
+ /**
+ * Return the DirectoryScanner associated with this FileSet. If the
+ * ZipFileSet defines a source Zip file, then a ZipScanner is returned
+ * instead.
+ *
+ * @param p Description of Parameter
+ * @return The DirectoryScanner value
+ */
+ public DirectoryScanner getDirectoryScanner( Project p )
+ {
+ if( isReference() )
+ {
+ return getRef( p ).getDirectoryScanner( p );
+ }
+ if( srcFile != null )
+ {
+ ZipScanner zs = new ZipScanner();
+ zs.setSrc( srcFile );
+ super.setDir( p.getBaseDir() );
+ setupDirectoryScanner( zs, p );
+ zs.init();
+ return zs;
+ }
+ else
+ {
+ return super.getDirectoryScanner( p );
+ }
+ }
+
+ /**
+ * Return the full pathname of the single entry in this fileset.
+ *
+ * @return The Fullpath value
+ */
+ public String getFullpath()
+ {
+ return fullpath;
+ }
+
+ /**
+ * Return the prefix prepended to entries in the zip file.
+ *
+ * @return The Prefix value
+ */
+ public String getPrefix()
+ {
+ return prefix;
+ }
+
+ /**
+ * Get the zip file from which entries will be extracted. References are not
+ * followed, since it is not possible to have a reference to a ZipFileSet,
+ * only to a FileSet.
+ *
+ * @return The Src value
+ */
+ public File getSrc()
+ {
+ return srcFile;
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/ZipScanner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/ZipScanner.java
new file mode 100644
index 000000000..4ed9fec95
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/ZipScanner.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.types;
+import java.io.File;
+import org.apache.tools.ant.DirectoryScanner;
+
+/**
+ * ZipScanner accesses the pattern matching algorithm in DirectoryScanner, which
+ * are protected methods that can only be accessed by subclassing. This
+ * implementation of FileScanner defines getIncludedFiles to return only the Zip
+ * File which is being scanned, not the matching Zip entries. Arguably, it
+ * should return the matching entries, however this would complicate existing
+ * code which assumes that FileScanners return a set of file system files that
+ * can be accessed directly.
+ *
+ * @author Don Ferguson don@bea.com
+ */
+public class ZipScanner extends DirectoryScanner
+{
+
+ /**
+ * The zip file which should be scanned.
+ */
+ protected File srcFile;
+
+ /**
+ * Sets the srcFile for scanning. This is the jar or zip file that is
+ * scanned for matching entries.
+ *
+ * @param srcFile the (non-null) zip file name for scanning
+ */
+ public void setSrc( File srcFile )
+ {
+ this.srcFile = srcFile;
+ }
+
+ /**
+ * Returns an empty list of directories to create.
+ *
+ * @return The IncludedDirectories value
+ */
+ public String[] getIncludedDirectories()
+ {
+ return new String[0];
+ }
+
+ /**
+ * Returns the zip file itself, not the matching entries within the zip
+ * file. This keeps the uptodate test in the Zip task simple; otherwise we'd
+ * need to treat zip filesets specially.
+ *
+ * @return the source file from which entries will be extracted.
+ */
+ public String[] getIncludedFiles()
+ {
+ String[] result = new String[1];
+ result[0] = srcFile.getAbsolutePath();
+ return result;
+ }
+
+ /**
+ * Initialize DirectoryScanner data structures.
+ */
+ public void init()
+ {
+ 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];
+ }
+ }
+
+ /**
+ * Matches a jar entry against the includes/excludes list, normalizing the
+ * path separator.
+ *
+ * @param path the (non-null) path name to test for inclusion
+ * @return true if the path should be included false
+ * otherwise.
+ */
+ public boolean match( String path )
+ {
+ String vpath = path.replace( '/', File.separatorChar ).
+ replace( '\\', File.separatorChar );
+ return isIncluded( vpath ) && !isExcluded( vpath );
+ }
+
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/defaults.properties b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/defaults.properties
new file mode 100644
index 000000000..eab5c9a8d
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/defaults.properties
@@ -0,0 +1,10 @@
+path=org.apache.tools.ant.types.Path
+fileset=org.apache.tools.ant.types.FileSet
+filelist=org.apache.tools.ant.types.FileList
+patternset=org.apache.tools.ant.types.PatternSet
+mapper=org.apache.tools.ant.types.Mapper
+filterset=org.apache.tools.ant.types.FilterSet
+description=org.apache.tools.ant.types.Description
+classfileset=org.apache.tools.ant.types.optional.depend.ClassfileSet
+substitution=org.apache.tools.ant.types.Substitution
+regularexpression=org.apache.tools.ant.types.RegularExpression
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/optional/depend/ClassfileSet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/optional/depend/ClassfileSet.java
new file mode 100644
index 000000000..287d945ac
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/optional/depend/ClassfileSet.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.types.optional.depend;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+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.util.depend.Dependencies;
+
+/**
+ * A DepSet is a FileSet, that enlists all classes that depend on a certain
+ * class. A DependSet extends FileSets and uses another FileSet as input. The
+ * nested FileSet attribute provides the domain, that is used for searching for
+ * dependent classes
+ *
+ * @author Holger Engels
+ */
+public class ClassfileSet extends FileSet
+{
+ private List rootClasses = new ArrayList();
+
+ protected ClassfileSet( ClassfileSet s )
+ {
+ super( s );
+ rootClasses = s.rootClasses;
+ }
+
+ public void setRootClass( String rootClass )
+ throws BuildException
+ {
+ rootClasses.add( rootClass );
+ }
+
+ /**
+ * Return the DirectoryScanner associated with this FileSet.
+ *
+ * @param p Description of Parameter
+ * @return The DirectoryScanner value
+ */
+ public DirectoryScanner getDirectoryScanner( Project p )
+ {
+ DependScanner scanner = new DependScanner();
+ scanner.setBasedir( getDir( p ) );
+ scanner.setRootClasses( rootClasses );
+ scanner.scan();
+ return scanner;
+ }
+
+ public void addConfiguredRoot( ClassRoot root )
+ {
+ rootClasses.add( root.getClassname() );
+ }
+
+ public Object clone()
+ {
+ if( isReference() )
+ {
+ return new ClassfileSet( ( ClassfileSet )getRef( getProject() ) );
+ }
+ else
+ {
+ return new ClassfileSet( this );
+ }
+ }
+
+ public static class ClassRoot
+ {
+ private String rootClass;
+
+ public void setClassname( String name )
+ {
+ this.rootClass = name;
+ }
+
+ public String getClassname()
+ {
+ return rootClass;
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/types/optional/depend/DependScanner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/optional/depend/DependScanner.java
new file mode 100644
index 000000000..4144487dc
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/types/optional/depend/DependScanner.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE file.
+ */
+package org.apache.tools.ant.types.optional.depend;
+import java.io.*;
+import java.util.*;
+import org.apache.bcel.*;
+import org.apache.bcel.classfile.*;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.util.depend.Dependencies;
+import org.apache.tools.ant.util.depend.Filter;
+
+/**
+ * An interface used to describe the actions required by any type of directory
+ * scanner.
+ *
+ * @author RT
+ */
+public class DependScanner extends DirectoryScanner
+{
+ List included = new LinkedList();
+ File baseClass;
+ File basedir;
+
+ private List rootClasses;
+
+ /**
+ * Sets the basedir for scanning. This is the directory that is scanned
+ * recursively.
+ *
+ * @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;
+ }
+
+ public void setCaseSensitive( boolean isCaseSensitive ) { }
+
+ public void setExcludes( String[] excludes ) { }
+
+ public void setIncludes( String[] includes ) { }
+
+ /**
+ * Sets the domain, where dependant classes are searched
+ *
+ * @param rootClasses The new RootClasses value
+ */
+ public void setRootClasses( List rootClasses )
+ {
+ this.rootClasses = rootClasses;
+ }
+
+ /**
+ * Gets the basedir that is used for scanning.
+ *
+ * @return the basedir that is used for scanning
+ */
+ public File getBasedir()
+ {
+ return basedir;
+ }
+
+ public String[] getExcludedDirectories()
+ {
+ return null;
+ }
+
+ public String[] getExcludedFiles()
+ {
+ return null;
+ }
+
+ public String[] getIncludedDirectories()
+ {
+ return new String[0];
+ }
+
+ /**
+ * Get the names of the class files, baseClass depends on
+ *
+ * @return the names of the files
+ */
+ public String[] getIncludedFiles()
+ {
+ int count = included.size();
+ String[] files = new String[count];
+ for( int i = 0; i < count; i++ )
+ {
+ files[i] = included.get( i ) + ".class";
+ //System.err.println(" " + files[i]);
+ }
+ return files;
+ }
+
+ public String[] getNotIncludedDirectories()
+ {
+ return null;
+ }
+
+ public String[] getNotIncludedFiles()
+ {
+ return null;
+ }
+
+ public void addDefaultExcludes() { }
+
+ /**
+ * Scans the base directory for files that baseClass depends on
+ *
+ */
+ public void scan()
+ {
+ Dependencies visitor = new Dependencies();
+
+ Set set = new TreeSet();
+
+ final String base;
+ try
+ {
+ base = basedir.getCanonicalPath() + File.separator;
+ }
+ catch( Exception e )
+ {
+ throw new IllegalArgumentException( e.getMessage() );
+ }
+
+ for( Iterator rootClassIterator = rootClasses.iterator(); rootClassIterator.hasNext(); )
+ {
+ Set newSet = new HashSet();
+ String start = ( String )rootClassIterator.next();
+ start = start.replace( '.', '/' );
+
+ newSet.add( start );
+ set.add( start );
+
+ do
+ {
+ Iterator 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 )
+ {
+ System.err.println( "exception: " + e.getMessage() );
+ }
+ }
+ 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 );
+ }
+
+ included.clear();
+ included.addAll( set );
+ }
+}
diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/DOMElementWriter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/DOMElementWriter.java
new file mode 100644
index 000000000..ca4d1b867
--- /dev/null
+++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/DOMElementWriter.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.util;
+import java.io.IOException;
+import java.io.Writer;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+/**
+ * Writes a DOM tree to a given Writer.
+ *
+ * Utility class used by {@link org.apache.tools.ant.XmlLogger XmlLogger} and
+ * org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter
+ * XMLJUnitResultFormatter}.
+ *
+ * @author The original author of XmlLogger
+ * @author Stefan Bodewig
+ * @author Stephane Bailliez
+ */
+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( "" );
+ out.write( child.getNodeName() );
+ String data = child.getNodeValue();
+ if( data != null && data.length() > 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( element.getTagName() );
+ 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