* Use multi-parent ClassLoaders for antlibs and extensions, so that each extension jar is loaded by a single ClassLoader in the hierarchy. Allows classes from extensions to be shared across dependent antlibs and extensions. * Changed contract of ClassLoaderManager.createClassLoader( File[] ), so that it creates a new ClassLoader each time it is called. * Changed ExtensionManager, so that it no longer extends PackageRepository. * Added a few test cases for DefaultClassLoaderManager. * Moved responsibility for checking myrmidon.home and building the various paths, from DefaultEmbeddor and DefaultExtensionManager to EmbeddedAnt. Use the platform path separator for the paths, rather than the | char. * Use EmbeddedAnt in AbstractProjectTest, rather than using an Embeddor directly. * AbstractComponentTest was not parameterising or initialising the test components. git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@272180 13f79535-47bb-0310-9956-ffa450edef68master
| @@ -67,6 +67,7 @@ Legal: | |||||
| <property name="test.dir" value="${build.dir}/test"/> | <property name="test.dir" value="${build.dir}/test"/> | ||||
| <property name="test.working.dir" value="${test.dir}/test"/> | <property name="test.working.dir" value="${test.dir}/test"/> | ||||
| <property name="test.classes" value="${test.dir}/classes"/> | <property name="test.classes" value="${test.dir}/classes"/> | ||||
| <property name="test.fork" value="true"/> | |||||
| <property name="constants.file" value="org/apache/myrmidon/Constants.java"/> | <property name="constants.file" value="org/apache/myrmidon/Constants.java"/> | ||||
| @@ -544,6 +545,9 @@ Legal: | |||||
| <!-- Compiles and runs the unit tests --> | <!-- Compiles and runs the unit tests --> | ||||
| <target name="run-tests" depends="dist-lite" if="junit.present"> | <target name="run-tests" depends="dist-lite" if="junit.present"> | ||||
| <property name="test.classloader.pkg" value="org/apache/myrmidon/components/classloader/test/libs"/> | |||||
| <!-- Compile the unit tests --> | <!-- Compile the unit tests --> | ||||
| <mkdir dir="${test.classes}"/> | <mkdir dir="${test.classes}"/> | ||||
| <javac srcdir="src/test" | <javac srcdir="src/test" | ||||
| @@ -558,6 +562,7 @@ Legal: | |||||
| <exclude name="**/SmbFileSystemTestCase.java" unless="jcifs.present"/> | <exclude name="**/SmbFileSystemTestCase.java" unless="jcifs.present"/> | ||||
| <exclude name="**/FtpFileSystemTestCase.java" unless="netcomp.present"/> | <exclude name="**/FtpFileSystemTestCase.java" unless="netcomp.present"/> | ||||
| <exclude name="${test.classloader.pkg}/**"/> | |||||
| </javac> | </javac> | ||||
| <!-- Prepare test files --> | <!-- Prepare test files --> | ||||
| @@ -602,6 +607,56 @@ Legal: | |||||
| <fileset dir="${test.classes}" includes="org/apache/myrmidon/interfaces/type/test/MyType1.class"/> | <fileset dir="${test.classes}" includes="org/apache/myrmidon/interfaces/type/test/MyType1.class"/> | ||||
| </jar> | </jar> | ||||
| <!-- Prepare the class loader manager tests --> | |||||
| <property name="test.classloader.dir" value="${test.working.dir}/${test.classloader.pkg}/.."/> | |||||
| <property name="test.classloader.classes" value="${test.dir}/classloader"/> | |||||
| <mkdir dir="${test.classloader.dir}"/> | |||||
| <mkdir dir="${test.classloader.dir}/ext"/> | |||||
| <mkdir dir="${test.classloader.classes}"/> | |||||
| <javac srcdir="src/test" | |||||
| destdir="${test.classloader.classes}" | |||||
| debug="${debug}" | |||||
| deprecation="${deprecation}"> | |||||
| <include name="${test.classloader.pkg}/**"/> | |||||
| </javac> | |||||
| <copy todir="${test.classloader.classes}"> | |||||
| <fileset dir="src/test"> | |||||
| <include name="${test.classloader.pkg}/**"/> | |||||
| <exclude name="**/*.java"/> | |||||
| </fileset> | |||||
| </copy> | |||||
| <jar jarfile="${test.classloader.dir}/common.jar"> | |||||
| <fileset dir="${test.classloader.classes}"> | |||||
| <include name="**/shared/**"/> | |||||
| </fileset> | |||||
| </jar> | |||||
| <jar jarfile="${test.classloader.dir}/no-dependencies.jar"> | |||||
| <fileset dir="${test.classloader.classes}"> | |||||
| <include name="**/shared/**"/> | |||||
| <include name="**/unshared/**"/> | |||||
| </fileset> | |||||
| </jar> | |||||
| <jar jarfile="${test.classloader.dir}/one-dependency.jar" | |||||
| manifest="src/test/${test.classloader.pkg}/one-dependency.mf"> | |||||
| <fileset dir="${test.classloader.classes}"> | |||||
| <include name="**/shared/**"/> | |||||
| <include name="**/unshared/**"/> | |||||
| </fileset> | |||||
| </jar> | |||||
| <copy file="${test.classloader.dir}/one-dependency.jar" | |||||
| tofile="${test.classloader.dir}/one-dependency-2.jar"/> | |||||
| <jar jarfile="${test.classloader.dir}/ext/simple-extension.jar" | |||||
| manifest="src/test/${test.classloader.pkg}/simple-extension.mf" > | |||||
| <fileset dir="${test.classloader.classes}"> | |||||
| <include name="**/shared/**"/> | |||||
| <include name="**/extn/**"/> | |||||
| </fileset> | |||||
| </jar> | |||||
| <jar jarfile="${test.classloader.dir}/ext/cycle-extension-1.jar" | |||||
| manifest="src/test/${test.classloader.pkg}/cycle-extension-1.mf" /> | |||||
| <jar jarfile="${test.classloader.dir}/ext/cycle-extension-2.jar" | |||||
| manifest="src/test/${test.classloader.pkg}/cycle-extension-2.mf" /> | |||||
| <!-- Prepare the project tests --> | <!-- Prepare the project tests --> | ||||
| <antlib-descriptor libName="unittests" | <antlib-descriptor libName="unittests" | ||||
| destdir="${gen.dir}" | destdir="${gen.dir}" | ||||
| @@ -616,7 +671,7 @@ Legal: | |||||
| <!-- Run all the tests --> | <!-- Run all the tests --> | ||||
| <junit printsummary="on" | <junit printsummary="on" | ||||
| fork="true" failureProperty="test.failed"> | |||||
| fork="${test.fork}" failureProperty="test.failed"> | |||||
| <formatter type="brief" usefile="false"/> | <formatter type="brief" usefile="false"/> | ||||
| <classpath> | <classpath> | ||||
| <fileset dir="${test.working.dir}/dist/bin/lib" includes="**/*.jar"/> | <fileset dir="${test.working.dir}/dist/bin/lib" includes="**/*.jar"/> | ||||
| @@ -837,23 +837,12 @@ public class MyrmidonSecurityManager | |||||
| <code><socket></code> | <code><socket></code> | ||||
| conditions to an antlib. Need to resolve how these will be passed a logger. | conditions to an antlib. Need to resolve how these will be passed a logger. | ||||
| </li> | </li> | ||||
| <li>Make the | |||||
| <code><uptodate></code> task a condition, and move to | |||||
| an antlib. | |||||
| </li> | |||||
| <li>Split up | |||||
| <code><is-set></code> condition into is-set and is-true conditions. | |||||
| </li> | |||||
| <li>Allow the | <li>Allow the | ||||
| <code><if></code> task to take any condition implementation. | <code><if></code> task to take any condition implementation. | ||||
| </li> | </li> | ||||
| <li>Add an else block to the | <li>Add an else block to the | ||||
| <code><if></code> task. | <code><if></code> task. | ||||
| </li> | </li> | ||||
| <li>Split the | |||||
| <code><available></code> condition into separate conditions | |||||
| that test for the availability of a class, or a resource. | |||||
| </li> | |||||
| <li>Move | <li>Move | ||||
| <code>crimson.jar</code> to | <code>crimson.jar</code> to | ||||
| <code>bin/lib</code> in the distribution, | <code>bin/lib</code> in the distribution, | ||||
| @@ -863,6 +852,19 @@ public class MyrmidonSecurityManager | |||||
| <li>Add a <code>--type</code> command-line option, to allow | <li>Add a <code>--type</code> command-line option, to allow | ||||
| the project builder to be manually selected. | the project builder to be manually selected. | ||||
| </li> | </li> | ||||
| <li>Change <code>ProjectBuilder</code> | |||||
| and <code>Embeddor</code> to throw something more | |||||
| specialised than Exception. | |||||
| </li> | |||||
| <li>Change <code>DefaultClassLoaderManager</code> to handle | |||||
| directories as part of a library classpath. | |||||
| </li> | |||||
| <li><code><condition></code> should set the property | |||||
| value to <code>false</code> when the condition is false.</li> | |||||
| <li>Split the <code><uptodate></code> condition into | |||||
| a condition that checks against a single target file, | |||||
| and one which checks using a destdir/mapper.</li> | |||||
| <li>Add a task to unset a property.</li> | |||||
| <li>Unit tests.</li> | <li>Unit tests.</li> | ||||
| </ul> | </ul> | ||||
| </blockquote> | </blockquote> | ||||
| @@ -8,19 +8,17 @@ | |||||
| package org.apache.myrmidon.components.classloader; | package org.apache.myrmidon.components.classloader; | ||||
| import java.io.File; | import java.io.File; | ||||
| import java.net.JarURLConnection; | |||||
| import java.net.MalformedURLException; | import java.net.MalformedURLException; | ||||
| import java.net.URL; | import java.net.URL; | ||||
| import java.net.URLClassLoader; | |||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||
| import java.util.Arrays; | |||||
| import java.util.HashMap; | import java.util.HashMap; | ||||
| import java.util.Map; | import java.util.Map; | ||||
| import java.util.List; | |||||
| import java.util.Set; | |||||
| import java.util.HashSet; | |||||
| import java.util.jar.Manifest; | import java.util.jar.Manifest; | ||||
| import java.util.jar.JarFile; | |||||
| import org.apache.avalon.excalibur.extension.Extension; | import org.apache.avalon.excalibur.extension.Extension; | ||||
| import org.apache.avalon.excalibur.extension.OptionalPackage; | import org.apache.avalon.excalibur.extension.OptionalPackage; | ||||
| import org.apache.avalon.excalibur.extension.PackageManager; | |||||
| import org.apache.avalon.excalibur.i18n.ResourceManager; | import org.apache.avalon.excalibur.i18n.ResourceManager; | ||||
| import org.apache.avalon.excalibur.i18n.Resources; | import org.apache.avalon.excalibur.i18n.Resources; | ||||
| import org.apache.avalon.framework.activity.Initializable; | import org.apache.avalon.framework.activity.Initializable; | ||||
| @@ -28,8 +26,8 @@ import org.apache.avalon.framework.logger.AbstractLogEnabled; | |||||
| import org.apache.avalon.framework.service.ServiceException; | import org.apache.avalon.framework.service.ServiceException; | ||||
| import org.apache.avalon.framework.service.ServiceManager; | import org.apache.avalon.framework.service.ServiceManager; | ||||
| import org.apache.avalon.framework.service.Serviceable; | import org.apache.avalon.framework.service.Serviceable; | ||||
| import org.apache.myrmidon.interfaces.classloader.ClassLoaderManager; | |||||
| import org.apache.myrmidon.interfaces.classloader.ClassLoaderException; | import org.apache.myrmidon.interfaces.classloader.ClassLoaderException; | ||||
| import org.apache.myrmidon.interfaces.classloader.ClassLoaderManager; | |||||
| import org.apache.myrmidon.interfaces.deployer.DeploymentException; | import org.apache.myrmidon.interfaces.deployer.DeploymentException; | ||||
| import org.apache.myrmidon.interfaces.extensions.ExtensionManager; | import org.apache.myrmidon.interfaces.extensions.ExtensionManager; | ||||
| import org.apache.tools.todo.types.PathUtil; | import org.apache.tools.todo.types.PathUtil; | ||||
| @@ -38,6 +36,7 @@ import org.apache.tools.todo.types.PathUtil; | |||||
| * A default implementation of a ClassLoader manager. | * A default implementation of a ClassLoader manager. | ||||
| * | * | ||||
| * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | ||||
| * @version $Revision$ $Date$ | |||||
| */ | */ | ||||
| public class DefaultClassLoaderManager | public class DefaultClassLoaderManager | ||||
| extends AbstractLogEnabled | extends AbstractLogEnabled | ||||
| @@ -47,13 +46,22 @@ public class DefaultClassLoaderManager | |||||
| ResourceManager.getPackageResources( DefaultClassLoaderManager.class ); | ResourceManager.getPackageResources( DefaultClassLoaderManager.class ); | ||||
| /** | /** | ||||
| * Map from File to the ClassLoader for that file. | |||||
| * Map from File/ArrayList to the ClassLoader for that file/files. | |||||
| */ | */ | ||||
| private final Map m_fileDeployers = new HashMap(); | |||||
| private final Map m_classLoaders = new HashMap(); | |||||
| private PackageManager m_packageManager; | |||||
| private ExtensionManager m_extensionManager; | |||||
| private ClassLoader m_commonClassLoader; | private ClassLoader m_commonClassLoader; | ||||
| public DefaultClassLoaderManager() | |||||
| { | |||||
| } | |||||
| public DefaultClassLoaderManager( final ClassLoader commonClassLoader ) | |||||
| { | |||||
| m_commonClassLoader = commonClassLoader; | |||||
| } | |||||
| public void initialize() throws Exception | public void initialize() throws Exception | ||||
| { | { | ||||
| if( null == m_commonClassLoader ) | if( null == m_commonClassLoader ) | ||||
| @@ -68,18 +76,7 @@ public class DefaultClassLoaderManager | |||||
| public void service( final ServiceManager serviceManager ) | public void service( final ServiceManager serviceManager ) | ||||
| throws ServiceException | throws ServiceException | ||||
| { | { | ||||
| final ExtensionManager extensionManager = | |||||
| (ExtensionManager)serviceManager.lookup( ExtensionManager.ROLE ); | |||||
| m_packageManager = new PackageManager( extensionManager ); | |||||
| } | |||||
| /** | |||||
| * Sets the ClassLoader to use as the parent for all classloaders | |||||
| * created by this ClassLoader manager. | |||||
| */ | |||||
| public void setCommonClassLoader( final ClassLoader classLoader ) | |||||
| { | |||||
| m_commonClassLoader = classLoader; | |||||
| m_extensionManager = (ExtensionManager)serviceManager.lookup( ExtensionManager.ROLE ); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -94,9 +91,27 @@ public class DefaultClassLoaderManager | |||||
| /** | /** | ||||
| * Creates a class loader for a Jar file. | * Creates a class loader for a Jar file. | ||||
| */ | */ | ||||
| public ClassLoader createClassLoader( final File file ) throws ClassLoaderException | |||||
| public ClassLoader getClassLoader( final File file ) throws ClassLoaderException | |||||
| { | { | ||||
| return createClassLoader( new File[] { file } ); | |||||
| try | |||||
| { | |||||
| final File canonFile = file.getCanonicalFile(); | |||||
| // Check for cached classloader, creating it if required | |||||
| ClassLoader loader = (ClassLoader)m_classLoaders.get( canonFile ); | |||||
| if( loader == null ) | |||||
| { | |||||
| checkFile( canonFile ); | |||||
| final OptionalPackage optionalPackage = toOptionalPackage( canonFile ); | |||||
| loader = buildClassLoader( optionalPackage, new HashSet() ); | |||||
| } | |||||
| return loader; | |||||
| } | |||||
| catch( final Exception e ) | |||||
| { | |||||
| final String message = REZ.getString( "create-classloader-for-file.error", file ); | |||||
| throw new ClassLoaderException( message, e ); | |||||
| } | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -106,113 +121,165 @@ public class DefaultClassLoaderManager | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| // Build a list of canonical file names | |||||
| final ArrayList canonFiles = new ArrayList( files.length ); | |||||
| for( int i = 0; i < files.length; i++ ) | |||||
| if( files == null || files.length == 0 ) | |||||
| { | { | ||||
| canonFiles.add( files[ i ].getCanonicalFile() ); | |||||
| return m_commonClassLoader; | |||||
| } | } | ||||
| // Locate cached classloader, creating it if necessary | |||||
| ClassLoader classLoader = (ClassLoader)m_fileDeployers.get( canonFiles ); | |||||
| if( classLoader == null ) | |||||
| // Build a list of optional packages for the files | |||||
| final OptionalPackage[] packages = new OptionalPackage[ files.length ]; | |||||
| for( int i = 0; i < files.length; i++ ) | |||||
| { | { | ||||
| classLoader = buildClassLoader( canonFiles ); | |||||
| m_fileDeployers.put( canonFiles, classLoader ); | |||||
| final File canonFile = files[ i ].getCanonicalFile(); | |||||
| checkFile( canonFile ); | |||||
| packages[ i ] = toOptionalPackage( canonFile ); | |||||
| } | } | ||||
| return classLoader; | |||||
| // Build the classloaders for the required extensions | |||||
| final ClassLoader[] parentClassLoaders = buildParentClassLoaders( packages, new HashSet() ); | |||||
| // Build the classloader | |||||
| final URL[] urls = buildClasspath( files ); | |||||
| return new MultiParentURLClassLoader( urls, parentClassLoaders ); | |||||
| } | } | ||||
| catch( final Exception e ) | catch( final Exception e ) | ||||
| { | { | ||||
| final String fileNames = PathUtil.formatPath( files ); | final String fileNames = PathUtil.formatPath( files ); | ||||
| final String message = REZ.getString( "create-classloader-for-file.error", fileNames ); | |||||
| final String message = REZ.getString( "create-classloader-for-files.error", fileNames ); | |||||
| throw new ClassLoaderException( message, e ); | throw new ClassLoaderException( message, e ); | ||||
| } | } | ||||
| } | } | ||||
| /** | /** | ||||
| * Builds the classloader for a set of files. | |||||
| * Builds the classloader for an optional package. | |||||
| */ | */ | ||||
| private ClassLoader buildClassLoader( final ArrayList files ) | |||||
| private ClassLoader buildClassLoader( final OptionalPackage pkg, | |||||
| final Set pending ) | |||||
| throws Exception | throws Exception | ||||
| { | { | ||||
| final ArrayList allFiles = new ArrayList( files ); | |||||
| final int count = files.size(); | |||||
| for( int i = 0; i < count; i++ ) | |||||
| final File jarFile = pkg.getFile(); | |||||
| // Check for cached classloader | |||||
| ClassLoader classLoader = (ClassLoader)m_classLoaders.get( jarFile ); | |||||
| if( classLoader != null ) | |||||
| { | { | ||||
| final File file = (File)files.get(i ); | |||||
| checkFile( file ); | |||||
| getOptionalPackagesFor( file, allFiles ); | |||||
| return classLoader; | |||||
| } | } | ||||
| final URL[] urls = buildClasspath( allFiles ); | |||||
| return new URLClassLoader( urls, m_commonClassLoader ); | |||||
| // Check for cyclic dependency | |||||
| if( pending.contains( jarFile ) ) | |||||
| { | |||||
| final String message = REZ.getString( "dependency-cycle.error", jarFile ); | |||||
| throw new Exception( message ); | |||||
| } | |||||
| pending.add( jarFile ); | |||||
| // Build the classloaders for the extensions required by this optional | |||||
| // package | |||||
| final ClassLoader[] parentClassLoaders = | |||||
| buildParentClassLoaders( new OptionalPackage[] { pkg }, pending ); | |||||
| // Create and cache the classloader | |||||
| final URL[] urls = { jarFile.toURL() }; | |||||
| classLoader = new MultiParentURLClassLoader( urls, parentClassLoaders ); | |||||
| m_classLoaders.put( jarFile, classLoader ); | |||||
| pending.remove( jarFile ); | |||||
| return classLoader; | |||||
| } | |||||
| /** | |||||
| * Builds the parent classloaders for a set of optional packages. That is, | |||||
| * the classloaders for all of the extensions required by the given set | |||||
| * of optional packages. | |||||
| */ | |||||
| private ClassLoader[] buildParentClassLoaders( final OptionalPackage[] packages, | |||||
| final Set pending ) | |||||
| throws Exception | |||||
| { | |||||
| final ArrayList classLoaders = new ArrayList(); | |||||
| // Include the common class loader | |||||
| classLoaders.add( m_commonClassLoader ); | |||||
| // Build the classloader for each optional package, filtering out duplicates | |||||
| for( int i = 0; i < packages.length; i++ ) | |||||
| { | |||||
| final OptionalPackage optionalPackage = packages[ i ]; | |||||
| // Locate the dependencies for this jar file | |||||
| final OptionalPackage[] requiredPackages = getOptionalPackagesFor( optionalPackage ); | |||||
| // Build the classloader for the package | |||||
| for( int j = 0; j < requiredPackages.length; j++ ) | |||||
| { | |||||
| final OptionalPackage requiredPackage = requiredPackages[j ]; | |||||
| final ClassLoader classLoader = buildClassLoader( requiredPackage, pending ); | |||||
| if( ! classLoaders.contains( classLoader ) ) | |||||
| { | |||||
| classLoaders.add( classLoader ); | |||||
| } | |||||
| } | |||||
| } | |||||
| return (ClassLoader[])classLoaders.toArray( new ClassLoader[classLoaders.size() ] ); | |||||
| } | } | ||||
| /** | /** | ||||
| * Assembles a set of files into a URL classpath. | * Assembles a set of files into a URL classpath. | ||||
| */ | */ | ||||
| private URL[] buildClasspath( final ArrayList files ) | |||||
| private URL[] buildClasspath( final File[] files ) | |||||
| throws MalformedURLException | throws MalformedURLException | ||||
| { | { | ||||
| final URL[] urls = new URL[ files.size() ]; | |||||
| final int count = files.size(); | |||||
| for( int i = 0; i < count; i++ ) | |||||
| final URL[] urls = new URL[ files.length ]; | |||||
| for( int i = 0; i < files.length; i++ ) | |||||
| { | { | ||||
| final File file = (File)files.get( i ); | |||||
| urls[ i ] = file.toURL(); | |||||
| urls[ i ] = files[i ].toURL(); | |||||
| } | } | ||||
| return urls; | return urls; | ||||
| } | } | ||||
| /** | /** | ||||
| * Retrieve the files for the optional packages required by | |||||
| * the specified typeLibrary jar. | |||||
| * Builds an OptionalPackage for a Jar file. | |||||
| * | * | ||||
| * @param jarFile the typeLibrary | |||||
| * @param packages used to return the files that need to be added to ClassLoader. | |||||
| * @param file the jar. | |||||
| */ | */ | ||||
| private void getOptionalPackagesFor( final File jarFile, final List packages ) | |||||
| private OptionalPackage toOptionalPackage( final File file ) | |||||
| throws Exception | throws Exception | ||||
| { | { | ||||
| final URL url = new URL( "jar:" + jarFile.getCanonicalFile().toURL() + "!/" ); | |||||
| final JarURLConnection connection = (JarURLConnection)url.openConnection(); | |||||
| final Manifest manifest = connection.getManifest(); | |||||
| final Extension[] available = Extension.getAvailable( manifest ); | |||||
| // Determine the extensions required by this file | |||||
| final JarFile jarFile = new JarFile( file ); | |||||
| final Manifest manifest = jarFile.getManifest(); | |||||
| final Extension[] required = Extension.getRequired( manifest ); | final Extension[] required = Extension.getRequired( manifest ); | ||||
| return new OptionalPackage( file, new Extension[0], required ); | |||||
| } | |||||
| if( getLogger().isDebugEnabled() ) | |||||
| { | |||||
| final String message1 = | |||||
| REZ.getString( "available-extensions.notice", Arrays.asList( available ) ); | |||||
| getLogger().debug( message1 ); | |||||
| final String message2 = | |||||
| REZ.getString( "required-extensions.notice", Arrays.asList( required ) ); | |||||
| getLogger().debug( message2 ); | |||||
| } | |||||
| final ArrayList dependencies = new ArrayList(); | |||||
| final ArrayList unsatisfied = new ArrayList(); | |||||
| m_packageManager.scanDependencies( required, | |||||
| available, | |||||
| dependencies, | |||||
| unsatisfied ); | |||||
| if( 0 != unsatisfied.size() ) | |||||
| /** | |||||
| * Locates the optional packages required by an optional package. | |||||
| */ | |||||
| private OptionalPackage[] getOptionalPackagesFor( final OptionalPackage pkg ) | |||||
| throws Exception | |||||
| { | |||||
| // Locate the optional packages that provide the required extesions | |||||
| final Extension[] required = pkg.getRequiredExtensions(); | |||||
| final ArrayList packages = new ArrayList(); | |||||
| for( int i = 0; i < required.length; i++ ) | |||||
| { | { | ||||
| final String message = | |||||
| REZ.getString( "unsatisfied.extensions.error", new Integer( unsatisfied.size() ) ); | |||||
| throw new Exception( message ); | |||||
| final Extension extension = required[i ]; | |||||
| final OptionalPackage optionalPackage = m_extensionManager.getOptionalPackage( extension ); | |||||
| if( optionalPackage == null ) | |||||
| { | |||||
| final String message = | |||||
| REZ.getString( "unsatisfied.extension.error", | |||||
| pkg.getFile(), | |||||
| extension.getExtensionName(), | |||||
| extension.getSpecificationVersion() ); | |||||
| throw new Exception( message ); | |||||
| } | |||||
| packages.add( optionalPackage ); | |||||
| } | } | ||||
| final int count = dependencies.size(); | |||||
| for( int i = 0; i < count; i++ ) | |||||
| { | |||||
| final OptionalPackage optionalPackage = (OptionalPackage)dependencies.get(i ); | |||||
| packages.add( optionalPackage.getFile() ); | |||||
| } | |||||
| return (OptionalPackage[])packages.toArray( new OptionalPackage[packages.size() ] ); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -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.txt file. | |||||
| */ | |||||
| package org.apache.myrmidon.components.classloader; | |||||
| import java.io.IOException; | |||||
| import java.net.URL; | |||||
| import java.net.URLClassLoader; | |||||
| import java.util.ArrayList; | |||||
| import java.util.Collections; | |||||
| import java.util.Enumeration; | |||||
| import java.util.List; | |||||
| import java.util.Set; | |||||
| import java.util.HashSet; | |||||
| /** | |||||
| * A URLClassLoader with more than one parent. | |||||
| * | |||||
| * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
| * @version $Revision$ $Date$ | |||||
| */ | |||||
| public class MultiParentURLClassLoader | |||||
| extends URLClassLoader | |||||
| { | |||||
| private final ClassLoader[] m_parents; | |||||
| /** | |||||
| * Constructs a new URLClassLoader for the given URLs. | |||||
| * | |||||
| * @param urls the URLs from which to load classes and resources | |||||
| * @param parents the parent class loaderer for delegation | |||||
| */ | |||||
| public MultiParentURLClassLoader( final URL[] urls, final ClassLoader[] parents ) | |||||
| { | |||||
| super( urls ); | |||||
| m_parents = parents; | |||||
| } | |||||
| /** | |||||
| * Finds a class. | |||||
| * | |||||
| * @param name the name of the class | |||||
| * @return the resulting class | |||||
| * @exception ClassNotFoundException if the class could not be found | |||||
| */ | |||||
| protected Class findClass( final String name ) | |||||
| throws ClassNotFoundException | |||||
| { | |||||
| // Try the parent classloaders first | |||||
| for( int i = 0; i < m_parents.length; i++ ) | |||||
| { | |||||
| try | |||||
| { | |||||
| final ClassLoader parent = m_parents[ i ]; | |||||
| return parent.loadClass( name ); | |||||
| } | |||||
| catch( ClassNotFoundException e ) | |||||
| { | |||||
| // Ignore - continue to the next ClassLoader | |||||
| } | |||||
| } | |||||
| // Now this classloader | |||||
| return super.findClass( name ); | |||||
| } | |||||
| /** | |||||
| * Finds a resource. | |||||
| * | |||||
| * @param name the name of the resource | |||||
| * @return a <code>URL</code> for the resource, or <code>null</code> | |||||
| * if the resource could not be found. | |||||
| */ | |||||
| public URL findResource( final String name ) | |||||
| { | |||||
| // Try the parent classloaders first | |||||
| for( int i = 0; i < m_parents.length; i++ ) | |||||
| { | |||||
| final ClassLoader parent = m_parents[ i ]; | |||||
| final URL resource = parent.getResource( name ); | |||||
| if( resource != null ) | |||||
| { | |||||
| return resource; | |||||
| } | |||||
| } | |||||
| // Now this classloader | |||||
| return super.findResource( name ); | |||||
| } | |||||
| /** | |||||
| * Returns an Enumeration of URLs representing all of the resources | |||||
| * having the specified name. | |||||
| * | |||||
| * @param name the resource name | |||||
| * @throws IOException if an I/O exception occurs | |||||
| * @return an <code>Enumeration</code> of <code>URL</code>s | |||||
| */ | |||||
| public Enumeration findResources( final String name ) throws IOException | |||||
| { | |||||
| // Need to filter out duplicate resources | |||||
| final ArrayList urls = new ArrayList(); | |||||
| final Set urlSet = new HashSet(); | |||||
| // Gather the resources from the parent classloaders | |||||
| for( int i = 0; i < m_parents.length; i++ ) | |||||
| { | |||||
| final ClassLoader parent = m_parents[ i ]; | |||||
| final Enumeration enum = parent.getResources( name ); | |||||
| addUrls( enum, urls, urlSet ); | |||||
| } | |||||
| // Gather the resources from this classloader | |||||
| addUrls( super.findResources( name ), urls, urlSet ); | |||||
| return Collections.enumeration( urls ); | |||||
| } | |||||
| /** | |||||
| * Adds those URLs not already present. | |||||
| */ | |||||
| private void addUrls( final Enumeration enum, | |||||
| final List urls, | |||||
| final Set urlSet ) | |||||
| { | |||||
| while( enum.hasMoreElements() ) | |||||
| { | |||||
| final URL url = (URL)enum.nextElement(); | |||||
| final String urlStr = url.toExternalForm(); | |||||
| if( !urlSet.contains( urlStr ) ) | |||||
| { | |||||
| urls.add( url ); | |||||
| urlSet.add( urlStr ); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,6 +1,5 @@ | |||||
| create-classloader-for-file.error=Could not create ClassLoader for files: {0}. | |||||
| available-extensions.notice=The list of available extensions for type library includes; {0} | |||||
| required-extensions.notice=The list of required extensions for type library includes; {0} | |||||
| unsatisfied.extensions.error=Missing {0} extensions for type library. | |||||
| no-file.error=Could not find type library "{0}". | |||||
| file-is-dir.error=Type library "{0}" is a directory. | |||||
| create-classloader-for-file.error=Could not create a ClassLoader for "{0}". | |||||
| create-classloader-for-files.error=Could not create a ClassLoader for {0}. | |||||
| unsatisfied.extension.error=Library "{0}" requires unknown extension "{1}" ( version {2}). | |||||
| no-file.error=Could not find library "{0}". | |||||
| file-is-dir.error=Library "{0}" is a directory. | |||||
| @@ -108,7 +108,7 @@ public class DefaultDeployer | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| final ClassLoader classLoader = m_classLoaderManager.createClassLoader( file ); | |||||
| final ClassLoader classLoader = m_classLoaderManager.getClassLoader( file ); | |||||
| return createDeployment( classLoader, file.toURL() ); | return createDeployment( classLoader, file.toURL() ); | ||||
| } | } | ||||
| catch( final Exception e ) | catch( final Exception e ) | ||||
| @@ -71,10 +71,7 @@ public class DefaultEmbeddor | |||||
| private List m_components = new ArrayList(); | private List m_components = new ArrayList(); | ||||
| private DefaultServiceManager m_serviceManager = new DefaultServiceManager(); | private DefaultServiceManager m_serviceManager = new DefaultServiceManager(); | ||||
| private Parameters m_parameters; | private Parameters m_parameters; | ||||
| private Parameters m_defaults; | |||||
| private File m_homeDir; | |||||
| private File m_taskLibDir; | |||||
| private static final String MYRMIDON_HOME = "myrmidon.home"; | private static final String MYRMIDON_HOME = "myrmidon.home"; | ||||
| /** | /** | ||||
| @@ -163,9 +160,6 @@ public class DefaultEmbeddor | |||||
| public void initialize() | public void initialize() | ||||
| throws Exception | throws Exception | ||||
| { | { | ||||
| // setup default properties | |||||
| m_defaults = createDefaultParameters(); | |||||
| // setup the root components | // setup the root components | ||||
| setupComponents(); | setupComponents(); | ||||
| @@ -183,9 +177,6 @@ public class DefaultEmbeddor | |||||
| m_workspaceServiceManager = new MultiSourceServiceManager(); | m_workspaceServiceManager = new MultiSourceServiceManager(); | ||||
| m_workspaceServiceManager.add( projServiceManager ); | m_workspaceServiceManager.add( projServiceManager ); | ||||
| m_workspaceServiceManager.add( m_serviceManager ); | m_workspaceServiceManager.add( m_serviceManager ); | ||||
| // setup | |||||
| setupFiles(); | |||||
| } | } | ||||
| public void start() | public void start() | ||||
| @@ -198,7 +189,8 @@ public class DefaultEmbeddor | |||||
| // Deploy all type libraries in the lib directory | // Deploy all type libraries in the lib directory | ||||
| final ExtensionFileFilter filter = new ExtensionFileFilter( ".atl" ); | final ExtensionFileFilter filter = new ExtensionFileFilter( ".atl" ); | ||||
| deployFromDirectory( m_deployer, m_taskLibDir, filter ); | |||||
| final File taskLibDir = new File( m_parameters.getParameter( "myrmidon.lib.path" ) ); | |||||
| deployFromDirectory( m_deployer, taskLibDir, filter ); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -230,26 +222,6 @@ public class DefaultEmbeddor | |||||
| m_deployer = null; | m_deployer = null; | ||||
| m_serviceManager = null; | m_serviceManager = null; | ||||
| m_parameters = null; | m_parameters = null; | ||||
| m_defaults = null; | |||||
| m_homeDir = null; | |||||
| m_taskLibDir = null; | |||||
| } | |||||
| /** | |||||
| * Create default properties which includes default names of all components. | |||||
| * Overide this in sub-classes to change values. | |||||
| * | |||||
| * @return the Parameters | |||||
| */ | |||||
| private Parameters createDefaultParameters() | |||||
| { | |||||
| final Parameters defaults = new Parameters(); | |||||
| //create all the default properties for files/directories | |||||
| defaults.setParameter( "myrmidon.bin.path", "bin" ); | |||||
| defaults.setParameter( "myrmidon.lib.path", "lib" ); | |||||
| return defaults; | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -296,80 +268,6 @@ public class DefaultEmbeddor | |||||
| return component; | return component; | ||||
| } | } | ||||
| /** | |||||
| * Setup all the files attributes. | |||||
| */ | |||||
| private void setupFiles() | |||||
| throws Exception | |||||
| { | |||||
| String filepath = null; | |||||
| filepath = getParameter( MYRMIDON_HOME ); | |||||
| m_homeDir = ( new File( filepath ) ).getAbsoluteFile(); | |||||
| checkDirectory( m_homeDir, "home-dir.name" ); | |||||
| filepath = getParameter( "myrmidon.lib.path" ); | |||||
| m_taskLibDir = resolveDirectory( filepath, "task-lib-dir.name" ); | |||||
| } | |||||
| /** | |||||
| * Retrieve value of named property. | |||||
| * First access passed in properties and then the default properties. | |||||
| * | |||||
| * @param name the name of property | |||||
| * @return the value of property or null | |||||
| */ | |||||
| private String getParameter( final String name ) | |||||
| { | |||||
| String value = m_parameters.getParameter( name, null ); | |||||
| if( null == value ) | |||||
| { | |||||
| value = m_defaults.getParameter( name, null ); | |||||
| } | |||||
| return value; | |||||
| } | |||||
| /** | |||||
| * Resolve a directory relative to another base directory. | |||||
| * | |||||
| * @param dir the base directory | |||||
| * @param name the relative directory | |||||
| * @return the created File | |||||
| * @exception Exception if an error occurs | |||||
| */ | |||||
| private File resolveDirectory( final String dir, final String name ) | |||||
| throws Exception | |||||
| { | |||||
| final File file = FileUtil.resolveFile( m_homeDir, dir ); | |||||
| checkDirectory( file, name ); | |||||
| return file; | |||||
| } | |||||
| /** | |||||
| * Verify file is a directory else throw an exception. | |||||
| * | |||||
| * @param file the File | |||||
| * @param name the name of file type (used in error messages) | |||||
| */ | |||||
| private void checkDirectory( final File file, final String name ) | |||||
| throws Exception | |||||
| { | |||||
| if( !file.exists() ) | |||||
| { | |||||
| final String nameStr = REZ.getString( name ); | |||||
| final String message = REZ.getString( "file-no-exist.error", nameStr, file ); | |||||
| throw new Exception( message ); | |||||
| } | |||||
| else if( !file.isDirectory() ) | |||||
| { | |||||
| final String nameStr = REZ.getString( name ); | |||||
| final String message = REZ.getString( "file-not-dir.error", nameStr, file ); | |||||
| throw new Exception( message ); | |||||
| } | |||||
| } | |||||
| /** | /** | ||||
| * Create a component that implements an interface. | * Create a component that implements an interface. | ||||
| * | * | ||||
| @@ -1,10 +1,6 @@ | |||||
| file-no-exist.error={0} ({1}) does not exist. | |||||
| file-not-dir.error={0} ({1}) is not a directory. | |||||
| bad-type.error=Object {0} is not an instance of {1}. | bad-type.error=Object {0} is not an instance of {1}. | ||||
| bad-ctor.error=Non-public constructor for {0} {1}. | bad-ctor.error=Non-public constructor for {0} {1}. | ||||
| no-instantiate.error=Error instantiating class for {0} {1}. | no-instantiate.error=Error instantiating class for {0} {1}. | ||||
| no-class.error=Could not find the class for {0} ({1}). | no-class.error=Could not find the class for {0} ({1}). | ||||
| bad-filename.error=Unable to retrieve filename for file {0}. | bad-filename.error=Unable to retrieve filename for file {0}. | ||||
| home-dir.name=Myrmidon home directory | |||||
| task-lib-dir.name=Task library directory | |||||
| create-project.error=Could not load the project definition from {0}. | create-project.error=Could not load the project definition from {0}. | ||||
| @@ -50,7 +50,6 @@ public class DefaultExtensionManager | |||||
| File.separator + "lib" + File.separator + "tools.jar"; | File.separator + "lib" + File.separator + "tools.jar"; | ||||
| private Logger m_logger; | private Logger m_logger; | ||||
| private String m_path; | private String m_path; | ||||
| public DefaultExtensionManager() | public DefaultExtensionManager() | ||||
| @@ -58,6 +57,11 @@ public class DefaultExtensionManager | |||||
| super( new File[ 0 ] ); | super( new File[ 0 ] ); | ||||
| } | } | ||||
| public DefaultExtensionManager( final File[] path ) | |||||
| { | |||||
| super( path ); | |||||
| } | |||||
| public void enableLogging( final Logger logger ) | public void enableLogging( final Logger logger ) | ||||
| { | { | ||||
| m_logger = logger; | m_logger = logger; | ||||
| @@ -66,16 +70,13 @@ public class DefaultExtensionManager | |||||
| public void parameterize( final Parameters parameters ) | public void parameterize( final Parameters parameters ) | ||||
| throws ParameterException | throws ParameterException | ||||
| { | { | ||||
| final String phoenixHome = parameters.getParameter( "myrmidon.home" ); | |||||
| final String defaultExtPath = phoenixHome + File.separator + "ext"; | |||||
| m_path = parameters.getParameter( "myrmidon.ext.path", defaultExtPath ); | |||||
| m_path = parameters.getParameter( "myrmidon.ext.path" ); | |||||
| } | } | ||||
| public void initialize() | public void initialize() | ||||
| throws Exception | throws Exception | ||||
| { | { | ||||
| final String[] pathElements = StringUtil.split( m_path, "|" ); | |||||
| final String[] pathElements = StringUtil.split( m_path, File.pathSeparator ); | |||||
| final File[] dirs = new File[ pathElements.length ]; | final File[] dirs = new File[ pathElements.length ]; | ||||
| for( int i = 0; i < dirs.length; i++ ) | for( int i = 0; i < dirs.length; i++ ) | ||||
| { | { | ||||
| @@ -86,6 +87,7 @@ public class DefaultExtensionManager | |||||
| scanPath(); | scanPath(); | ||||
| // Add the JVM's tools.jar as an extension | |||||
| final Extension extension = createToolsExtension(); | final Extension extension = createToolsExtension(); | ||||
| final File jar = getToolsJar(); | final File jar = getToolsJar(); | ||||
| final Extension[] available = new Extension[]{extension}; | final Extension[] available = new Extension[]{extension}; | ||||
| @@ -99,6 +101,23 @@ public class DefaultExtensionManager | |||||
| clearCache(); | clearCache(); | ||||
| } | } | ||||
| /** | |||||
| * Locates the optional package which best matches a required extension. | |||||
| * | |||||
| * @param extension the extension to locate an optional package | |||||
| * @return the optional package, or null if not found. | |||||
| */ | |||||
| public OptionalPackage getOptionalPackage( final Extension extension ) | |||||
| { | |||||
| final OptionalPackage[] packages = getOptionalPackages( extension ); | |||||
| if( null == packages || 0 == packages.length ) return null; | |||||
| //TODO: Use heurisitic to find which is best package | |||||
| return packages[ 0 ]; | |||||
| } | |||||
| protected void debug( final String message ) | protected void debug( final String message ) | ||||
| { | { | ||||
| m_logger.debug( message ); | m_logger.debug( message ); | ||||
| @@ -277,10 +277,10 @@ public class CLIMain | |||||
| break; | break; | ||||
| case LISTENER_OPT: | case LISTENER_OPT: | ||||
| m_embedded.setListener( option.getArgument() ); | |||||
| m_embedded.setProjectListener( option.getArgument() ); | |||||
| break; | break; | ||||
| case NO_PREFIX_OPT: | case NO_PREFIX_OPT: | ||||
| m_embedded.setListener( "noprefix" ); | |||||
| m_embedded.setProjectListener( "noprefix" ); | |||||
| break; | break; | ||||
| case DEFINE_OPT: | case DEFINE_OPT: | ||||
| @@ -310,7 +310,7 @@ public class CLIMain | |||||
| try | try | ||||
| { | { | ||||
| // Set system properties set up by launcher | // Set system properties set up by launcher | ||||
| m_embedded.setEmbeddorProperty( "myrmidon.home", properties.get( "myrmidon.home" ) ); | |||||
| m_embedded.setHomeDirectory( (File)properties.get( "myrmidon.home" ) ); | |||||
| // Command line | // Command line | ||||
| if( !parseCommandLineOptions( args ) ) | if( !parseCommandLineOptions( args ) ) | ||||
| @@ -8,8 +8,10 @@ | |||||
| package org.apache.myrmidon.frontends; | package org.apache.myrmidon.frontends; | ||||
| import java.io.File; | import java.io.File; | ||||
| import java.util.ArrayList; | |||||
| import org.apache.avalon.excalibur.i18n.ResourceManager; | import org.apache.avalon.excalibur.i18n.ResourceManager; | ||||
| import org.apache.avalon.excalibur.i18n.Resources; | import org.apache.avalon.excalibur.i18n.Resources; | ||||
| import org.apache.avalon.excalibur.io.FileUtil; | |||||
| import org.apache.avalon.framework.activity.Disposable; | import org.apache.avalon.framework.activity.Disposable; | ||||
| import org.apache.avalon.framework.activity.Initializable; | import org.apache.avalon.framework.activity.Initializable; | ||||
| import org.apache.avalon.framework.activity.Startable; | import org.apache.avalon.framework.activity.Startable; | ||||
| @@ -47,12 +49,25 @@ public class EmbeddedAnt | |||||
| private String m_projectFile = "build.ant"; | private String m_projectFile = "build.ant"; | ||||
| private Project m_project; | private Project m_project; | ||||
| private String m_listenerName = "default"; | private String m_listenerName = "default"; | ||||
| private ProjectListener m_listener; | |||||
| private ArrayList m_listeners = new ArrayList(); | |||||
| private Parameters m_workspaceProps = new Parameters(); | private Parameters m_workspaceProps = new Parameters(); | ||||
| private Parameters m_builderProps = new Parameters(); | private Parameters m_builderProps = new Parameters(); | ||||
| private Parameters m_embeddorParameters = new Parameters(); | private Parameters m_embeddorParameters = new Parameters(); | ||||
| private ClassLoader m_sharedClassLoader; | private ClassLoader m_sharedClassLoader; | ||||
| private Embeddor m_embeddor; | private Embeddor m_embeddor; | ||||
| private File m_homeDir; | |||||
| /** | |||||
| * Sets the Myrmidon home directory. Default is to use the current | |||||
| * directory. | |||||
| * | |||||
| * @todo Autodetect myrmidon home, rather than using current directory | |||||
| * as the default (which is a dud default). | |||||
| */ | |||||
| public void setHomeDirectory( final File homeDir ) | |||||
| { | |||||
| m_homeDir = homeDir.getAbsoluteFile(); | |||||
| } | |||||
| /** | /** | ||||
| * Sets the project file to execute. Default is 'build.ant'. | * Sets the project file to execute. Default is 'build.ant'. | ||||
| @@ -75,21 +90,20 @@ public class EmbeddedAnt | |||||
| } | } | ||||
| /** | /** | ||||
| * Sets the name of the project listener to use. | |||||
| * Sets the name of the project listener to use. Set to null to disable | |||||
| * the project listener. | |||||
| */ | */ | ||||
| public void setListener( final String listener ) | |||||
| public void setProjectListener( final String listener ) | |||||
| { | { | ||||
| m_listenerName = listener; | m_listenerName = listener; | ||||
| m_listener = null; | |||||
| } | } | ||||
| /** | /** | ||||
| * Sets the project listener to use. | |||||
| * Adds a project listener. | |||||
| */ | */ | ||||
| public void setListener( final ProjectListener listener ) | |||||
| public void addProjectListener( final ProjectListener listener ) | |||||
| { | { | ||||
| m_listenerName = null; | |||||
| m_listener = listener; | |||||
| m_listeners.add( listener ); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -99,7 +113,7 @@ public class EmbeddedAnt | |||||
| public void setWorkspaceProperty( final String name, final Object value ) | public void setWorkspaceProperty( final String name, final Object value ) | ||||
| { | { | ||||
| // TODO - Make properties Objects, not Strings | // TODO - Make properties Objects, not Strings | ||||
| m_workspaceProps.setParameter( name, (String)value ); | |||||
| m_workspaceProps.setParameter( name, value.toString() ); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -109,7 +123,7 @@ public class EmbeddedAnt | |||||
| public void setBuilderProperty( final String name, final Object value ) | public void setBuilderProperty( final String name, final Object value ) | ||||
| { | { | ||||
| // TODO - Make properties Objects, not Strings | // TODO - Make properties Objects, not Strings | ||||
| m_builderProps.setParameter( name, (String)value ); | |||||
| m_builderProps.setParameter( name, value.toString() ); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -145,14 +159,13 @@ public class EmbeddedAnt | |||||
| checkHomeDir(); | checkHomeDir(); | ||||
| // Prepare the embeddor, project listener and project model | |||||
| // Prepare the embeddor, and project model | |||||
| final Embeddor embeddor = prepareEmbeddor(); | final Embeddor embeddor = prepareEmbeddor(); | ||||
| final ProjectListener listener = prepareListener( embeddor ); | |||||
| final Project project = prepareProjectModel( embeddor ); | final Project project = prepareProjectModel( embeddor ); | ||||
| // Create a new workspace | // Create a new workspace | ||||
| final Workspace workspace = embeddor.createWorkspace( m_workspaceProps ); | final Workspace workspace = embeddor.createWorkspace( m_workspaceProps ); | ||||
| workspace.addProjectListener( listener ); | |||||
| prepareListeners( embeddor, workspace ); | |||||
| //execute the project | //execute the project | ||||
| executeTargets( workspace, project, targets ); | executeTargets( workspace, project, targets ); | ||||
| @@ -181,7 +194,7 @@ public class EmbeddedAnt | |||||
| { | { | ||||
| m_embeddor = null; | m_embeddor = null; | ||||
| m_project = null; | m_project = null; | ||||
| m_listener = null; | |||||
| m_listeners.clear(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -213,19 +226,26 @@ public class EmbeddedAnt | |||||
| */ | */ | ||||
| private void checkHomeDir() throws Exception | private void checkHomeDir() throws Exception | ||||
| { | { | ||||
| final String home = m_embeddorParameters.getParameter( "myrmidon.home" ); | |||||
| final File homeDir = ( new File( home ) ).getAbsoluteFile(); | |||||
| if( !homeDir.isDirectory() ) | |||||
| if( m_homeDir == null ) | |||||
| { | { | ||||
| final String message = REZ.getString( "home-not-dir.error", homeDir ); | |||||
| throw new Exception( message ); | |||||
| m_homeDir = new File( "." ).getAbsoluteFile(); | |||||
| } | } | ||||
| checkDirectory( m_homeDir, "home-dir.name" ); | |||||
| m_embeddorParameters.setParameter( "myrmidon.home", m_homeDir.getAbsolutePath() ); | |||||
| if( getLogger().isInfoEnabled() ) | if( getLogger().isInfoEnabled() ) | ||||
| { | { | ||||
| final String message = REZ.getString( "homedir.notice", homeDir ); | |||||
| final String message = REZ.getString( "homedir.notice", m_homeDir ); | |||||
| getLogger().info( message ); | getLogger().info( message ); | ||||
| } | } | ||||
| String path = m_embeddorParameters.getParameter( "myrmidon.lib.path", "lib" ); | |||||
| File dir = resolveDirectory( m_homeDir, path, "task-lib-dir.name" ); | |||||
| m_embeddorParameters.setParameter( "myrmidon.lib.path", dir.getAbsolutePath() ); | |||||
| path = m_embeddorParameters.getParameter( "myrmidon.ext.path", "ext" ); | |||||
| dir = resolveDirectory( m_homeDir, path, "ext-dir.name" ); | |||||
| m_embeddorParameters.setParameter( "myrmidon.ext.path", dir.getAbsolutePath() ); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -267,14 +287,21 @@ public class EmbeddedAnt | |||||
| /** | /** | ||||
| * Prepares and returns the project listener to use. | * Prepares and returns the project listener to use. | ||||
| */ | */ | ||||
| private ProjectListener prepareListener( final Embeddor embeddor ) | |||||
| private void prepareListeners( final Embeddor embeddor, | |||||
| final Workspace workspace ) | |||||
| throws Exception | throws Exception | ||||
| { | { | ||||
| if( m_listener == null ) | |||||
| if( m_listenerName != null ) | |||||
| { | |||||
| final ProjectListener listener = embeddor.createListener( m_listenerName ); | |||||
| workspace.addProjectListener( listener ); | |||||
| } | |||||
| final int count = m_listeners.size(); | |||||
| for( int i = 0; i < count; i++ ) | |||||
| { | { | ||||
| m_listener = embeddor.createListener( m_listenerName ); | |||||
| final ProjectListener listener = (ProjectListener)m_listeners.get(i ); | |||||
| workspace.addProjectListener( listener ); | |||||
| } | } | ||||
| return m_listener; | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -310,4 +337,36 @@ public class EmbeddedAnt | |||||
| return projectFile; | return projectFile; | ||||
| } | } | ||||
| /** | |||||
| * Resolve a directory relative to another base directory. | |||||
| */ | |||||
| private File resolveDirectory( final File baseDir, final String dir, final String name ) | |||||
| throws Exception | |||||
| { | |||||
| final File file = FileUtil.resolveFile( baseDir, dir ); | |||||
| checkDirectory( file, name ); | |||||
| return file; | |||||
| } | |||||
| /** | |||||
| * Verify file is a directory else throw an exception. | |||||
| */ | |||||
| private void checkDirectory( final File file, final String name ) | |||||
| throws Exception | |||||
| { | |||||
| if( !file.exists() ) | |||||
| { | |||||
| final String nameStr = REZ.getString( name ); | |||||
| final String message = REZ.getString( "file-no-exist.error", nameStr, file ); | |||||
| throw new Exception( message ); | |||||
| } | |||||
| else if( !file.isDirectory() ) | |||||
| { | |||||
| final String nameStr = REZ.getString( name ); | |||||
| final String message = REZ.getString( "file-not-dir.error", nameStr, file ); | |||||
| throw new Exception( message ); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -16,7 +16,8 @@ define.opt=Define a property (ie -Dfoo=var). | |||||
| build.opt=Define a builder parameter (ie -Bfoo=var). | build.opt=Define a builder parameter (ie -Bfoo=var). | ||||
| dry-run.opt=Do not execute tasks - just print them out. | dry-run.opt=Do not execute tasks - just print them out. | ||||
| home-not-dir.error=myrmidon-home ({0}) is not a directory. | |||||
| file-no-exist.error={0} {1} does not exist. | |||||
| file-not-dir.error={0} {1} is not a directory. | |||||
| bad-file.error=File {0} is not a file or doesn't exist. | bad-file.error=File {0} is not a file or doesn't exist. | ||||
| bad-loglevel.error=Unknown log level - {0}. | bad-loglevel.error=Unknown log level - {0}. | ||||
| build-failed.error=BUILD FAILED. | build-failed.error=BUILD FAILED. | ||||
| @@ -26,3 +27,6 @@ repeat.notice=Continue ? (Enter no to stop) | |||||
| homedir.notice=Ant Home Directory: {0} | homedir.notice=Ant Home Directory: {0} | ||||
| buildfile.notice=Ant Build File: {0} | buildfile.notice=Ant Build File: {0} | ||||
| home-dir.name=Ant home directory | |||||
| task-lib-dir.name=Task library directory | |||||
| ext-dir.name=Extension library directory | |||||
| @@ -13,6 +13,7 @@ import java.io.File; | |||||
| * Manages a classloader hierarchy. | * Manages a classloader hierarchy. | ||||
| * | * | ||||
| * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | ||||
| * @version $Revision$ $Date$ | |||||
| */ | */ | ||||
| public interface ClassLoaderManager | public interface ClassLoaderManager | ||||
| { | { | ||||
| @@ -20,15 +21,22 @@ public interface ClassLoaderManager | |||||
| String ROLE = ClassLoaderManager.class.getName(); | String ROLE = ClassLoaderManager.class.getName(); | ||||
| /** | /** | ||||
| * Builds the ClassLoader for a Jar file, resolving dependencies. | |||||
| * Returns the ClassLoader for a Jar file. The ClassLoader is created, | |||||
| * if necessary. The ClassLoader's parent will include the common | |||||
| * ClassLoader, along with any extensions required by the Jar file. | |||||
| * It is guaranteed that each extension will appear at most once in the | |||||
| * ClassLoader hierarchy, so that classes from the extension can be | |||||
| * shared across the ClassLoaders returned by this method. | |||||
| * | |||||
| * @param jar the jar file containing the classes to load | * @param jar the jar file containing the classes to load | ||||
| * @return the created classloader | |||||
| * @return the classloader | |||||
| * @throws ClassLoaderException on error | * @throws ClassLoaderException on error | ||||
| */ | */ | ||||
| ClassLoader createClassLoader( File jar ) throws ClassLoaderException; | |||||
| ClassLoader getClassLoader( File jar ) throws ClassLoaderException; | |||||
| /** | /** | ||||
| * Builds the ClassLoader for a set of files, resolving dependencies. | |||||
| * Creates a ClassLoader for a set of files. See {@link #getClassLoader} | |||||
| * for details. | |||||
| * | * | ||||
| * @param jars The Jar/zip files to create the classloader for. Use null | * @param jars The Jar/zip files to create the classloader for. Use null | ||||
| * or an empty array to use the common classloader. | * or an empty array to use the common classloader. | ||||
| @@ -38,9 +46,10 @@ public interface ClassLoaderManager | |||||
| ClassLoader createClassLoader( File[] jars ) throws ClassLoaderException; | ClassLoader createClassLoader( File[] jars ) throws ClassLoaderException; | ||||
| /** | /** | ||||
| * Provides the common ClassLoader, which is the parent of all classloaders | |||||
| * Returns the common ClassLoader, which is the parent of all classloaders | |||||
| * built by this ClassLoaderManager. | * built by this ClassLoaderManager. | ||||
| * @return the common ClassLoader | |||||
| * | |||||
| * @return the common ClassLoader. | |||||
| */ | */ | ||||
| ClassLoader getCommonClassLoader(); | ClassLoader getCommonClassLoader(); | ||||
| } | } | ||||
| @@ -7,17 +7,25 @@ | |||||
| */ | */ | ||||
| package org.apache.myrmidon.interfaces.extensions; | package org.apache.myrmidon.interfaces.extensions; | ||||
| import org.apache.avalon.excalibur.extension.PackageRepository; | |||||
| import org.apache.avalon.excalibur.extension.Extension; | |||||
| import org.apache.avalon.excalibur.extension.OptionalPackage; | |||||
| /** | /** | ||||
| * PackageRepository | |||||
| * Maintains a set of optional packages. | |||||
| * | * | ||||
| * @author <a href="mailto:peter@apache.org">Peter Donald</a> | * @author <a href="mailto:peter@apache.org">Peter Donald</a> | ||||
| * @version $Revision$ $Date$ | * @version $Revision$ $Date$ | ||||
| */ | */ | ||||
| public interface ExtensionManager | public interface ExtensionManager | ||||
| extends PackageRepository | |||||
| { | { | ||||
| /** Role name for this interface. */ | /** Role name for this interface. */ | ||||
| String ROLE = ExtensionManager.class.getName(); | String ROLE = ExtensionManager.class.getName(); | ||||
| /** | |||||
| * Locates the optional package which best matches a required extension. | |||||
| * | |||||
| * @param extension the extension to locate an optional package | |||||
| * @return the optional package, or null if not found. | |||||
| */ | |||||
| public OptionalPackage getOptionalPackage( Extension extension ); | |||||
| } | } | ||||
| @@ -29,28 +29,21 @@ public abstract class AbstractMyrmidonTest | |||||
| private final File m_baseDir; | private final File m_baseDir; | ||||
| private Logger m_logger; | private Logger m_logger; | ||||
| protected static final Resources getResourcesForTested( final Class clazz ) | |||||
| public AbstractMyrmidonTest( final String name ) | |||||
| { | { | ||||
| final Package pkg = clazz.getPackage(); | |||||
| String baseName; | |||||
| if( null == pkg ) | |||||
| { | |||||
| final String name = clazz.getName(); | |||||
| if( -1 == name.lastIndexOf( "." ) ) | |||||
| { | |||||
| baseName = ""; | |||||
| } | |||||
| else | |||||
| { | |||||
| baseName = name.substring( 0, name.lastIndexOf( "." ) ); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| baseName = pkg.getName(); | |||||
| } | |||||
| super( name ); | |||||
| final String baseDirProp = System.getProperty( "test.basedir" ); | |||||
| m_baseDir = getCanonicalFile( new File( baseDirProp ) ); | |||||
| final String packagePath = getPackageName( getClass() ).replace( '.', File.separatorChar ); | |||||
| m_testBaseDir = getCanonicalFile( new File( m_baseDir, packagePath ) ); | |||||
| } | |||||
| /** | |||||
| * Locates the error message resources for a class. | |||||
| */ | |||||
| protected static final Resources getResourcesForTested( final Class clazz ) | |||||
| { | |||||
| String baseName = getPackageName( clazz ); | |||||
| if( baseName.endsWith( ".test" ) ) | if( baseName.endsWith( ".test" ) ) | ||||
| { | { | ||||
| baseName = baseName.substring( 0, baseName.length() - 5 ); | baseName = baseName.substring( 0, baseName.length() - 5 ); | ||||
| @@ -59,16 +52,29 @@ public abstract class AbstractMyrmidonTest | |||||
| return ResourceManager.getBaseResources( baseName + ".Resources", AbstractMyrmidonTest.class.getClassLoader() ); | return ResourceManager.getBaseResources( baseName + ".Resources", AbstractMyrmidonTest.class.getClassLoader() ); | ||||
| } | } | ||||
| public AbstractMyrmidonTest( String name ) | |||||
| /** | |||||
| * Returns the name of the package containing a class. | |||||
| * | |||||
| * @return The . delimited package name, or an empty string if the class | |||||
| * is in the default package. | |||||
| */ | |||||
| protected static String getPackageName( final Class clazz ) | |||||
| { | { | ||||
| super( name ); | |||||
| final String baseDirProp = System.getProperty( "test.basedir" ); | |||||
| m_baseDir = getCanonicalFile( new File( baseDirProp ) ); | |||||
| String packagePath = getClass().getName(); | |||||
| int idx = packagePath.lastIndexOf( '.' ); | |||||
| packagePath = packagePath.substring( 0, idx ); | |||||
| packagePath = packagePath.replace( '.', File.separatorChar ); | |||||
| m_testBaseDir = getCanonicalFile( new File( m_baseDir, packagePath ) ); | |||||
| final Package pkg = clazz.getPackage(); | |||||
| if( null != pkg ) | |||||
| { | |||||
| return pkg.getName(); | |||||
| } | |||||
| final String name = clazz.getName(); | |||||
| if( -1 == name.lastIndexOf( "." ) ) | |||||
| { | |||||
| return ""; | |||||
| } | |||||
| else | |||||
| { | |||||
| return name.substring( 0, name.lastIndexOf( "." ) ); | |||||
| } | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -8,12 +8,7 @@ | |||||
| package org.apache.myrmidon; | package org.apache.myrmidon; | ||||
| import java.io.File; | import java.io.File; | ||||
| import org.apache.avalon.framework.logger.Logger; | |||||
| import org.apache.avalon.framework.parameters.Parameters; | |||||
| import org.apache.myrmidon.components.embeddor.DefaultEmbeddor; | |||||
| import org.apache.myrmidon.interfaces.embeddor.Embeddor; | |||||
| import org.apache.myrmidon.interfaces.model.Project; | |||||
| import org.apache.myrmidon.interfaces.workspace.Workspace; | |||||
| import org.apache.myrmidon.frontends.EmbeddedAnt; | |||||
| import org.apache.myrmidon.listeners.ProjectListener; | import org.apache.myrmidon.listeners.ProjectListener; | ||||
| /** | /** | ||||
| @@ -25,50 +20,11 @@ import org.apache.myrmidon.listeners.ProjectListener; | |||||
| public class AbstractProjectTest | public class AbstractProjectTest | ||||
| extends AbstractMyrmidonTest | extends AbstractMyrmidonTest | ||||
| { | { | ||||
| private DefaultEmbeddor m_embeddor; | |||||
| public AbstractProjectTest( final String name ) | public AbstractProjectTest( final String name ) | ||||
| { | { | ||||
| super( name ); | super( name ); | ||||
| } | } | ||||
| /** | |||||
| * Tear-down the test. | |||||
| */ | |||||
| protected void tearDown() throws Exception | |||||
| { | |||||
| if( m_embeddor != null ) | |||||
| { | |||||
| m_embeddor.dispose(); | |||||
| m_embeddor = null; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Returns an embeddor which can be used to build and execute projects. | |||||
| */ | |||||
| protected Embeddor getEmbeddor() throws Exception | |||||
| { | |||||
| if( m_embeddor == null ) | |||||
| { | |||||
| // Need to set the context classloader - The default embeddor uses it | |||||
| Thread.currentThread().setContextClassLoader( getClass().getClassLoader() ); | |||||
| final Logger logger = getLogger(); | |||||
| m_embeddor = new DefaultEmbeddor(); | |||||
| m_embeddor.enableLogging( logger ); | |||||
| final Parameters params = new Parameters(); | |||||
| final File instDir = getInstallDirectory(); | |||||
| params.setParameter( "myrmidon.home", instDir.getAbsolutePath() ); | |||||
| m_embeddor.parameterize( params ); | |||||
| m_embeddor.initialize(); | |||||
| m_embeddor.start(); | |||||
| } | |||||
| return m_embeddor; | |||||
| } | |||||
| /** | /** | ||||
| * Executes a target in a project, and asserts that it fails with the | * Executes a target in a project, and asserts that it fails with the | ||||
| * given error message. | * given error message. | ||||
| @@ -117,22 +73,27 @@ public class AbstractProjectTest | |||||
| throws Exception | throws Exception | ||||
| { | { | ||||
| // Create the project and workspace | // Create the project and workspace | ||||
| final Embeddor embeddor = getEmbeddor(); | |||||
| final Project project = embeddor.createProject( projectFile.getAbsolutePath(), null, null ); | |||||
| final Workspace workspace = embeddor.createWorkspace( new Parameters() ); | |||||
| final EmbeddedAnt embeddor = new EmbeddedAnt(); | |||||
| embeddor.setHomeDirectory( getInstallDirectory() ); | |||||
| embeddor.enableLogging( getLogger() ); | |||||
| embeddor.setSharedClassLoader( getClass().getClassLoader() ); | |||||
| embeddor.setProjectFile( projectFile.getAbsolutePath() ); | |||||
| embeddor.setProjectListener( null ); | |||||
| // Add a listener to make sure all is good | // Add a listener to make sure all is good | ||||
| final TrackingProjectListener tracker = new TrackingProjectListener(); | final TrackingProjectListener tracker = new TrackingProjectListener(); | ||||
| workspace.addProjectListener( tracker ); | |||||
| embeddor.addProjectListener( tracker ); | |||||
| // Add supplied listener | // Add supplied listener | ||||
| if( listener != null ) | if( listener != null ) | ||||
| { | { | ||||
| workspace.addProjectListener( listener ); | |||||
| embeddor.addProjectListener( listener ); | |||||
| } | } | ||||
| // Now execute the target | // Now execute the target | ||||
| workspace.executeProject( project, targetName ); | |||||
| embeddor.executeTargets( new String[] { targetName } ); | |||||
| embeddor.stop(); | |||||
| // Make sure all expected events were delivered | // Make sure all expected events were delivered | ||||
| tracker.assertComplete(); | tracker.assertComplete(); | ||||
| @@ -10,12 +10,16 @@ package org.apache.myrmidon.components; | |||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||
| import java.util.Iterator; | import java.util.Iterator; | ||||
| import java.util.List; | import java.util.List; | ||||
| import java.io.File; | |||||
| import org.apache.aut.converter.Converter; | import org.apache.aut.converter.Converter; | ||||
| import org.apache.avalon.framework.logger.LogEnabled; | import org.apache.avalon.framework.logger.LogEnabled; | ||||
| import org.apache.avalon.framework.logger.Logger; | import org.apache.avalon.framework.logger.Logger; | ||||
| import org.apache.avalon.framework.service.DefaultServiceManager; | import org.apache.avalon.framework.service.DefaultServiceManager; | ||||
| import org.apache.avalon.framework.service.ServiceManager; | import org.apache.avalon.framework.service.ServiceManager; | ||||
| import org.apache.avalon.framework.service.Serviceable; | import org.apache.avalon.framework.service.Serviceable; | ||||
| import org.apache.avalon.framework.parameters.Parameters; | |||||
| import org.apache.avalon.framework.parameters.Parameterizable; | |||||
| import org.apache.avalon.framework.activity.Initializable; | |||||
| import org.apache.myrmidon.AbstractMyrmidonTest; | import org.apache.myrmidon.AbstractMyrmidonTest; | ||||
| import org.apache.myrmidon.components.classloader.DefaultClassLoaderManager; | import org.apache.myrmidon.components.classloader.DefaultClassLoaderManager; | ||||
| import org.apache.myrmidon.components.configurer.DefaultConfigurer; | import org.apache.myrmidon.components.configurer.DefaultConfigurer; | ||||
| @@ -93,10 +97,9 @@ public abstract class AbstractComponentTest | |||||
| m_serviceManager.put( Executor.ROLE, component ); | m_serviceManager.put( Executor.ROLE, component ); | ||||
| components.add( component ); | components.add( component ); | ||||
| final DefaultClassLoaderManager classLoaderMgr = new DefaultClassLoaderManager(); | |||||
| classLoaderMgr.setCommonClassLoader( getClass().getClassLoader() ); | |||||
| m_serviceManager.put( ClassLoaderManager.ROLE, classLoaderMgr ); | |||||
| components.add( classLoaderMgr ); | |||||
| component = createComponent( ClassLoaderManager.ROLE, DefaultClassLoaderManager.class ); | |||||
| m_serviceManager.put( ClassLoaderManager.ROLE, component ); | |||||
| components.add( component ); | |||||
| component = createComponent( ExtensionManager.ROLE, DefaultExtensionManager.class ); | component = createComponent( ExtensionManager.ROLE, DefaultExtensionManager.class ); | ||||
| m_serviceManager.put( ExtensionManager.ROLE, component ); | m_serviceManager.put( ExtensionManager.ROLE, component ); | ||||
| @@ -132,6 +135,29 @@ public abstract class AbstractComponentTest | |||||
| } | } | ||||
| } | } | ||||
| // Parameterise the components | |||||
| final Parameters parameters = getParameters(); | |||||
| for( Iterator iterator = components.iterator(); iterator.hasNext(); ) | |||||
| { | |||||
| Object obj = iterator.next(); | |||||
| if( obj instanceof Parameterizable ) | |||||
| { | |||||
| final Parameterizable parameterizable = (Parameterizable)obj; | |||||
| parameterizable.parameterize( parameters ); | |||||
| } | |||||
| } | |||||
| // Initialise the components | |||||
| for( Iterator iterator = components.iterator(); iterator.hasNext(); ) | |||||
| { | |||||
| Object obj = iterator.next(); | |||||
| if( obj instanceof Initializable ) | |||||
| { | |||||
| final Initializable initializable = (Initializable)obj; | |||||
| initializable.initialize(); | |||||
| } | |||||
| } | |||||
| // Register some standard roles | // Register some standard roles | ||||
| // Add some core roles | // Add some core roles | ||||
| final RoleManager roleManager = (RoleManager)getServiceManager().lookup( RoleManager.ROLE ); | final RoleManager roleManager = (RoleManager)getServiceManager().lookup( RoleManager.ROLE ); | ||||
| @@ -144,12 +170,29 @@ public abstract class AbstractComponentTest | |||||
| } | } | ||||
| /** | /** | ||||
| * Creates an instance of a component. Sub-classes can override this | |||||
| * Creates the parameters for the test. Sub-classes can override this | |||||
| * method to set-up the parameters. | |||||
| */ | |||||
| protected Parameters getParameters() | |||||
| { | |||||
| final Parameters parameters = new Parameters(); | |||||
| final String homeDir = getInstallDirectory().getAbsolutePath(); | |||||
| parameters.setParameter( "myrmidon.home", homeDir ); | |||||
| parameters.setParameter( "myrmidon.ext.path", homeDir + File.separatorChar + "ext" ); | |||||
| return parameters; | |||||
| } | |||||
| /** | |||||
| * Creates an instance of a test component. Sub-classes can override this | |||||
| * method to add a particular implementation to the set of test components. | * method to add a particular implementation to the set of test components. | ||||
| */ | */ | ||||
| protected Object createComponent( final String role, final Class defaultImpl ) | protected Object createComponent( final String role, final Class defaultImpl ) | ||||
| throws Exception | throws Exception | ||||
| { | { | ||||
| if( role.equals( ClassLoaderManager.ROLE ) ) | |||||
| { | |||||
| return new DefaultClassLoaderManager( getClass().getClassLoader() ); | |||||
| } | |||||
| return defaultImpl.newInstance(); | return defaultImpl.newInstance(); | ||||
| } | } | ||||
| @@ -0,0 +1,364 @@ | |||||
| /* | |||||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||||
| * | |||||
| * This software is published under the terms of the Apache Software License | |||||
| * version 1.1, a copy of which has been included with this distribution in | |||||
| * the LICENSE.txt file. | |||||
| */ | |||||
| package org.apache.myrmidon.components.classloader.test; | |||||
| import java.io.File; | |||||
| import java.net.URL; | |||||
| import java.net.URLClassLoader; | |||||
| import java.util.Enumeration; | |||||
| import org.apache.myrmidon.components.AbstractComponentTest; | |||||
| import org.apache.myrmidon.components.classloader.DefaultClassLoaderManager; | |||||
| import org.apache.myrmidon.interfaces.classloader.ClassLoaderManager; | |||||
| import org.apache.myrmidon.interfaces.classloader.ClassLoaderException; | |||||
| import org.apache.avalon.framework.parameters.Parameters; | |||||
| import org.apache.avalon.excalibur.i18n.Resources; | |||||
| /** | |||||
| * Test cases for the DefaultClassLoaderManager. | |||||
| * | |||||
| * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
| * @version $Revision$ $Date$ | |||||
| */ | |||||
| public class DefaultClassLoaderManagerTestCase | |||||
| extends AbstractComponentTest | |||||
| { | |||||
| private static final String UNSHARED_PKG_NAME = | |||||
| getPackageName( DefaultClassLoaderManagerTestCase.class ) + ".libs.unshared"; | |||||
| private static final String UNSHARED_RES_NAME = getResourceName( UNSHARED_PKG_NAME, "unshared.txt" ); | |||||
| private static final String UNSHARED_CLASS_NAME = UNSHARED_PKG_NAME + ".UnsharedClass"; | |||||
| private static final String SHARED_PKG_NAME = | |||||
| getPackageName( DefaultClassLoaderManagerTestCase.class ) + ".libs.shared"; | |||||
| private static final String SHARED_RES_NAME = getResourceName( SHARED_PKG_NAME, "shared.txt" ); | |||||
| private static final String SHARED_CLASS_NAME = SHARED_PKG_NAME + ".SharedClass"; | |||||
| private static final String EXTN_PKG_NAME = | |||||
| getPackageName( DefaultClassLoaderManagerTestCase.class ) + ".libs.extn"; | |||||
| private static final String EXTN_RES_NAME = getResourceName( EXTN_PKG_NAME, "extn.txt" ); | |||||
| private static final String EXTN_CLASS_NAME = EXTN_PKG_NAME + ".ExtnClass"; | |||||
| private File m_commonJar; | |||||
| private ClassLoader m_commonClassLoader; | |||||
| private ClassLoaderManager m_loaderManager; | |||||
| public DefaultClassLoaderManagerTestCase( final String name ) | |||||
| { | |||||
| super( name ); | |||||
| } | |||||
| /** | |||||
| * Sets up the test. | |||||
| */ | |||||
| protected void setUp() throws Exception | |||||
| { | |||||
| m_commonJar = getTestResource( "common.jar" ); | |||||
| final URL commonJarUrl = m_commonJar.toURL(); | |||||
| m_commonClassLoader = new URLClassLoader( new URL[]{commonJarUrl} ); | |||||
| assertClassFound( m_commonClassLoader, SHARED_CLASS_NAME ); | |||||
| assertResourcesFound( m_commonClassLoader, SHARED_RES_NAME, m_commonJar ); | |||||
| // Create the classloader mgr | |||||
| m_loaderManager = (ClassLoaderManager)getServiceManager().lookup( ClassLoaderManager.ROLE ); | |||||
| } | |||||
| /** | |||||
| * Creates an instance of a test component. | |||||
| */ | |||||
| protected Object createComponent( final String role, final Class defaultImpl ) | |||||
| throws Exception | |||||
| { | |||||
| if( role.equals( ClassLoaderManager.ROLE ) ) | |||||
| { | |||||
| return new DefaultClassLoaderManager( m_commonClassLoader ); | |||||
| } | |||||
| else | |||||
| { | |||||
| return super.createComponent( role, defaultImpl ); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Creates the parameters for the test. Sub-classes can override this | |||||
| * method to set-up the parameters. | |||||
| */ | |||||
| protected Parameters getParameters() | |||||
| { | |||||
| final Parameters parameters = super.getParameters(); | |||||
| parameters.setParameter( "myrmidon.ext.path", getTestDirectory( "ext" ).getAbsolutePath() ); | |||||
| return parameters; | |||||
| } | |||||
| /** | |||||
| * Returns the name of a resource in a package. | |||||
| */ | |||||
| private static String getResourceName( final String pkgName, | |||||
| final String resname ) | |||||
| { | |||||
| return pkgName.replace( '.', '/' ) + '/' + resname; | |||||
| } | |||||
| /** | |||||
| * Asserts that a class is not available in a classloader. | |||||
| */ | |||||
| private void assertClassNotFound( final ClassLoader classLoader, | |||||
| final String className ) | |||||
| { | |||||
| try | |||||
| { | |||||
| classLoader.loadClass( className ); | |||||
| fail( "Class " + className + " should not be available." ); | |||||
| } | |||||
| catch( ClassNotFoundException e ) | |||||
| { | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Asserts that a class is available in a classloader. | |||||
| */ | |||||
| private void assertClassFound( final ClassLoader classLoader, | |||||
| final String className ) | |||||
| throws Exception | |||||
| { | |||||
| assertClassFound( classLoader, className, classLoader ); | |||||
| } | |||||
| /** | |||||
| * Asserts that a class is available in a classloader. | |||||
| */ | |||||
| private void assertClassFound( final ClassLoader classLoader, | |||||
| final String className, | |||||
| final ClassLoader expectedClassLoader ) | |||||
| throws Exception | |||||
| { | |||||
| try | |||||
| { | |||||
| final Class cls = classLoader.loadClass( className ); | |||||
| assertSame( expectedClassLoader, cls.getClassLoader() ); | |||||
| if( classLoader != expectedClassLoader ) | |||||
| { | |||||
| final Class expectedCls = expectedClassLoader.loadClass( className ); | |||||
| assertSame( expectedCls, cls ); | |||||
| } | |||||
| } | |||||
| catch( ClassNotFoundException e ) | |||||
| { | |||||
| fail( "Class " + className + " not found." ); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Asserts that a resouce is not available in a classloader. | |||||
| */ | |||||
| private void assertResourceNotFound( final ClassLoader classLoader, | |||||
| final String resName ) | |||||
| throws Exception | |||||
| { | |||||
| assertNull( classLoader.getResource( resName ) ); | |||||
| assertNull( classLoader.getResourceAsStream( resName ) ); | |||||
| final Enumeration enum = classLoader.getResources( resName ); | |||||
| assertTrue( !enum.hasMoreElements() ); | |||||
| } | |||||
| /** | |||||
| * Asserts that a resource is available in a classloader. | |||||
| */ | |||||
| private void assertResourcesFound( final ClassLoader classLoader, | |||||
| final String resName, | |||||
| final File expectedJar ) | |||||
| throws Exception | |||||
| { | |||||
| assertResourcesFound( classLoader, resName, new File[]{expectedJar} ); | |||||
| } | |||||
| /** | |||||
| * Asserts that a resource is available in a classloader. | |||||
| */ | |||||
| private void assertResourcesFound( final ClassLoader classLoader, | |||||
| final String resName, | |||||
| final File[] expectedJars ) | |||||
| throws Exception | |||||
| { | |||||
| final String[] expectedLocations = new String[ expectedJars.length ]; | |||||
| for( int i = 0; i < expectedJars.length; i++ ) | |||||
| { | |||||
| final File jar = expectedJars[ i ]; | |||||
| expectedLocations[ i ] = "jar:" + jar.toURL() + "!/" + resName; | |||||
| } | |||||
| assertResourcesFound( classLoader, resName, expectedLocations ); | |||||
| } | |||||
| /** | |||||
| * Asserts that a resource is available in a classloader. | |||||
| */ | |||||
| private void assertResourcesFound( final ClassLoader classLoader, | |||||
| final String resName, | |||||
| final String[] expectedLocations ) | |||||
| throws Exception | |||||
| { | |||||
| // Use the first in the list of expected locations as the location | |||||
| // of the resource returned by getResource() | |||||
| final URL resUrl = classLoader.getResource( resName ); | |||||
| assertNotNull( resUrl ); | |||||
| assertEquals( expectedLocations[ 0 ], resUrl.toString() ); | |||||
| // Now check all of the resources returned by getResources() | |||||
| final Enumeration resources = classLoader.getResources( resName ); | |||||
| for( int i = 0; i < expectedLocations.length; i++ ) | |||||
| { | |||||
| final String expectedLocation = expectedLocations[ i ]; | |||||
| assertTrue( resources.hasMoreElements() ); | |||||
| final URL location = (URL)resources.nextElement(); | |||||
| assertEquals( expectedLocation, location.toString() ); | |||||
| } | |||||
| assertTrue( !resources.hasMoreElements() ); | |||||
| } | |||||
| /** | |||||
| * Tests for a Jar with no required extensions. | |||||
| */ | |||||
| public void testNoDependencies() throws Exception | |||||
| { | |||||
| // Make some assumptions about the common classloader | |||||
| assertClassNotFound( m_commonClassLoader, UNSHARED_CLASS_NAME ); | |||||
| assertResourceNotFound( m_commonClassLoader, UNSHARED_RES_NAME ); | |||||
| // Build the classloader | |||||
| final File jarFile = getTestResource( "no-dependencies.jar" ); | |||||
| final ClassLoader classLoader = m_loaderManager.getClassLoader( jarFile ); | |||||
| // Check shared classes/resources | |||||
| assertClassFound( classLoader, SHARED_CLASS_NAME, m_commonClassLoader ); | |||||
| assertResourcesFound( classLoader, SHARED_RES_NAME, new File[]{m_commonJar, jarFile} ); | |||||
| // Check unshared classes/resources | |||||
| assertClassFound( classLoader, UNSHARED_CLASS_NAME ); | |||||
| assertResourcesFound( classLoader, UNSHARED_RES_NAME, jarFile ); | |||||
| } | |||||
| /** | |||||
| * Tests ClassLoader caching. | |||||
| */ | |||||
| public void testClassLoaderReuse() throws Exception | |||||
| { | |||||
| final File jarFile = getTestResource( "no-dependencies.jar" ); | |||||
| final ClassLoader classLoader1 = m_loaderManager.getClassLoader( jarFile ); | |||||
| final ClassLoader classLoader2 = m_loaderManager.getClassLoader( jarFile ); | |||||
| assertSame( classLoader1, classLoader2 ); | |||||
| } | |||||
| /** | |||||
| * Tests for a Jar with a single required extension. | |||||
| */ | |||||
| public void testOneDependency() throws Exception | |||||
| { | |||||
| // Make some assumptions about the common classloader | |||||
| assertClassNotFound( m_commonClassLoader, UNSHARED_CLASS_NAME ); | |||||
| assertResourceNotFound( m_commonClassLoader, UNSHARED_RES_NAME ); | |||||
| assertClassNotFound( m_commonClassLoader, EXTN_CLASS_NAME ); | |||||
| assertResourceNotFound( m_commonClassLoader, EXTN_RES_NAME ); | |||||
| // Build the extension classloader | |||||
| final File extnJarFile = getTestResource( "ext/simple-extension.jar" ); | |||||
| final ClassLoader extnClassLoader = m_loaderManager.getClassLoader( extnJarFile ); | |||||
| // Build the Jar classloader | |||||
| final File jarFile = getTestResource( "one-dependency.jar" ); | |||||
| final ClassLoader classLoader = m_loaderManager.getClassLoader( jarFile ); | |||||
| // Check shared classes/resources | |||||
| assertClassFound( classLoader, SHARED_CLASS_NAME, m_commonClassLoader ); | |||||
| assertResourcesFound( classLoader, SHARED_RES_NAME, new File[]{m_commonJar, extnJarFile, jarFile} ); | |||||
| // Check extension classes/resources | |||||
| assertClassFound( classLoader, EXTN_CLASS_NAME, extnClassLoader ); | |||||
| assertResourcesFound( classLoader, EXTN_RES_NAME, extnJarFile ); | |||||
| // Check unshared classes/resources | |||||
| assertClassFound( classLoader, UNSHARED_CLASS_NAME ); | |||||
| assertResourcesFound( classLoader, UNSHARED_RES_NAME, jarFile ); | |||||
| } | |||||
| /** | |||||
| * Tests that classes from extensions can be shared across classloaders. | |||||
| */ | |||||
| public void testShareClasses() throws Exception | |||||
| { | |||||
| // Build the extension classloader | |||||
| final File extnJarFile = getTestResource( "ext/simple-extension.jar" ); | |||||
| final ClassLoader extnClassLoader = m_loaderManager.getClassLoader( extnJarFile ); | |||||
| // Build the Jar classloaders | |||||
| final File jarFile1 = getTestResource( "one-dependency.jar" ); | |||||
| final ClassLoader classLoader1 = m_loaderManager.getClassLoader( jarFile1 ); | |||||
| final File jarFile2 = getTestResource( "one-dependency-2.jar" ); | |||||
| final ClassLoader classLoader2 = m_loaderManager.getClassLoader( jarFile2 ); | |||||
| // Check extension classes/resources | |||||
| assertClassFound( classLoader1, EXTN_CLASS_NAME, extnClassLoader ); | |||||
| assertResourcesFound( classLoader1, EXTN_RES_NAME, extnJarFile ); | |||||
| assertClassFound( classLoader2, EXTN_CLASS_NAME, extnClassLoader ); | |||||
| assertResourcesFound( classLoader2, EXTN_RES_NAME, extnJarFile ); | |||||
| } | |||||
| /** | |||||
| * Tests detection of dependency cycles in extensions. | |||||
| */ | |||||
| public void testCycle() throws Exception | |||||
| { | |||||
| final File jarFile = getTestResource( "ext/cycle-extension-1.jar" ); | |||||
| try | |||||
| { | |||||
| m_loaderManager.getClassLoader( jarFile ); | |||||
| fail(); | |||||
| } | |||||
| catch( final ClassLoaderException e ) | |||||
| { | |||||
| final Resources rez = getResourcesForTested( DefaultClassLoaderManager.class ); | |||||
| final String[] messages = { | |||||
| rez.getString( "create-classloader-for-file.error", jarFile ), | |||||
| rez.getString( "dependency-cycle.error", jarFile ) | |||||
| }; | |||||
| assertSameMessage( messages, e ); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * add some classes to common loader only. | |||||
| * | |||||
| * unknown extension | |||||
| * multiple versions of extensions | |||||
| * extn with requirement on itself | |||||
| * | |||||
| * jar with 1 and 2 extns: | |||||
| * class/resources in parent | |||||
| * class/resources in jar | |||||
| * class/resources in extn | |||||
| * class/resources in all | |||||
| * | |||||
| * jar with transitive extn | |||||
| * class/resources in 2nd extn | |||||
| * | |||||
| * jar with transitive extn + explicit extn on same jar | |||||
| * class/resources in 2nd extn | |||||
| * | |||||
| * Same classes: | |||||
| * get extn explicitly and implicitly, and check classes are the same | |||||
| * extn shared by 2 jars, using same extn and different extns | |||||
| * classes in common classloader, shared by 2 jars | |||||
| * | |||||
| * multiple files: | |||||
| * fetch classloader twice | |||||
| * different path ordering | |||||
| * | |||||
| * tools.jar | |||||
| */ | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| Extension-Name: test.cycle1 | |||||
| Specification-Title: Test Extension | |||||
| Specification-Version: 1.0.0 | |||||
| Specification-Vendor: Jakarta Apache | |||||
| Implementation-Vendor-Id: org.apache.myrmidon | |||||
| Implementation-Vendor: Apache Myrmidon Project | |||||
| Implementation-Version: 3.0 | |||||
| Extension-List: cycle2 | |||||
| cycle2-Extension-Name: test.cycle2 | |||||
| cycle2-Specification-Version: 1.0 | |||||
| @@ -0,0 +1,10 @@ | |||||
| Extension-Name: test.cycle2 | |||||
| Specification-Title: Test Extension | |||||
| Specification-Version: 1.0.0 | |||||
| Specification-Vendor: Jakarta Apache | |||||
| Implementation-Vendor-Id: org.apache.myrmidon | |||||
| Implementation-Vendor: Apache Myrmidon Project | |||||
| Implementation-Version: 1.709.2 | |||||
| Extension-List: cycle1 | |||||
| cycle1-Extension-Name: test.cycle1 | |||||
| cycle1-Specification-Version: 1.0 | |||||
| @@ -0,0 +1,21 @@ | |||||
| /* | |||||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||||
| * | |||||
| * This software is published under the terms of the Apache Software License | |||||
| * version 1.1, a copy of which has been included with this distribution in | |||||
| * the LICENSE.txt file. | |||||
| */ | |||||
| package org.apache.myrmidon.components.classloader.test.libs.extn; | |||||
| import org.apache.myrmidon.components.classloader.test.libs.shared.SharedClass; | |||||
| /** | |||||
| * A test class loaded from an extension. | |||||
| * | |||||
| * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
| * @version $Revision$ $Date$ | |||||
| */ | |||||
| public class ExtnClass | |||||
| { | |||||
| public SharedClass m_test = new SharedClass(); | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| A test resource loaded from an extension. | |||||
| @@ -0,0 +1,3 @@ | |||||
| Extension-List: extension1 | |||||
| extension1-Extension-Name: test.simple | |||||
| extension1-Specification-Version: 1.0 | |||||
| @@ -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.txt file. | |||||
| */ | |||||
| package org.apache.myrmidon.components.classloader.test.libs.shared; | |||||
| /** | |||||
| * A test class. | |||||
| * | |||||
| * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
| * @version $Revision$ $Date$ | |||||
| */ | |||||
| public class SharedClass | |||||
| { | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| A shared resource. | |||||
| @@ -0,0 +1,7 @@ | |||||
| Extension-Name: test.simple | |||||
| Specification-Title: Test Simple Extension | |||||
| Specification-Version: 1.0.0 | |||||
| Specification-Vendor: Jakarta Apache | |||||
| Implementation-Vendor-Id: org.apache.myrmidon | |||||
| Implementation-Vendor: Apache Myrmidon Project | |||||
| Implementation-Version: 1.0.2 | |||||
| @@ -0,0 +1,21 @@ | |||||
| /* | |||||
| * Copyright (C) The Apache Software Foundation. All rights reserved. | |||||
| * | |||||
| * This software is published under the terms of the Apache Software License | |||||
| * version 1.1, a copy of which has been included with this distribution in | |||||
| * the LICENSE.txt file. | |||||
| */ | |||||
| package org.apache.myrmidon.components.classloader.test.libs.unshared; | |||||
| import org.apache.myrmidon.components.classloader.test.libs.shared.SharedClass; | |||||
| /** | |||||
| * A test class. | |||||
| * | |||||
| * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
| * @version $Revision$ $Date$ | |||||
| */ | |||||
| public class UnsharedClass | |||||
| { | |||||
| public SharedClass m_test = new SharedClass(); | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| An unshared resource. | |||||
| @@ -9,8 +9,10 @@ package org.apache.myrmidon.components.embeddor.test; | |||||
| import java.io.File; | import java.io.File; | ||||
| import org.apache.avalon.framework.parameters.Parameters; | import org.apache.avalon.framework.parameters.Parameters; | ||||
| import org.apache.avalon.framework.logger.Logger; | |||||
| import org.apache.myrmidon.AbstractProjectTest; | import org.apache.myrmidon.AbstractProjectTest; | ||||
| import org.apache.myrmidon.LogMessageTracker; | import org.apache.myrmidon.LogMessageTracker; | ||||
| import org.apache.myrmidon.components.embeddor.DefaultEmbeddor; | |||||
| import org.apache.myrmidon.interfaces.embeddor.Embeddor; | import org.apache.myrmidon.interfaces.embeddor.Embeddor; | ||||
| import org.apache.myrmidon.interfaces.model.Project; | import org.apache.myrmidon.interfaces.model.Project; | ||||
| import org.apache.myrmidon.interfaces.model.Target; | import org.apache.myrmidon.interfaces.model.Target; | ||||
| @@ -26,11 +28,50 @@ import org.apache.myrmidon.listeners.ProjectListener; | |||||
| public class DefaultEmbeddorTest | public class DefaultEmbeddorTest | ||||
| extends AbstractProjectTest | extends AbstractProjectTest | ||||
| { | { | ||||
| private DefaultEmbeddor m_embeddor; | |||||
| public DefaultEmbeddorTest( String name ) | public DefaultEmbeddorTest( String name ) | ||||
| { | { | ||||
| super( name ); | super( name ); | ||||
| } | } | ||||
| /** | |||||
| * Tear-down the test. | |||||
| */ | |||||
| protected void tearDown() throws Exception | |||||
| { | |||||
| if( m_embeddor != null ) | |||||
| { | |||||
| m_embeddor.dispose(); | |||||
| m_embeddor = null; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Returns an embeddor which can be used to build and execute projects. | |||||
| */ | |||||
| protected Embeddor getEmbeddor() throws Exception | |||||
| { | |||||
| if( m_embeddor == null ) | |||||
| { | |||||
| // Need to set the context classloader - The default embeddor uses it | |||||
| Thread.currentThread().setContextClassLoader( getClass().getClassLoader() ); | |||||
| final Logger logger = getLogger(); | |||||
| m_embeddor = new DefaultEmbeddor(); | |||||
| m_embeddor.enableLogging( logger ); | |||||
| final Parameters params = new Parameters(); | |||||
| final File instDir = getInstallDirectory(); | |||||
| params.setParameter( "myrmidon.home", instDir.getAbsolutePath() ); | |||||
| m_embeddor.parameterize( params ); | |||||
| m_embeddor.initialize(); | |||||
| m_embeddor.start(); | |||||
| } | |||||
| return m_embeddor; | |||||
| } | |||||
| /** | /** | ||||
| * Tests that a project is successfully built from a file. | * Tests that a project is successfully built from a file. | ||||
| */ | */ | ||||
| @@ -63,6 +104,7 @@ public class DefaultEmbeddorTest | |||||
| public void testCreateListener() throws Exception | public void testCreateListener() throws Exception | ||||
| { | { | ||||
| final ProjectListener listener = getEmbeddor().createListener( "default" ); | final ProjectListener listener = getEmbeddor().createListener( "default" ); | ||||
| assertNotNull( listener ); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -524,23 +524,12 @@ public class MyrmidonSecurityManager | |||||
| <code><socket></code> | <code><socket></code> | ||||
| conditions to an antlib. Need to resolve how these will be passed a logger. | conditions to an antlib. Need to resolve how these will be passed a logger. | ||||
| </li> | </li> | ||||
| <li>Make the | |||||
| <code><uptodate></code> task a condition, and move to | |||||
| an antlib. | |||||
| </li> | |||||
| <li>Split up | |||||
| <code><is-set></code> condition into is-set and is-true conditions. | |||||
| </li> | |||||
| <li>Allow the | <li>Allow the | ||||
| <code><if></code> task to take any condition implementation. | <code><if></code> task to take any condition implementation. | ||||
| </li> | </li> | ||||
| <li>Add an else block to the | <li>Add an else block to the | ||||
| <code><if></code> task. | <code><if></code> task. | ||||
| </li> | </li> | ||||
| <li>Split the | |||||
| <code><available></code> condition into separate conditions | |||||
| that test for the availability of a class, or a resource. | |||||
| </li> | |||||
| <li>Move | <li>Move | ||||
| <code>crimson.jar</code> to | <code>crimson.jar</code> to | ||||
| <code>bin/lib</code> in the distribution, | <code>bin/lib</code> in the distribution, | ||||
| @@ -550,6 +539,21 @@ public class MyrmidonSecurityManager | |||||
| <li>Add a <code>--type</code> command-line option, to allow | <li>Add a <code>--type</code> command-line option, to allow | ||||
| the project builder to be manually selected. | the project builder to be manually selected. | ||||
| </li> | </li> | ||||
| <li>Change <code>ProjectBuilder</code> | |||||
| and <code>Embeddor</code> to throw something more | |||||
| specialised than Exception. | |||||
| </li> | |||||
| <li>Change <code>DefaultClassLoaderManager</code> to handle | |||||
| directories as part of a library classpath. | |||||
| </li> | |||||
| <li><code><condition></code> should set the property | |||||
| value to <code>false</code> when the condition is false.</li> | |||||
| <li>Split the <code><uptodate></code> condition into | |||||
| a condition that checks against a single target file, | |||||
| and one which checks using a destdir/mapper.</li> | |||||
| <li>Add a task to unset a property.</li> | |||||
| <li>Change the various def and import task to allow a classpath | |||||
| to be provided.</li> | |||||
| <li>Unit tests.</li> | <li>Unit tests.</li> | ||||
| </ul> | </ul> | ||||