diff --git a/bootstrap/build b/bootstrap/build
new file mode 100755
index 0000000000..273130fb97
--- /dev/null
+++ b/bootstrap/build
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+buildDir=target
+classesDir=${buildDir}/classes
+srcDir=src/main/java
+
+rm -rf ${buildDir} > /dev/null 2>&1
+
+mkdir -p ${classesDir}
+
+"$JAVA_HOME/bin/javac" -g -d ${classesDir} `find ${srcDir} -name '*.java'`
+
+( cd ${classesDir} ; "$JAVA_HOME/bin/jar" -cfm ../bootstrap.jar ../../src/main/resources/META-INF/MANIFEST.MF * )
diff --git a/bootstrap/build.bat b/bootstrap/build.bat
new file mode 100644
index 0000000000..84b5934108
--- /dev/null
+++ b/bootstrap/build.bat
@@ -0,0 +1,19 @@
+@echo off
+
+set buildDir=target
+set classesDir=%buildDir%\classes
+set srcDir=src\main\java
+
+if exist %classesDir% rmdir /S/Q %buildDir%
+if exist %buildDir% rmdir /S/Q %buildDir%
+
+mkdir %buildDir%
+mkdir %classesDir%
+
+dir /B /s %srcDir%\*.java >sources
+"%JAVA_HOME%\bin\javac" -d %classesDir% @sources
+del /F/Q sources
+
+cd %classesDir%
+"%JAVA_HOME%\bin\jar" -cfm ..\bootstrap.jar ..\..\src\main\resources\META-INF\MANIFEST.MF *.*
+cd ..\..
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/Bootstrap.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/Bootstrap.java
new file mode 100644
index 0000000000..9dcf4ce4ce
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/Bootstrap.java
@@ -0,0 +1,641 @@
+package org.apache.maven.bootstrap;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.bootstrap.compile.CompilerConfiguration;
+import org.apache.maven.bootstrap.compile.JavacCompiler;
+import org.apache.maven.bootstrap.download.ArtifactResolver;
+import org.apache.maven.bootstrap.download.OfflineArtifactResolver;
+import org.apache.maven.bootstrap.download.OnlineArtifactDownloader;
+import org.apache.maven.bootstrap.model.Dependency;
+import org.apache.maven.bootstrap.model.ModelReader;
+import org.apache.maven.bootstrap.model.Plugin;
+import org.apache.maven.bootstrap.model.Repository;
+import org.apache.maven.bootstrap.settings.Mirror;
+import org.apache.maven.bootstrap.settings.Proxy;
+import org.apache.maven.bootstrap.settings.Settings;
+import org.apache.maven.bootstrap.util.FileUtils;
+import org.apache.maven.bootstrap.util.IsolatedClassLoader;
+import org.apache.maven.bootstrap.util.JarMojo;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Main class for bootstrap module.
+ *
+ * @author Brett Porter
+ * @version $Id$
+ */
+public class Bootstrap
+{
+ private static final String MODELLO_PLUGIN_ID = "org.codehaus.modello:modello-maven-plugin";
+
+ private Set inProgress = new HashSet();
+
+ private Map modelFileCache = new HashMap();
+
+ public static void main( String[] args )
+ throws Exception
+ {
+ Bootstrap bootstrap = new Bootstrap();
+
+ bootstrap.run( args );
+ }
+
+ private static File getSettingsPath( String userHome, String[] args )
+ throws Exception
+ {
+ for ( int i = 0; i < args.length; i++ )
+ {
+ if ( args[i].equals( "-s" ) || args[i].equals( "--settings" ) )
+ {
+ if ( i == args.length - 1 )
+ {
+ throw new Exception( "missing argument to -s" );
+ }
+ return new File( args[i + 1] );
+ }
+ }
+ return new File( userHome, ".m2/settings.xml" );
+ }
+
+ private void run( String[] args )
+ throws Exception
+ {
+ Date fullStart = new Date();
+
+ String userHome = System.getProperty( "user.home" );
+
+ File settingsXml = getSettingsPath( userHome, args );
+
+ System.out.println( "Using settings from " + settingsXml );
+
+ Settings settings = Settings.read( userHome, settingsXml );
+
+ // TODO: have an alternative implementation of ArtifactDownloader for source compiles
+ // - if building from source, checkout and build then resolve to built jar (still download POM?)
+ ArtifactResolver resolver = setupRepositories( settings );
+
+ String basedir = System.getProperty( "user.dir" );
+
+ // TODO: only build this guy, then move the next part to a new phase using it for resolution
+ // Root POM
+// buildProject( basedir, "", resolver, false );
+// buildProject( basedir, "maven-artifact-manager", resolver );
+
+ // Pre-cache models so we know where they are for dependencies
+ cacheModels( new File( basedir ), resolver );
+
+ buildProject( new File( basedir ), resolver, true );
+
+ stats( fullStart, new Date() );
+ }
+
+ private void cacheModels( File basedir, ArtifactResolver resolver )
+ throws IOException, ParserConfigurationException, SAXException
+ {
+ ModelReader reader = readModel( resolver, new File( basedir, "pom.xml" ), false );
+
+ for ( Iterator i = reader.getModules().iterator(); i.hasNext(); )
+ {
+ String module = (String) i.next();
+
+ cacheModels( new File( basedir, module ), resolver );
+ }
+ }
+
+ private void buildProject( File basedir, ArtifactResolver resolver, boolean buildModules )
+ throws Exception
+ {
+ System.setProperty( "basedir", basedir.getAbsolutePath() );
+
+ File file = new File( basedir, "pom.xml" );
+
+ ModelReader reader = readModel( resolver, file, true );
+
+ String key = reader.getGroupId() + ":" + reader.getArtifactId() + ":" + reader.getPackaging();
+ if ( inProgress.contains( key ) )
+ {
+ return;
+ }
+
+ if ( reader.getPackaging().equals( "pom" ) )
+ {
+ if ( buildModules )
+ {
+ for ( Iterator i = reader.getModules().iterator(); i.hasNext(); )
+ {
+ String module = (String) i.next();
+
+ buildProject( new File( basedir, module ), resolver, true );
+ }
+ }
+
+ return;
+ }
+
+ inProgress.add( key );
+
+ if ( resolver.isAlreadyBuilt( key ) )
+ {
+ return;
+ }
+
+ String sources = new File( basedir, "src/main/java" ).getAbsolutePath();
+
+ String resources = new File( basedir, "src/main/resources" ).getAbsolutePath();
+
+ String classes = new File( basedir, "target/classes" ).getAbsolutePath();
+
+ File buildDirFile = new File( basedir, "target" );
+ String buildDir = buildDirFile.getAbsolutePath();
+
+ System.out.println( "Analysing dependencies ..." );
+
+ for ( Iterator i = reader.getDependencies().iterator(); i.hasNext(); )
+ {
+ Dependency dep = (Dependency) i.next();
+
+ if ( modelFileCache.containsKey( dep.getId() ) )
+ {
+ buildProject( resolver.getArtifactFile( dep.getPomDependency() ).getParentFile(), resolver, false );
+ }
+ }
+
+ resolver.downloadDependencies( reader.getDependencies() );
+
+ System.out.println();
+ System.out.println();
+ System.out.println( "Building project in " + basedir );
+
+ line();
+
+ // clean
+ System.out.println( "Cleaning " + buildDirFile + "..." );
+ FileUtils.forceDelete( buildDirFile );
+
+ // ----------------------------------------------------------------------
+ // Generate sources - modello
+ // ----------------------------------------------------------------------
+
+ File generatedSourcesDirectory = null;
+ if ( reader.getPlugins().containsKey( MODELLO_PLUGIN_ID ) )
+ {
+ Plugin plugin = (Plugin) reader.getPlugins().get( MODELLO_PLUGIN_ID );
+
+ File model = new File( basedir, (String) plugin.getConfiguration().get( "model" ) );
+
+ System.out.println( "Model exists!" );
+
+ String modelVersion = (String) plugin.getConfiguration().get( "version" );
+ if ( modelVersion == null || modelVersion.trim().length() < 1 )
+ {
+ System.out.println( "No model version configured. Using \'1.0.0\'..." );
+ modelVersion = "1.0.0";
+ }
+
+ generatedSourcesDirectory = new File( basedir, "target/generated-sources/modello" );
+
+ if ( !generatedSourcesDirectory.exists() )
+ {
+ generatedSourcesDirectory.mkdirs();
+ }
+
+ File artifactFile = resolver.getArtifactFile( plugin.asDependencyPom() );
+ ModelReader pluginReader = readModel( resolver, artifactFile, true );
+
+ ClassLoader classLoader =
+ createClassloaderFromDependencies( pluginReader.getDependencies(), null, resolver );
+
+ System.out.println( "Generating model bindings for version \'" + modelVersion + "\' from '" + model + "'" );
+
+ generateModelloSources( model.getAbsolutePath(), "java", generatedSourcesDirectory, modelVersion, "false",
+ classLoader );
+ generateModelloSources( model.getAbsolutePath(), "xpp3-reader", generatedSourcesDirectory, modelVersion,
+ "false", classLoader );
+ generateModelloSources( model.getAbsolutePath(), "xpp3-writer", generatedSourcesDirectory, modelVersion,
+ "false", classLoader );
+ }
+
+ // ----------------------------------------------------------------------
+ // Standard compile
+ // ----------------------------------------------------------------------
+
+ System.out.println( "Compiling sources ..." );
+
+ compile( reader.getDependencies(), sources, classes, null, generatedSourcesDirectory, Dependency.SCOPE_COMPILE,
+ resolver );
+
+ // ----------------------------------------------------------------------
+ // Standard resources
+ // ----------------------------------------------------------------------
+
+ System.out.println( "Packaging resources ..." );
+
+ copyResources( resources, classes );
+
+ // ----------------------------------------------------------------------
+ // Create JAR
+ // ----------------------------------------------------------------------
+
+ File jarFile = createJar( new File( basedir, "pom.xml" ), classes, buildDir, reader );
+
+ System.out.println( "Packaging " + jarFile + " ..." );
+
+ resolver.addBuiltArtifact( reader.getGroupId(), reader.getArtifactId(), "jar", jarFile );
+
+ line();
+
+ inProgress.remove( key );
+ }
+
+ private ModelReader readModel( ArtifactResolver resolver, File file, boolean resolveTransitiveDependencies )
+ throws ParserConfigurationException, SAXException, IOException
+ {
+ ModelReader reader = new ModelReader( resolver, resolveTransitiveDependencies );
+
+ reader.parse( file );
+
+ resolver.addBuiltArtifact( reader.getGroupId(), reader.getArtifactId(), "pom", file );
+
+ modelFileCache.put( reader.getGroupId() + ":" + reader.getArtifactId(), file );
+
+ return reader;
+ }
+
+ private void line()
+ {
+ System.out.println( "------------------------------------------------------------------" );
+ }
+
+ private File createJar( File pomFile, String classes, String buildDir, ModelReader reader )
+ throws Exception
+ {
+ JarMojo jarMojo = new JarMojo();
+
+ String artifactId = reader.getArtifactId();
+
+ String version = reader.getVersion();
+
+ // ----------------------------------------------------------------------
+ // Create pom.properties file
+ // ----------------------------------------------------------------------
+
+ Properties p = new Properties();
+
+ p.setProperty( "groupId", reader.getGroupId() );
+
+ p.setProperty( "artifactId", reader.getArtifactId() );
+
+ p.setProperty( "version", reader.getVersion() );
+
+ File pomPropertiesDir =
+ new File( new File( classes ), "META-INF/maven/" + reader.getGroupId() + "/" + reader.getArtifactId() );
+
+ pomPropertiesDir.mkdirs();
+
+ File pomPropertiesFile = new File( pomPropertiesDir, "pom.properties" );
+
+ OutputStream os = new FileOutputStream( pomPropertiesFile );
+
+ p.store( os, "Generated by Maven" );
+
+ os.close(); // stream is flushed but not closed by Properties.store()
+
+ FileUtils.copyFile( pomFile, new File( pomPropertiesDir, "pom.xml" ) );
+
+ File jarFile = new File( buildDir, artifactId + "-" + version + ".jar" );
+ jarMojo.execute( new File( classes ), jarFile );
+
+ return jarFile;
+ }
+
+ public String getCurrentUtcDate()
+ {
+ TimeZone timezone = TimeZone.getTimeZone( "UTC" );
+ DateFormat fmt = new SimpleDateFormat( "yyyyMMddHHmmss" );
+ fmt.setTimeZone( timezone );
+ return fmt.format( new Date() );
+ }
+
+ private void copyResources( String sourceDirectory, String destinationDirectory )
+ throws Exception
+ {
+ File sd = new File( sourceDirectory );
+
+ if ( !sd.exists() )
+ {
+ return;
+ }
+
+ List files = FileUtils.getFiles( sd, "**/**", "**/CVS/**,**/.svn/**", false );
+
+ for ( Iterator i = files.iterator(); i.hasNext(); )
+ {
+ File f = (File) i.next();
+
+ File source = new File( sourceDirectory, f.getPath() );
+
+ File dest = new File( destinationDirectory, f.getPath() );
+
+ if ( !dest.getParentFile().exists() )
+ {
+ dest.getParentFile().mkdirs();
+ }
+
+ FileUtils.copyFile( source, dest );
+ }
+ }
+
+ private static ArtifactResolver setupRepositories( Settings settings )
+ throws Exception
+ {
+ boolean online = true;
+
+ String onlineProperty = System.getProperty( "maven.online" );
+
+ if ( onlineProperty != null && onlineProperty.equals( "false" ) )
+ {
+ online = false;
+ }
+
+ Repository localRepository =
+ new Repository( "local", settings.getLocalRepository(), Repository.LAYOUT_DEFAULT, false, false );
+
+ File repoLocalFile = new File( localRepository.getBasedir() );
+ repoLocalFile.mkdirs();
+
+ if ( !repoLocalFile.canWrite() )
+ {
+ throw new Exception( "Can't write to " + repoLocalFile );
+ }
+
+ ArtifactResolver resolver;
+ if ( online )
+ {
+ OnlineArtifactDownloader downloader = new OnlineArtifactDownloader( localRepository );
+ resolver = downloader;
+ if ( settings.getActiveProxy() != null )
+ {
+ Proxy proxy = settings.getActiveProxy();
+ downloader.setProxy( proxy.getHost(), proxy.getPort(), proxy.getUserName(), proxy.getPassword() );
+ }
+
+ List remoteRepos = downloader.getRemoteRepositories();
+ List newRemoteRepos = new ArrayList();
+
+ for ( Iterator i = remoteRepos.iterator(); i.hasNext(); )
+ {
+ Repository repo = (Repository) i.next();
+
+ boolean foundMirror = false;
+ for ( Iterator j = settings.getMirrors().iterator(); j.hasNext() && !foundMirror; )
+ {
+ Mirror m = (Mirror) j.next();
+ if ( m.getMirrorOf().equals( repo.getId() ) )
+ {
+ newRemoteRepos.add( new Repository( m.getId(), m.getUrl(), repo.getLayout(), repo.isSnapshots(),
+ repo.isReleases() ) );
+ foundMirror = true;
+ }
+ }
+ if ( !foundMirror )
+ {
+ newRemoteRepos.add( repo );
+ }
+ }
+
+ downloader.setRemoteRepositories( newRemoteRepos );
+
+ System.out.println( "Using the following for your local repository: " + localRepository );
+ System.out.println( "Using the following for your remote repository: " + newRemoteRepos );
+ }
+ else
+ {
+ resolver = new OfflineArtifactResolver( localRepository );
+ }
+
+ return resolver;
+ }
+
+ protected static String formatTime( long ms )
+ {
+ long secs = ms / 1000;
+
+ long min = secs / 60;
+ secs = secs % 60;
+
+ if ( min > 0 )
+ {
+ return min + " minutes " + secs + " seconds";
+ }
+ else
+ {
+ return secs + " seconds";
+ }
+ }
+
+ private void stats( Date fullStart, Date fullStop )
+ {
+ long fullDiff = fullStop.getTime() - fullStart.getTime();
+
+ System.out.println( "Total time: " + formatTime( fullDiff ) );
+
+ System.out.println( "Finished at: " + fullStop );
+ }
+
+ private void compile( Collection dependencies, String sourceDirectory, String outputDirectory,
+ String extraClasspath, File generatedSources, String scope, ArtifactResolver resolver )
+ throws Exception
+ {
+ JavacCompiler compiler = new JavacCompiler();
+
+ String[] sourceDirectories = null;
+
+ if ( generatedSources != null )
+ {
+ // We might only have generated sources
+
+ if ( new File( sourceDirectory ).exists() )
+ {
+ sourceDirectories = new String[]{sourceDirectory, generatedSources.getAbsolutePath()};
+ }
+ else
+ {
+ sourceDirectories = new String[]{generatedSources.getAbsolutePath()};
+ }
+ }
+ else
+ {
+ if ( new File( sourceDirectory ).exists() )
+ {
+ sourceDirectories = new String[]{sourceDirectory};
+ }
+ }
+
+ if ( sourceDirectories != null )
+ {
+ CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
+ compilerConfiguration.setOutputLocation( outputDirectory );
+ List classpathEntries = classpath( dependencies, extraClasspath, scope, resolver );
+ compilerConfiguration.setNoWarn( true );
+ compilerConfiguration.setClasspathEntries( classpathEntries );
+ compilerConfiguration.setSourceLocations( Arrays.asList( sourceDirectories ) );
+
+ /* Compile with debugging info */
+ String debugAsString = System.getProperty( "maven.compiler.debug", "true" );
+
+ if ( !Boolean.valueOf( debugAsString ).booleanValue() )
+ {
+ compilerConfiguration.setDebug( false );
+ }
+ else
+ {
+ compilerConfiguration.setDebug( true );
+ }
+
+ List messages = compiler.compile( compilerConfiguration );
+
+ for ( Iterator i = messages.iterator(); i.hasNext(); )
+ {
+ System.out.println( i.next() );
+ }
+
+ if ( messages.size() > 0 )
+ {
+ throw new Exception( "Compilation error." );
+ }
+ }
+ }
+
+ private List classpath( Collection dependencies, String extraClasspath, String scope, ArtifactResolver resolver )
+ {
+ List classpath = new ArrayList( dependencies.size() + 1 );
+
+ for ( Iterator i = dependencies.iterator(); i.hasNext(); )
+ {
+ Dependency d = (Dependency) i.next();
+
+ String element = resolver.getArtifactFile( d ).getAbsolutePath();
+
+ if ( Dependency.SCOPE_COMPILE.equals( scope ) )
+ {
+ if ( d.getScope().equals( Dependency.SCOPE_COMPILE ) )
+ {
+ classpath.add( element );
+ }
+ }
+ else if ( Dependency.SCOPE_RUNTIME.equals( scope ) )
+ {
+ if ( d.getScope().equals( Dependency.SCOPE_COMPILE ) ||
+ d.getScope().equals( Dependency.SCOPE_RUNTIME ) )
+ {
+ classpath.add( element );
+ }
+ }
+ else if ( Dependency.SCOPE_TEST.equals( scope ) )
+ {
+ classpath.add( element );
+ }
+ }
+
+ if ( extraClasspath != null )
+ {
+ classpath.add( extraClasspath );
+ }
+
+ return classpath;
+ }
+
+ private void generateModelloSources( String model, String mode, File dir, String modelVersion,
+ String packageWithVersion, ClassLoader modelloClassLoader )
+ throws Exception
+ {
+ Class c = modelloClassLoader.loadClass( "org.codehaus.modello.ModelloCli" );
+
+ Object generator = c.newInstance();
+
+ Method m = c.getMethod( "main", new Class[]{String[].class} );
+
+ String[] args = new String[]{model, mode, dir.getAbsolutePath(), modelVersion, packageWithVersion};
+
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+
+ Thread.currentThread().setContextClassLoader( modelloClassLoader );
+
+ m.invoke( generator, new Object[]{args} );
+
+ Thread.currentThread().setContextClassLoader( old );
+ }
+
+ private IsolatedClassLoader createClassloaderFromDependencies( Collection dependencies, ClassLoader parent,
+ ArtifactResolver resolver )
+ throws Exception
+ {
+ System.out.println( "Checking for dependencies ..." );
+
+ resolver.downloadDependencies( dependencies );
+
+ IsolatedClassLoader cl;
+ if ( parent == null )
+ {
+ cl = new IsolatedClassLoader();
+ }
+ else
+ {
+ cl = new IsolatedClassLoader( parent );
+ }
+
+ for ( Iterator i = dependencies.iterator(); i.hasNext(); )
+ {
+ Dependency dependency = (Dependency) i.next();
+
+ File f = resolver.getArtifactFile( dependency );
+ if ( !f.exists() )
+ {
+ String msg =
+ ( !resolver.isOnline() ? "; run again online" : "; there was a problem downloading it earlier" );
+ throw new FileNotFoundException( "Missing dependency: " + dependency + msg );
+ }
+
+ cl.addURL( f.toURL() );
+ }
+
+ return cl;
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/AbstractCompiler.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/AbstractCompiler.java
new file mode 100644
index 0000000000..d1fb92912d
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/AbstractCompiler.java
@@ -0,0 +1,186 @@
+package org.apache.maven.bootstrap.compile;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.bootstrap.util.DirectoryScanner;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Jason van Zyl
+ * @author Michal Maczka
+ * @version $Id$
+ */
+public abstract class AbstractCompiler
+ implements Compiler
+{
+ private static String PS = System.getProperty( "path.separator" );
+
+ public String getPathString( List pathElements )
+ throws Exception
+ {
+ StringBuffer sb = new StringBuffer();
+
+ for ( Iterator it = pathElements.iterator(); it.hasNext(); )
+ {
+ String element = (String) it.next();
+
+ sb.append( element ).append( PS );
+ }
+
+ return sb.toString();
+ }
+
+ protected String[] getSourceFiles( CompilerConfiguration config )
+ {
+ Set sources = new HashSet();
+
+ Set sourceFiles = config.getSourceFiles();
+ if ( sourceFiles != null && !sourceFiles.isEmpty() )
+ {
+ for ( Iterator it = sourceFiles.iterator(); it.hasNext(); )
+ {
+ File sourceFile = (File) it.next();
+ sources.add( sourceFile.getAbsolutePath() );
+ }
+ }
+ else
+ {
+ for ( Iterator it = config.getSourceLocations().iterator(); it.hasNext(); )
+ {
+ String sourceLocation = (String) it.next();
+
+ DirectoryScanner scanner = new DirectoryScanner();
+
+ scanner.setBasedir( sourceLocation );
+
+ Set includes = config.getIncludes();
+ if ( includes != null && !includes.isEmpty() )
+ {
+ String[] inclStrs = (String[]) includes.toArray( new String[includes.size()] );
+ scanner.setIncludes( inclStrs );
+ }
+ else
+ {
+ scanner.setIncludes( new String[]{"**/*.java"} );
+ }
+
+ Set excludes = config.getExcludes();
+ if ( excludes != null && !excludes.isEmpty() )
+ {
+ String[] exclStrs = (String[]) excludes.toArray( new String[excludes.size()] );
+ scanner.setIncludes( exclStrs );
+ }
+
+ scanner.scan();
+
+ String[] sourceDirectorySources = scanner.getIncludedFiles();
+
+ for ( int j = 0; j < sourceDirectorySources.length; j++ )
+ {
+ File f = new File( sourceLocation, sourceDirectorySources[j] );
+
+ sources.add( f.getPath() );
+ }
+ }
+ }
+
+ String[] result = null;
+
+ if ( sources.isEmpty() )
+ {
+ result = new String[0];
+ }
+ else
+ {
+ result = (String[]) sources.toArray( new String[sources.size()] );
+ }
+
+ return result;
+ }
+
+ protected String makeClassName( String fileName, String sourceDir )
+ throws IOException
+ {
+ File origFile = new File( fileName );
+ String canonical = null;
+
+ if ( origFile.exists() )
+ {
+ canonical = origFile.getCanonicalPath().replace( '\\', '/' );
+ }
+
+ String str = fileName;
+ str = str.replace( '\\', '/' );
+
+ if ( sourceDir != null )
+ {
+ String prefix = new File( sourceDir ).getCanonicalPath().replace( '\\', '/' );
+
+ if ( canonical != null )
+ {
+ if ( canonical.startsWith( prefix ) )
+ {
+ String result = canonical.substring( prefix.length() + 1, canonical.length() - 5 );
+
+ result = result.replace( '/', '.' );
+
+ return result;
+ }
+ }
+ else
+ {
+ File t = new File( sourceDir, fileName );
+
+ if ( t.exists() )
+ {
+ str = t.getCanonicalPath().replace( '\\', '/' );
+
+ String result = str.substring( prefix.length() + 1, str.length() - 5 ).replace( '/', '.' );
+
+ return result;
+ }
+ }
+ }
+
+ if ( fileName.endsWith( ".java" ) )
+ {
+ fileName = fileName.substring( 0, fileName.length() - 5 );
+ }
+
+ fileName = fileName.replace( '\\', '.' );
+
+ return fileName.replace( '/', '.' );
+ }
+
+ protected String[] toStringArray( List arguments )
+ {
+ String[] args = new String[arguments.size()];
+
+ for ( int i = 0; i < arguments.size(); i++ )
+ {
+ args[i] = (String) arguments.get( i );
+ }
+
+ return args;
+ }
+}
\ No newline at end of file
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/Compiler.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/Compiler.java
new file mode 100644
index 0000000000..85799a1b1d
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/Compiler.java
@@ -0,0 +1,35 @@
+package org.apache.maven.bootstrap.compile;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.List;
+
+/**
+ *
+ *
+ * @author Jason van Zyl
+ *
+ * @version $Id$
+ */
+public interface Compiler
+{
+ static String ROLE = Compiler.class.getName();
+
+ List compile( CompilerConfiguration configuration )
+ throws Exception;
+}
+
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/CompilerConfiguration.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/CompilerConfiguration.java
new file mode 100644
index 0000000000..f244d2610e
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/CompilerConfiguration.java
@@ -0,0 +1,159 @@
+package org.apache.maven.bootstrap.compile;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * @author jdcasey
+ */
+public class CompilerConfiguration
+{
+
+ private String outputLocation;
+
+ private List classpathEntries = new LinkedList();
+
+ private List sourceLocations = new LinkedList();
+
+ private Set includes = new HashSet();
+ private Set excludes = new HashSet();
+
+ private Map compilerOptions = new TreeMap();
+
+ private boolean debug = false;
+
+ private Set sourceFiles = new HashSet();
+
+ private boolean noWarn;
+
+ public void setSourceFiles(Set sourceFiles)
+ {
+ this.sourceFiles = sourceFiles;
+ }
+
+ public Set getSourceFiles()
+ {
+ return sourceFiles;
+ }
+
+ public void setOutputLocation(String outputLocation)
+ {
+ this.outputLocation = outputLocation;
+ }
+
+ public String getOutputLocation()
+ {
+ return outputLocation;
+ }
+
+ public void addClasspathEntry(String classpathEntry)
+ {
+ this.classpathEntries.add(classpathEntry);
+ }
+
+ public void setClasspathEntries(List classpathEntries) {
+ this.classpathEntries = new LinkedList(classpathEntries);
+ }
+
+ public List getClasspathEntries() {
+ return Collections.unmodifiableList(classpathEntries);
+ }
+
+ public void addSourceLocation(String sourceLocation) {
+ this.sourceLocations.add(sourceLocation);
+ }
+
+ public void setSourceLocations(List sourceLocations) {
+ this.sourceLocations = new LinkedList(sourceLocations);
+ }
+
+ public List getSourceLocations() {
+ return Collections.unmodifiableList(sourceLocations);
+ }
+
+ public void addInclude(String include) {
+ this.includes.add(include);
+ }
+
+ public void setIncludes(Set includes) {
+ this.includes = new HashSet(includes);
+ }
+
+ public Set getIncludes() {
+ return Collections.unmodifiableSet(includes);
+ }
+
+ public void addExclude(String exclude) {
+ this.excludes.add(exclude);
+ }
+
+ public void setExcludes(Set excludes) {
+ this.excludes = new HashSet(excludes);
+ }
+
+ public Set getExcludes() {
+ return Collections.unmodifiableSet(excludes);
+ }
+
+ public void addCompilerOption(String optionName, String optionValue) {
+ this.compilerOptions.put(optionName, optionValue);
+ }
+
+ public void setCompilerOptions(Map compilerOptions) {
+ this.compilerOptions = new TreeMap(compilerOptions);
+ }
+
+ public Map getCompilerOptions() {
+ return Collections.unmodifiableMap(compilerOptions);
+ }
+
+ /**
+ * @param debug The debug to set.
+ */
+ public void setDebug( boolean debug )
+ {
+ this.debug = debug;
+ }
+
+ /**
+ * Compile with debug info
+ *
+ * @return Returns the debug.
+ */
+ public boolean isDebug()
+ {
+ return debug;
+ }
+
+ public void setNoWarn( boolean noWarn )
+ {
+ this.noWarn = noWarn;
+ }
+
+ public boolean isNoWarn()
+ {
+ return noWarn;
+ }
+
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/CompilerError.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/CompilerError.java
new file mode 100644
index 0000000000..3853d342ea
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/CompilerError.java
@@ -0,0 +1,196 @@
+package org.apache.maven.bootstrap.compile;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This class encapsulates an error message produced by a programming language
+ * processor (whether interpreted or compiled)
+ *
+ * @author Stefano Mazzocchi
+ * @version CVS $Id$
+ * @since 2.0
+ */
+
+public class CompilerError
+{
+ /**
+ * Is this a severe error or a warning?
+ */
+ private boolean error;
+ /**
+ * The start line number of the offending program text
+ */
+ private int startline;
+ /**
+ * The start column number of the offending program text
+ */
+ private int startcolumn;
+ /**
+ * The end line number of the offending program text
+ */
+ private int endline;
+ /**
+ * The end column number of the offending program text
+ */
+ private int endcolumn;
+ /**
+ * The name of the file containing the offending program text
+ */
+ private String file;
+ /**
+ * The actual error text produced by the language processor
+ */
+ private String message;
+
+ /**
+ * The error message constructor.
+ *
+ * @param file The name of the file containing the offending program text
+ * @param error The actual error text produced by the language processor
+ * @param startline The start line number of the offending program text
+ * @param startcolumn The start column number of the offending program text
+ * @param endline The end line number of the offending program text
+ * @param endcolumn The end column number of the offending program text
+ * @param message The actual error text produced by the language processor
+ */
+ public CompilerError(
+ String file,
+ boolean error,
+ int startline,
+ int startcolumn,
+ int endline,
+ int endcolumn,
+ String message
+ )
+ {
+ this.file = file;
+ this.error = error;
+ this.startline = startline;
+ this.startcolumn = startcolumn;
+ this.endline = endline;
+ this.endcolumn = endcolumn;
+ this.message = message;
+ }
+
+ /**
+ * The error message constructor.
+ *
+ * @param message The actual error text produced by the language processor
+ */
+ public CompilerError( String message )
+ {
+ this.message = message;
+ }
+
+ /**
+ * The error message constructor.
+ *
+ * @param message The actual error text produced by the language processor
+ * @param error whether it was an error or informational
+ */
+ public CompilerError( String message, boolean error )
+ {
+ this.message = message;
+ this.error = error;
+ }
+
+ /**
+ * Return the filename associated with this compiler error.
+ *
+ * @return The filename associated with this compiler error
+ */
+ public String getFile()
+ {
+ return file;
+ }
+
+ /**
+ * Assert whether this is a severe error or a warning
+ *
+ * @return Whether the error is severe
+ */
+ public boolean isError()
+ {
+ return error;
+ }
+
+ /**
+ * Return the starting line number of the program text originating this error
+ *
+ * @return The starting line number of the program text originating this error
+ */
+ public int getStartLine()
+ {
+ return startline;
+ }
+
+ /**
+ * Return the starting column number of the program text originating this
+ * error
+ *
+ * @return The starting column number of the program text originating this
+ * error
+ */
+ public int getStartColumn()
+ {
+ return startcolumn;
+ }
+
+ /**
+ * Return the ending line number of the program text originating this error
+ *
+ * @return The ending line number of the program text originating this error
+ */
+ public int getEndLine()
+ {
+ return endline;
+ }
+
+ /**
+ * Return the ending column number of the program text originating this
+ * error
+ *
+ * @return The ending column number of the program text originating this
+ * error
+ */
+ public int getEndColumn()
+ {
+ return endcolumn;
+ }
+
+ /**
+ * Return the message produced by the language processor
+ *
+ * @return The message produced by the language processor
+ */
+ public String getMessage()
+ {
+ return message;
+ }
+
+ public String toString()
+ {
+ if ( file != null )
+ {
+ return file + ":" + "[" + startline + "," + startcolumn + "] " + message;
+ }
+ else
+ {
+ return message;
+ }
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/JavacCompiler.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/JavacCompiler.java
new file mode 100644
index 0000000000..1a8e1eec06
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/compile/JavacCompiler.java
@@ -0,0 +1,254 @@
+package org.apache.maven.bootstrap.compile;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.bootstrap.util.IsolatedClassLoader;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+public class JavacCompiler
+ extends AbstractCompiler
+{
+ static final int OUTPUT_BUFFER_SIZE = 1024;
+
+ public JavacCompiler()
+ {
+ }
+
+ public List compile( CompilerConfiguration config )
+ throws Exception
+ {
+ File destinationDir = new File( config.getOutputLocation() );
+
+ if ( !destinationDir.exists() )
+ {
+ destinationDir.mkdirs();
+ }
+
+ String[] sources = getSourceFiles( config );
+
+ if ( sources.length == 0 )
+ {
+ return Collections.EMPTY_LIST;
+ }
+
+ System.out.println( "Compiling " + sources.length + " source file" + ( sources.length == 1 ? "" : "s" ) +
+ " to " + destinationDir.getAbsolutePath() );
+
+ Map compilerOptions = config.getCompilerOptions();
+
+ List args = new ArrayList( sources.length + 5 + compilerOptions.size() * 2 );
+
+ args.add( "-d" );
+
+ args.add( destinationDir.getAbsolutePath() );
+
+ if ( config.isNoWarn() )
+ {
+ args.add( "-nowarn" );
+ }
+
+ List classpathEntries = config.getClasspathEntries();
+ if ( classpathEntries != null && !classpathEntries.isEmpty() )
+ {
+ args.add( "-classpath" );
+
+ args.add( getPathString( classpathEntries ) );
+ }
+
+ if ( config.isDebug() )
+ {
+ args.add( "-g" );
+ }
+
+ List sourceLocations = config.getSourceLocations();
+ if ( sourceLocations != null && !sourceLocations.isEmpty() )
+ {
+ args.add( "-sourcepath" );
+
+ args.add( getPathString( sourceLocations ) );
+ }
+
+ // TODO: this could be much improved
+ if ( !compilerOptions.containsKey( "-target" ) )
+ {
+ if ( !compilerOptions.containsKey( "-source" ) )
+ {
+ // If omitted, later JDKs complain about a 1.1 target
+ args.add( "-source" );
+ args.add( "1.3" );
+ }
+
+ // Required, or it defaults to the target of your JDK (eg 1.5)
+ args.add( "-target" );
+ args.add( "1.1" );
+ }
+
+ Iterator it = compilerOptions.entrySet().iterator();
+
+ while ( it.hasNext() )
+ {
+ Map.Entry entry = (Map.Entry) it.next();
+ args.add( entry.getKey() );
+ if ( ( entry.getValue() != null ) )
+ {
+ args.add( entry.getValue() );
+ }
+ }
+
+ for ( int i = 0; i < sources.length; i++ )
+ {
+ args.add( sources[i] );
+ }
+
+ IsolatedClassLoader cl = new IsolatedClassLoader();
+
+ File toolsJar = new File( System.getProperty( "java.home" ), "../lib/tools.jar" );
+
+ if ( toolsJar.exists() )
+ {
+ cl.addURL( toolsJar.toURL() );
+ }
+
+ Class c = cl.loadClass( "com.sun.tools.javac.Main" );
+
+ ByteArrayOutputStream err = new ByteArrayOutputStream();
+
+ Method compile = c.getMethod( "compile", new Class[]{String[].class} );
+
+ Integer ok = (Integer) compile.invoke( null, new Object[]{args.toArray( new String[0] )} );
+
+ List messages = parseModernStream(
+ new BufferedReader( new InputStreamReader( new ByteArrayInputStream( err.toByteArray() ) ) ) );
+
+ if ( ok.intValue() != 0 && messages.isEmpty() )
+ {
+ // TODO: exception?
+ messages.add( new CompilerError(
+ "Failure executing javac, but could not parse the error:\n\n" + err.toString(), true ) );
+ }
+
+ return messages;
+ }
+
+ protected List parseModernStream( BufferedReader input )
+ throws IOException
+ {
+ List errors = new ArrayList();
+
+ String line;
+
+ StringBuffer buffer;
+
+ while ( true )
+ {
+ // cleanup the buffer
+ buffer = new StringBuffer(); // this is quicker than clearing it
+
+ // most errors terminate with the '^' char
+ do
+ {
+ if ( ( line = input.readLine() ) == null )
+ {
+ return errors;
+ }
+
+ // TODO: there should be a better way to parse these
+ if ( buffer.length() == 0 && line.startsWith( "error: " ) )
+ {
+ errors.add( new CompilerError( line, true ) );
+ }
+ else if ( buffer.length() == 0 && line.startsWith( "Note: " ) )
+ {
+ // skip this one - it is JDK 1.5 telling us that the interface is deprecated.
+ }
+ else
+ {
+ buffer.append( line );
+
+ buffer.append( '\n' );
+ }
+ }
+ while ( !line.endsWith( "^" ) );
+
+ // add the error bean
+ errors.add( parseModernError( buffer.toString() ) );
+ }
+ }
+
+ private CompilerError parseModernError( String error )
+ {
+ StringTokenizer tokens = new StringTokenizer( error, ":" );
+
+ try
+ {
+ String file = tokens.nextToken();
+
+ if ( file.length() == 1 )
+ {
+ file = new StringBuffer( file ).append( ":" ).append( tokens.nextToken() ).toString();
+ }
+
+ int line = Integer.parseInt( tokens.nextToken() );
+
+ String message = tokens.nextToken( "\n" ).substring( 1 );
+
+ String context = tokens.nextToken( "\n" );
+
+ String pointer = tokens.nextToken( "\n" );
+
+ int startcolumn = pointer.indexOf( "^" );
+
+ int endcolumn = context.indexOf( " ", startcolumn );
+
+ if ( endcolumn == -1 )
+ {
+ endcolumn = context.length();
+ }
+
+ return new CompilerError( file, true, line, startcolumn, line, endcolumn, message );
+ }
+ catch ( NoSuchElementException nse )
+ {
+ // TODO: exception?
+ return new CompilerError( "no more tokens - could not parse error message: " + error, true );
+ }
+ catch ( Exception nse )
+ {
+ // TODO: exception?
+ return new CompilerError( "could not parse error message: " + error, true );
+ }
+ }
+
+ public String toString()
+ {
+ return "Sun Javac Compiler";
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/download/AbstractArtifactResolver.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/AbstractArtifactResolver.java
new file mode 100644
index 0000000000..5037c9f5da
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/AbstractArtifactResolver.java
@@ -0,0 +1,78 @@
+package org.apache.maven.bootstrap.download;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.bootstrap.model.Repository;
+import org.apache.maven.bootstrap.model.Dependency;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.io.File;
+
+/**
+ */
+public abstract class AbstractArtifactResolver
+ implements ArtifactResolver
+{
+ private Repository localRepository;
+
+ private Map builtArtifacts = new HashMap();
+
+ protected AbstractArtifactResolver( Repository localRepository )
+ {
+ if ( localRepository == null )
+ {
+ System.err.println( "local repository not specified" );
+
+ System.exit( 1 );
+ }
+
+ this.localRepository = localRepository;
+ }
+
+ public Repository getLocalRepository()
+ {
+ return localRepository;
+ }
+
+ public void addBuiltArtifact( String groupId, String artifactId, String type, File jarFile )
+ {
+ builtArtifacts.put( groupId + ":" + artifactId + ":" + type, jarFile );
+ }
+
+ public boolean isAlreadyBuilt( Dependency dep )
+ {
+ return builtArtifacts.containsKey( dep.getConflictId() );
+ }
+
+ public boolean isAlreadyBuilt( String conflictId )
+ {
+ return builtArtifacts.containsKey( conflictId );
+ }
+
+ public File getArtifactFile( Dependency dependency )
+ {
+ if ( isAlreadyBuilt( dependency ) )
+ {
+ return (File) builtArtifacts.get( dependency.getConflictId() );
+ }
+ else
+ {
+ return localRepository.getArtifactFile( dependency );
+ }
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/download/ArtifactResolver.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/ArtifactResolver.java
new file mode 100644
index 0000000000..2c20d04bfa
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/ArtifactResolver.java
@@ -0,0 +1,44 @@
+package org.apache.maven.bootstrap.download;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.bootstrap.model.Repository;
+import org.apache.maven.bootstrap.model.Dependency;
+
+import java.util.Collection;
+import java.io.File;
+
+/**
+ * Artifact resolver.
+ */
+public interface ArtifactResolver
+{
+ void downloadDependencies( Collection dependencies )
+ throws DownloadFailedException;
+
+ Repository getLocalRepository();
+
+ void addBuiltArtifact( String groupId, String artifactId, String type, File jarFile );
+
+ boolean isAlreadyBuilt( Dependency dep );
+
+ File getArtifactFile( Dependency dependency );
+
+ boolean isOnline();
+
+ boolean isAlreadyBuilt( String key );
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/download/Base64.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/Base64.java
new file mode 100644
index 0000000000..e23765d1f7
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/Base64.java
@@ -0,0 +1,383 @@
+package org.apache.maven.bootstrap.download;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Encode/Decode Base-64.
+ *
+ * @author John Casey
+ */
+public final class Base64
+{
+
+ // private static final Log LOG = LogFactory.getLog( Base64.class );
+
+ private static final String CRLF = System.getProperty( "line.separator" );
+
+ private static final int LINE_END = 64;
+
+ public static String encode( byte[] data )
+ {
+ return Base64.encode( data, true );
+ }
+
+ public static String encode( byte[] data, boolean useLineDelimiter )
+ {
+ if ( data == null )
+ {
+ return null;
+ }
+ else if ( data.length == 0 )
+ {
+ return "";
+ }
+
+ int padding = 3 - ( data.length % 3 );
+
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "padding = " + padding + "characters." );
+ // }
+
+ StringBuffer buffer = new StringBuffer();
+
+ for ( int i = 0; i < data.length; i += 3 )
+ {
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "iteration base offset = " + i );
+ // }
+
+ int neutral = ( data[i] < 0 ? data[i] + 256 : data[i] );
+
+ int block = ( neutral & 0xff );
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "after first byte, block = " + Integer.toBinaryString( block ) );
+ // }
+
+ boolean inLastSegment = false;
+
+ block <<= 8;
+ if ( i + 1 < data.length )
+ {
+ neutral = ( data[i + 1] < 0 ? data[i + 1] + 256 : data[i + 1] );
+ block |= ( neutral & 0xff );
+ }
+ else
+ {
+ inLastSegment = true;
+ }
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "after second byte, block = " + Integer.toBinaryString( block ) + "; inLastSegment = "
+ // + inLastSegment );
+ // }
+
+ block <<= 8;
+ if ( i + 2 < data.length )
+ {
+ neutral = ( data[i + 2] < 0 ? data[i + 2] + 256 : data[i + 2] );
+ block |= ( neutral & 0xff );
+ }
+ else
+ {
+ inLastSegment = true;
+ }
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "after third byte, block = " + Integer.toBinaryString( block ) + "; inLastSegment = "
+ // + inLastSegment );
+ // }
+
+ char[] encoded = new char[4];
+ int encIdx = 0;
+ encoded[0] = toBase64Char( ( block >>> 18 ) & 0x3f );
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "first character = " + encoded[0] );
+ // }
+
+ encoded[1] = toBase64Char( ( block >>> 12 ) & 0x3f );
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "second character = " + encoded[1] );
+ // }
+
+ if ( inLastSegment && padding > 1 )
+ {
+ encoded[2] = '=';
+ }
+ else
+ {
+ encoded[2] = toBase64Char( ( block >>> 6 ) & 0x3f );
+ }
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "third character = " + encoded[2] );
+ // }
+
+ if ( inLastSegment && padding > 0 )
+ {
+ encoded[3] = '=';
+ }
+ else
+ {
+ encoded[3] = toBase64Char( block & 0x3f );
+ }
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "fourth character = " + encoded[3] );
+ // }
+
+ buffer.append( encoded );
+ }
+
+ if ( useLineDelimiter )
+ {
+ return canonicalize( buffer.toString() );
+ }
+ else
+ {
+ return buffer.toString();
+ }
+ }
+
+ public static byte[] decode( String src )
+ {
+ return Base64.decode( src, true );
+ }
+
+ public static byte[] decode( String src, boolean useLineDelimiter )
+ {
+ if ( src == null )
+ {
+ return null;
+ }
+ else if ( src.length() < 1 )
+ {
+ return new byte[0];
+ }
+
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "pre-canonicalization = \n" + src );
+ // }
+ String data = src;
+
+ if ( useLineDelimiter )
+ {
+ data = deCanonicalize( src );
+ }
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "post-canonicalization = \n" + data );
+ // }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ char[] input = data.toCharArray();
+
+ int index = 0;
+ for ( int i = 0; i < input.length; i += 4 )
+ {
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "iteration base offset = " + i );
+ // }
+
+ int block = ( toBase64Int( input[i] ) & 0x3f );
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "block after first char [" + input[i] + "] = " + Integer.toBinaryString( block ) );
+ // }
+
+ block <<= 6;
+ block |= ( toBase64Int( input[i + 1] ) & 0x3f );
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "block after second char [" + input[i + 1] + "] = " + Integer.toBinaryString( block ) );
+ // }
+
+ boolean inPadding = false;
+ boolean twoCharPadding = false;
+ block <<= 6;
+ if ( input[i + 2] != '=' )
+ {
+ block |= ( toBase64Int( input[i + 2] ) & 0x3f );
+ }
+ else
+ {
+ twoCharPadding = true;
+ inPadding = true;
+ }
+
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "block after third char [" + input[i + 2] + "] = " + Integer.toBinaryString( block ) );
+ // }
+
+ block <<= 6;
+ if ( input[i + 3] != '=' )
+ {
+ block |= ( toBase64Int( input[i + 3] ) & 0x3f );
+ }
+ else
+ {
+ inPadding = true;
+ }
+
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "block after fourth char [" + input[i + 3] + "] = " + Integer.toBinaryString( block ) );
+ // }
+
+ baos.write( ( block >>> 16 ) & 0xff );
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "byte[" + ( index++ ) + "] = " + ( ( block >>> 16 ) & 0xff ) );
+ // }
+
+ if ( !inPadding || !twoCharPadding )
+ {
+ baos.write( ( block >>> 8 ) & 0xff );
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "byte[" + ( index++ ) + "] = " + ( ( block >>> 8 ) & 0xff ) );
+ // }
+ }
+
+ if ( !inPadding )
+ {
+ baos.write( block & 0xff );
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "byte[" + ( index++ ) + "] = " + ( block & 0xff ) );
+ // }
+ }
+ }
+
+ byte[] result = baos.toByteArray();
+ // if ( LOG.isDebugEnabled() )
+ // {
+ // LOG.debug( "byte array is " + result.length + " bytes long." );
+ // }
+
+ return result;
+ }
+
+ private static char toBase64Char( int input )
+ {
+ if ( input > -1 && input < 26 )
+ {
+ return (char) ( 'A' + input );
+ }
+ else if ( input > 25 && input < 52 )
+ {
+ return (char) ( 'a' + input - 26 );
+ }
+ else if ( input > 51 && input < 62 )
+ {
+ return (char) ( '0' + input - 52 );
+ }
+ else if ( input == 62 )
+ {
+ return '+';
+ }
+ else if ( input == 63 )
+ {
+ return '/';
+ }
+ else
+ {
+ return '?';
+ }
+ }
+
+ private static int toBase64Int( char input )
+ {
+ if ( input >= 'A' && input <= 'Z' )
+ {
+ return input - 'A';
+ }
+ else if ( input >= 'a' && input <= 'z' )
+ {
+ return input + 26 - 'a';
+ }
+ else if ( input >= '0' && input <= '9' )
+ {
+ return input + 52 - '0';
+ }
+ else if ( input == '+' )
+ {
+ return 62;
+ }
+ else if ( input == '/' )
+ {
+ return 63;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ private static String deCanonicalize( String data )
+ {
+ if ( data == null )
+ {
+ return null;
+ }
+
+ StringBuffer buffer = new StringBuffer( data.length() );
+ for ( int i = 0; i < data.length(); i++ )
+ {
+ char c = data.charAt( i );
+ if ( c != '\r' && c != '\n' )
+ {
+ buffer.append( c );
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ private static String canonicalize( String data )
+ {
+ StringBuffer buffer = new StringBuffer( (int) ( data.length() * 1.1 ) );
+
+ int col = 0;
+ for ( int i = 0; i < data.length(); i++ )
+ {
+ if ( col == LINE_END )
+ {
+ buffer.append( CRLF );
+ col = 0;
+ }
+
+ buffer.append( data.charAt( i ) );
+ col++;
+ }
+
+ buffer.append( CRLF );
+
+ return buffer.toString();
+ }
+
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/download/DownloadFailedException.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/DownloadFailedException.java
new file mode 100755
index 0000000000..ad04a7b6b9
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/DownloadFailedException.java
@@ -0,0 +1,31 @@
+package org.apache.maven.bootstrap.download;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Failed download.
+ *
+ * @author Brett Porter
+ * @version $Id$
+ */
+public class DownloadFailedException extends Exception
+{
+ public DownloadFailedException( String message )
+ {
+ super( message );
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/download/HttpUtils.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/HttpUtils.java
new file mode 100644
index 0000000000..6c634d6c46
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/HttpUtils.java
@@ -0,0 +1,359 @@
+package org.apache.maven.bootstrap.download;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Authenticator;
+import java.net.HttpURLConnection;
+import java.net.PasswordAuthentication;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * Http utils for retrieving files.
+ *
+ * @author costin@dnt.ro
+ * @author gg@grtmail.com (Added Java 1.1 style HTTP basic auth)
+ * @author Jason van Zyl
+ */
+public class HttpUtils
+{
+ /**
+ * Use a proxy to bypass the firewall with or without authentication
+ *
+ * @param proxyHost Proxy Host (if proxy is required), or null
+ * @param proxyPort Proxy Port (if proxy is required), or null
+ * @param proxyUserName Proxy Username (if authentification is required),
+ * or null
+ * @param proxyPassword Proxy Password (if authentification is required),
+ * or null
+ * @throws SecurityException if an operation is not authorized by the
+ * SecurityManager
+ */
+ public static void useProxyUser( final String proxyHost, final String proxyPort, final String proxyUserName,
+ final String proxyPassword )
+ {
+ if ( proxyHost != null && proxyPort != null )
+ {
+ System.getProperties().put( "proxySet", "true" );
+ System.getProperties().put( "proxyHost", proxyHost );
+ System.getProperties().put( "proxyPort", proxyPort );
+
+ if ( proxyUserName != null )
+ {
+ Authenticator.setDefault( new Authenticator()
+ {
+ protected PasswordAuthentication getPasswordAuthentication()
+ {
+ return new PasswordAuthentication( proxyUserName, proxyPassword == null ? new char[0]
+ : proxyPassword.toCharArray() );
+ }
+ } );
+ }
+ }
+ }
+
+ /**
+ * Retrieve a remote file. Throws an Exception on errors unless the
+ * ifnoreErrors flag is set to True
+ *
+ * @param url the of the file to retrieve
+ * @param destinationFile where to store it
+ * @param ignoreErrors whether to ignore errors during I/O or throw an
+ * exception when they happen
+ * @param useTimestamp whether to check the modified timestamp on the
+ * Returns the names of the files which were selected out and
+ * therefore not ultimately included. The names are relative to the base directory. This involves
+ * performing a slow scan if one has not already been completed. Returns the names of the directories which were selected out and
+ * therefore not ultimately included. The names are relative to the base directory. This involves
+ * performing a slow scan if one has not already been completed. It doesn't really test for symbolic links but whether the
+ * canonical and absolute paths of the file are identical - this
+ * may lead to false positives on some platforms. Methods exist to retrieve the components of a typical file path. For example
+ * destinationFile
against the remote source
+ * @param proxyHost Proxy Host (if proxy is required), or null
+ * @param proxyPort Proxy Port (if proxy is required), or null
+ * @param proxyUserName Proxy Username (if authentification is required),
+ * or null.
+ * @param proxyPassword Proxy Password (if authentification is required),
+ * or null.
+ * @param useChecksum Flag to indicate the use of the checksum for the retrieved
+ * artifact if it is available.
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static void getFile( String url, File destinationFile, boolean ignoreErrors, boolean useTimestamp,
+ String proxyHost, String proxyPort, String proxyUserName, String proxyPassword,
+ boolean useChecksum )
+ throws IOException
+ {
+ // Get the requested file.
+ getFile( url, destinationFile, ignoreErrors, useTimestamp, proxyHost, proxyPort, proxyUserName, proxyPassword );
+
+ // Get the checksum if requested.
+ if ( useChecksum )
+ {
+ File checksumFile = new File( destinationFile + ".md5" );
+
+ try
+ {
+ getFile( url + ".md5", checksumFile, ignoreErrors, useTimestamp, proxyHost, proxyPort, proxyUserName,
+ proxyPassword );
+ }
+ catch ( Exception e )
+ {
+ // do nothing we will check later in the process
+ // for the checksums.
+ }
+ }
+ }
+
+ /**
+ * Retrieve a remote file. Throws an Exception on errors unless the
+ * ifnoreErrors flag is set to True
+ *
+ * @param url the of the file to retrieve
+ * @param destinationFile where to store it
+ * @param ignoreErrors whether to ignore errors during I/O or throw an
+ * exception when they happen
+ * @param useTimestamp whether to check the modified timestamp on the
+ * destinationFile
against the remote source
+ * @param proxyHost Proxy Host (if proxy is required), or null
+ * @param proxyPort Proxy Port (if proxy is required), or null
+ * @param proxyUserName Proxy Username (if authentification is required),
+ * or null
+ * @param proxyPassword Proxy Password (if authentification is required),
+ * or null
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static void getFile( String url, File destinationFile, boolean ignoreErrors, boolean useTimestamp,
+ String proxyHost, String proxyPort, String proxyUserName, String proxyPassword )
+ throws IOException
+ {
+ //set the timestamp to the file date.
+ long timestamp = -1;
+ if ( useTimestamp && destinationFile.exists() )
+ {
+ timestamp = destinationFile.lastModified();
+ }
+
+ try
+ {
+ getFile( url, destinationFile, timestamp, proxyHost, proxyPort, proxyUserName, proxyPassword );
+ }
+ catch ( IOException ex )
+ {
+ if ( !ignoreErrors )
+ {
+ throw ex;
+ }
+ }
+ }
+
+ /**
+ * Retrieve a remote file.
+ *
+ * @param url the URL of the file to retrieve
+ * @param destinationFile where to store it
+ * @param timestamp if provided, the remote URL is only retrieved if it was
+ * modified more recently than timestamp. Otherwise, negative value indicates that
+ * the remote URL should be retrieved unconditionally.
+ * @param proxyHost Proxy Host (if proxy is required), or null
+ * @param proxyPort Proxy Port (if proxy is required), or null
+ * @param proxyUserName Proxy Username (if authentification is required),
+ * or null
+ * @param proxyPassword Proxy Password (if authentification is required),
+ * or null
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static void getFile( String url, File destinationFile, long timestamp, String proxyHost, String proxyPort,
+ String proxyUserName, String proxyPassword )
+ throws IOException
+ {
+ String[] s = parseUrl( url );
+ String username = s[0];
+ String password = s[1];
+ String parsedUrl = s[2];
+
+ URL source = new URL( parsedUrl );
+
+ //set proxy connection
+ useProxyUser( proxyHost, proxyPort, proxyUserName, proxyPassword );
+
+ //set up the URL connection
+ URLConnection connection = source.openConnection();
+
+ //modify the headers
+ if ( timestamp >= 0 )
+ {
+ connection.setIfModifiedSince( timestamp );
+ }
+ // prepare Java 1.1 style credentials
+ if ( username != null || password != null )
+ {
+ String up = username + ":" + password;
+ String encoding = Base64.encode( up.getBytes(), false );
+ connection.setRequestProperty( "Authorization", "Basic " + encoding );
+ }
+
+ connection.setRequestProperty( "Pragma", "no-cache" );
+
+ //connect to the remote site (may take some time)
+ connection.connect();
+ //next test for a 304 result (HTTP only)
+ if ( connection instanceof HttpURLConnection )
+ {
+ HttpURLConnection httpConnection = (HttpURLConnection) connection;
+ // although HTTPUrlConnection javadocs says FileNotFoundException should be
+ // thrown on a 404 error, that certainly does not appear to be the case, so
+ // test for 404 ourselves, and throw FileNotFoundException as needed
+ if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND )
+ {
+ throw new FileNotFoundException( url + " (HTTP Error: " + httpConnection.getResponseCode() +
+ " " + httpConnection.getResponseMessage() + ")" );
+ }
+ if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED )
+ {
+ return;
+ }
+ // test for 401 result (HTTP only)
+ if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED )
+ {
+ throw new IOException( "Not authorized." );
+ }
+ // test for 407 result (HTTP only)
+ if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_PROXY_AUTH )
+ {
+ throw new IOException( "Not authorized by proxy." );
+ }
+ }
+
+ // REVISIT: at this point even non HTTP connections may support the
+ // if-modified-since behaviour - we just check the date of the
+ // content and skip the write if it is not newer.
+ // Some protocols (FTP) dont include dates, of course.
+
+ InputStream is = null;
+ IOException isException = null;
+ for ( int i = 0; i < 3; i++ )
+ {
+ try
+ {
+ is = connection.getInputStream();
+ break;
+ }
+ catch ( IOException ex )
+ {
+ isException = ex;
+ }
+ }
+ if ( is == null )
+ {
+ throw isException;
+ }
+
+ if ( connection.getLastModified() <= timestamp && connection.getLastModified() != 0 )
+ {
+ return;
+ }
+
+ FileOutputStream fos = new FileOutputStream( destinationFile );
+
+ byte[] buffer = new byte[100 * 1024];
+ int length;
+
+ while ( ( length = is.read( buffer ) ) >= 0 )
+ {
+ fos.write( buffer, 0, length );
+ System.out.print( "." );
+ }
+
+ System.out.println();
+ fos.close();
+ is.close();
+
+ // if (and only if) the use file time option is set, then the
+ // saved file now has its timestamp set to that of the downloaded
+ // file
+ if ( timestamp >= 0 )
+ {
+ long remoteTimestamp = connection.getLastModified();
+ if ( remoteTimestamp != 0 )
+ {
+ touchFile( destinationFile, remoteTimestamp );
+ }
+ }
+ }
+
+ /**
+ * Parse an url which might contain a username and password. If the
+ * given url doesn't contain a username and password then return the
+ * origin url unchanged.
+ *
+ * @param url The url to parse.
+ * @return The username, password and url.
+ * @throws RuntimeException if the url is (very) invalid
+ */
+ static String[] parseUrl( String url )
+ {
+ String[] parsedUrl = new String[3];
+ parsedUrl[0] = null;
+ parsedUrl[1] = null;
+ parsedUrl[2] = url;
+
+ // We want to be able to deal with Basic Auth where the username
+ // and password are part of the URL. An example of the URL string
+ // we would like to be able to parse is like the following:
+ //
+ // http://username:password@repository.mycompany.com
+
+ int i = url.indexOf( "@" );
+ if ( i > 0 )
+ {
+ String protocol = url.substring( 0, url.indexOf( "://" ) ) + "://";
+ String s = url.substring( protocol.length(), i );
+ int j = s.indexOf( ":" );
+ parsedUrl[0] = s.substring( 0, j );
+ parsedUrl[1] = s.substring( j + 1 );
+ parsedUrl[2] = protocol + url.substring( i + 1 );
+ }
+
+ return parsedUrl;
+ }
+
+ /**
+ * set the timestamp of a named file to a specified time.
+ *
+ * @param file the file to touch
+ * @param timemillis in milliseconds since the start of the era
+ * @return true if it succeeded. False means that this is a java1.1 system
+ * and that file times can not be set
+ * @throws RuntimeException Thrown in unrecoverable error. Likely this
+ * comes from file access failures.
+ */
+ private static boolean touchFile( File file, long timemillis )
+ {
+ long modifiedTime;
+
+ if ( timemillis < 0 )
+ {
+ modifiedTime = System.currentTimeMillis();
+ }
+ else
+ {
+ modifiedTime = timemillis;
+ }
+
+ file.setLastModified( modifiedTime );
+ return true;
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OfflineArtifactResolver.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OfflineArtifactResolver.java
new file mode 100644
index 0000000000..4855095281
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OfflineArtifactResolver.java
@@ -0,0 +1,44 @@
+package org.apache.maven.bootstrap.download;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.bootstrap.model.Repository;
+
+import java.util.Collection;
+
+/**
+ * Resolve from the local repository - don't attempt to download or check out.
+ */
+public class OfflineArtifactResolver
+ extends AbstractArtifactResolver
+{
+ public OfflineArtifactResolver( Repository localRepository )
+ {
+ super( localRepository );
+ }
+
+ public void downloadDependencies( Collection dependencies )
+ throws DownloadFailedException
+ {
+ // Nothing to see here
+ }
+
+ public boolean isOnline()
+ {
+ return false;
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OnlineArtifactDownloader.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OnlineArtifactDownloader.java
new file mode 100644
index 0000000000..f60a7d591a
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OnlineArtifactDownloader.java
@@ -0,0 +1,332 @@
+package org.apache.maven.bootstrap.download;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.bootstrap.model.Dependency;
+import org.apache.maven.bootstrap.model.Repository;
+import org.apache.maven.bootstrap.util.FileUtils;
+import org.apache.maven.bootstrap.util.StringUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class OnlineArtifactDownloader
+ extends AbstractArtifactResolver
+{
+ public static final String SNAPSHOT_SIGNATURE = "-SNAPSHOT";
+
+ private boolean useTimestamp = true;
+
+ private boolean ignoreErrors = false;
+
+ private String proxyHost;
+
+ private String proxyPort;
+
+ private String proxyUserName;
+
+ private String proxyPassword;
+
+ private static final String REPO_URL = "http://repo1.maven.org/maven2";
+
+ private Map downloadedArtifacts = new HashMap();
+
+ private List remoteRepositories;
+
+ public OnlineArtifactDownloader( Repository localRepository )
+ throws Exception
+ {
+ super( localRepository );
+ }
+
+ public void setProxy( String host, String port, String userName, String password )
+ {
+ proxyHost = host;
+ proxyPort = port;
+ proxyUserName = userName;
+ proxyPassword = password;
+ System.out.println( "Using the following proxy : " + proxyHost + "/" + proxyPort );
+ }
+
+ public void downloadDependencies( Collection dependencies )
+ throws DownloadFailedException
+ {
+ for ( Iterator j = dependencies.iterator(); j.hasNext(); )
+ {
+ Dependency dep = (Dependency) j.next();
+
+ if ( isAlreadyBuilt( dep ) )
+ {
+ continue;
+ }
+
+ String dependencyConflictId = dep.getDependencyConflictId();
+ if ( !downloadedArtifacts.containsKey( dependencyConflictId ) )
+ {
+ File destinationFile = getLocalRepository().getArtifactFile( dep );
+ // The directory structure for this project may
+ // not exists so create it if missing.
+ File directory = destinationFile.getParentFile();
+
+ if ( !directory.exists() )
+ {
+ directory.mkdirs();
+ }
+
+ if ( !getRemoteArtifact( dep, destinationFile ) )
+ {
+ throw new DownloadFailedException( "Failed to download " + dep );
+ }
+
+ downloadedArtifacts.put( dependencyConflictId, dep );
+ }
+ else
+ {
+ Dependency d = (Dependency) downloadedArtifacts.get( dependencyConflictId );
+ dep.setResolvedVersion( d.getResolvedVersion() );
+ }
+ }
+ }
+
+ public boolean isOnline()
+ {
+ return true;
+ }
+
+ private static boolean isSnapshot( Dependency dep )
+ {
+ return dep.getVersion().indexOf( SNAPSHOT_SIGNATURE ) >= 0;
+ }
+
+ private boolean getRemoteArtifact( Dependency dep, File destinationFile )
+ {
+ boolean fileFound = false;
+
+ for ( Iterator i = getRemoteRepositories().iterator(); i.hasNext(); )
+ {
+ Repository remoteRepo = (Repository) i.next();
+
+ boolean snapshot = isSnapshot( dep );
+ if ( snapshot && !remoteRepo.isSnapshots() )
+ {
+ continue;
+ }
+ if ( !snapshot && !remoteRepo.isReleases() )
+ {
+ continue;
+ }
+
+ // The username and password parameters are not being used here.
+ String url = remoteRepo.getBasedir() + "/" + remoteRepo.getArtifactPath( dep );
+
+ // Attempt to retrieve the artifact and set the checksum if retrieval
+ // of the checksum file was successful.
+ try
+ {
+ String version = dep.getVersion();
+ if ( snapshot )
+ {
+ String filename = "maven-metadata-" + remoteRepo.getId() + ".xml";
+ File localFile = getLocalRepository().getMetadataFile( dep.getGroupId(), dep.getArtifactId(),
+ dep.getVersion(), dep.getType(),
+ "maven-metadata-local.xml" );
+ File remoteFile = getLocalRepository().getMetadataFile( dep.getGroupId(), dep.getArtifactId(),
+ dep.getVersion(), dep.getType(), filename );
+ String metadataPath = remoteRepo.getMetadataPath( dep.getGroupId(), dep.getArtifactId(),
+ dep.getVersion(), dep.getType(),
+ "maven-metadata.xml" );
+ String metaUrl = remoteRepo.getBasedir() + "/" + metadataPath;
+ log( "Downloading " + metaUrl );
+ try
+ {
+ HttpUtils.getFile( metaUrl, remoteFile, ignoreErrors, true, proxyHost, proxyPort, proxyUserName,
+ proxyPassword, false );
+ }
+ catch ( IOException e )
+ {
+ log( "WARNING: remote metadata version not found, using local: " + e.getMessage() );
+ remoteFile.delete();
+ }
+
+ File file = localFile;
+ if ( remoteFile.exists() )
+ {
+ if ( !localFile.exists() )
+ {
+ file = remoteFile;
+ }
+ else
+ {
+ RepositoryMetadata localMetadata = RepositoryMetadata.read( localFile );
+
+ RepositoryMetadata remoteMetadata = RepositoryMetadata.read( remoteFile );
+
+ if ( remoteMetadata.getLastUpdatedUtc() > localMetadata.getLastUpdatedUtc() )
+ {
+ file = remoteFile;
+ }
+ else
+ {
+ file = localFile;
+ }
+ }
+ }
+
+ if ( file.exists() )
+ {
+ log( "Using metadata: " + file );
+
+ RepositoryMetadata metadata = RepositoryMetadata.read( file );
+
+ if ( !file.equals( localFile ) )
+ {
+ version = metadata.constructVersion( version );
+ }
+ log( "Resolved version: " + version );
+ dep.setResolvedVersion( version );
+ if ( !version.endsWith( "SNAPSHOT" ) )
+ {
+ String ver =
+ version.substring( version.lastIndexOf( "-", version.lastIndexOf( "-" ) - 1 ) + 1 );
+ String extension = url.substring( url.length() - 4 );
+ url = getSnapshotMetadataFile( url, ver + extension );
+ }
+ else if ( destinationFile.exists() )
+ {
+ // It's already there
+ return true;
+ }
+ }
+ }
+ if ( !"pom".equals( dep.getType() ) )
+ {
+ String name = dep.getArtifactId() + "-" + dep.getResolvedVersion() + ".pom";
+ File file = getLocalRepository().getMetadataFile( dep.getGroupId(), dep.getArtifactId(),
+ dep.getVersion(), dep.getType(), name );
+
+ file.getParentFile().mkdirs();
+
+ if ( !file.exists() || version.indexOf( "SNAPSHOT" ) >= 0 )
+ {
+ String filename = dep.getArtifactId() + "-" + version + ".pom";
+ String metadataPath = remoteRepo.getMetadataPath( dep.getGroupId(), dep.getArtifactId(),
+ dep.getVersion(), dep.getType(), filename );
+ String metaUrl = remoteRepo.getBasedir() + "/" + metadataPath;
+ log( "Downloading " + metaUrl );
+
+ try
+ {
+ HttpUtils.getFile( metaUrl, file, ignoreErrors, false, proxyHost, proxyPort, proxyUserName,
+ proxyPassword, false );
+ }
+ catch ( IOException e )
+ {
+ log( "Couldn't find POM - ignoring: " + e.getMessage() );
+ }
+ }
+ }
+
+ destinationFile = getLocalRepository().getArtifactFile( dep );
+ if ( !destinationFile.exists() )
+ {
+ log( "Downloading " + url );
+ HttpUtils.getFile( url, destinationFile, ignoreErrors, useTimestamp, proxyHost, proxyPort,
+ proxyUserName, proxyPassword, true );
+ if ( dep.getVersion().indexOf( "SNAPSHOT" ) >= 0 )
+ {
+ String name = StringUtils.replace( destinationFile.getName(), version, dep.getVersion() );
+ FileUtils.copyFile( destinationFile, new File( destinationFile.getParentFile(), name ) );
+ }
+ }
+
+ // Artifact was found, continue checking additional remote repos (if any)
+ // in case there is a newer version (i.e. snapshots) in another repo
+ fileFound = true;
+ }
+ catch ( FileNotFoundException e )
+ {
+ log( "Artifact not found at [" + url + "]" );
+ // Ignore
+ }
+ catch ( Exception e )
+ {
+ // If there are additional remote repos, then ignore exception
+ // as artifact may be found in another remote repo. If there
+ // are no more remote repos to check and the artifact wasn't found in
+ // a previous remote repo, then artifactFound is false indicating
+ // that the artifact could not be found in any of the remote repos
+ //
+ // arguably, we need to give the user better control (another command-
+ // line switch perhaps) of what to do in this case? Maven already has
+ // a command-line switch to work in offline mode, but what about when
+ // one of two or more remote repos is unavailable? There may be multiple
+ // remote repos for redundancy, in which case you probably want the build
+ // to continue. There may however be multiple remote repos because some
+ // artifacts are on one, and some are on another. In this case, you may
+ // want the build to break.
+ //
+ // print a warning, in any case, so user catches on to mistyped
+ // hostnames, or other snafus
+ log( "Error retrieving artifact from [" + url + "]: " + e );
+ }
+ }
+
+ return fileFound;
+ }
+
+ private static String getSnapshotMetadataFile( String filename, String s )
+ {
+ int index = filename.lastIndexOf( "SNAPSHOT" );
+ return filename.substring( 0, index ) + s;
+ }
+
+ private void log( String message )
+ {
+ System.out.println( message );
+ }
+
+ public List getRemoteRepositories()
+ {
+ if ( remoteRepositories == null )
+ {
+ remoteRepositories = new ArrayList();
+ }
+
+ if ( remoteRepositories.isEmpty() )
+ {
+ // TODO: use super POM?
+ remoteRepositories.add( new Repository( "central", REPO_URL, Repository.LAYOUT_DEFAULT, false, true ) );
+ // TODO: use maven root POM?
+ remoteRepositories.add( new Repository( "snapshots", "http://snapshots.maven.codehaus.org/maven2/",
+ Repository.LAYOUT_DEFAULT, true, false ) );
+ }
+
+ return remoteRepositories;
+ }
+
+ public void setRemoteRepositories( List remoteRepositories )
+ {
+ this.remoteRepositories = remoteRepositories;
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/download/RepositoryMetadata.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/RepositoryMetadata.java
new file mode 100644
index 0000000000..307b99a2cd
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/RepositoryMetadata.java
@@ -0,0 +1,395 @@
+package org.apache.maven.bootstrap.download;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.bootstrap.util.AbstractReader;
+import org.apache.maven.bootstrap.util.StringUtils;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * I/O for repository metadata.
+ *
+ * @author Brett Porter
+ * @version $Id$
+ */
+public class RepositoryMetadata
+{
+ private String snapshotTimestamp;
+
+ private int snapshotBuildNumber;
+
+ private String releaseVersion;
+
+ private String groupId;
+
+ private String artifactId;
+
+ private String version;
+
+ private List versions = new ArrayList();
+
+ private String latestVersion;
+
+ private boolean localCopy;
+
+ private String lastUpdated;
+
+ public String getSnapshotTimestamp()
+ {
+ return snapshotTimestamp;
+ }
+
+ public void setSnapshotTimestamp( String snapshotTimestamp )
+ {
+ this.snapshotTimestamp = snapshotTimestamp;
+ }
+
+ public int getSnapshotBuildNumber()
+ {
+ return snapshotBuildNumber;
+ }
+
+ public void setSnapshotBuildNumber( int snapshotBuildNumber )
+ {
+ this.snapshotBuildNumber = snapshotBuildNumber;
+ }
+
+ public String getGroupId()
+ {
+ return groupId;
+ }
+
+ public void setGroupId( String groupId )
+ {
+ this.groupId = groupId;
+ }
+
+ public String getArtifactId()
+ {
+ return artifactId;
+ }
+
+ public void setArtifactId( String artifactId )
+ {
+ this.artifactId = artifactId;
+ }
+
+ public String getVersion()
+ {
+ return version;
+ }
+
+ public void setVersion( String version )
+ {
+ this.version = version;
+ }
+
+ public List getVersions()
+ {
+ return versions;
+ }
+
+ public void setVersions( List versions )
+ {
+ this.versions = versions;
+ }
+
+ public String getReleaseVersion()
+ {
+ return releaseVersion;
+ }
+
+ public void setReleaseVersion( String releaseVersion )
+ {
+ this.releaseVersion = releaseVersion;
+ }
+
+ public String getLatestVersion()
+ {
+ return latestVersion;
+ }
+
+ public void setLatestVersion( String latestVersion )
+ {
+ this.latestVersion = latestVersion;
+ }
+
+ public void addVersion( String version )
+ {
+ versions.add( version );
+ }
+
+ public boolean isLocalCopy()
+ {
+ return localCopy;
+ }
+
+ public void setLocalCopy( boolean localCopy )
+ {
+ this.localCopy = localCopy;
+ }
+
+ public static RepositoryMetadata read( File file )
+ throws IOException, ParserConfigurationException, SAXException
+ {
+ return new Reader().parseMetadata( file );
+ }
+
+ public void write( File file )
+ throws IOException
+ {
+ new Writer( this ).write( file );
+ }
+
+ public String constructVersion( String baseVersion )
+ {
+ if ( snapshotTimestamp != null )
+ {
+ baseVersion = StringUtils.replace( baseVersion, "SNAPSHOT", snapshotTimestamp + "-" + snapshotBuildNumber );
+ }
+ return baseVersion;
+ }
+
+ public long getLastUpdatedUtc()
+ {
+ TimeZone timezone = TimeZone.getTimeZone( "UTC" );
+ DateFormat fmt = new SimpleDateFormat( "yyyyMMddHHmmss" );
+ fmt.setTimeZone( timezone );
+
+ try
+ {
+ return fmt.parse( lastUpdated ).getTime();
+ }
+ catch ( ParseException e )
+ {
+ return -1;
+ }
+ }
+
+ public void setLastUpdated( String lastUpdated )
+ {
+ this.lastUpdated = lastUpdated;
+ }
+
+ public String getLastUpdated()
+ {
+ return lastUpdated;
+ }
+
+ static class Reader
+ extends AbstractReader
+ {
+ private boolean insideVersioning;
+
+ private StringBuffer bodyText = new StringBuffer();
+
+ private boolean insideSnapshot;
+
+ private final RepositoryMetadata metadata = new RepositoryMetadata();
+
+ private boolean insideVersions;
+
+ public RepositoryMetadata parseMetadata( File metadataFile )
+ throws IOException, ParserConfigurationException, SAXException
+ {
+ parse( metadataFile );
+ return metadata;
+ }
+
+ public void startElement( String uri, String localName, String rawName, Attributes attributes )
+ {
+ if ( insideVersioning )
+ {
+ if ( "snapshot".equals( rawName ) )
+ {
+ insideSnapshot = true;
+ }
+ else if ( "versions".equals( rawName ) )
+ {
+ insideVersions = true;
+ }
+ }
+ else
+ {
+ // root element
+ if ( "versioning".equals( rawName ) )
+ {
+ insideVersioning = true;
+ }
+ }
+ }
+
+ public void characters( char buffer[], int start, int length )
+ {
+ bodyText.append( buffer, start, length );
+ }
+
+ private String getBodyText()
+ {
+ return bodyText.toString().trim();
+ }
+
+ public void endElement( String uri, String localName, String rawName )
+ throws SAXException
+ {
+ if ( insideVersioning )
+ {
+ if ( "versioning".equals( rawName ) )
+ {
+ insideVersioning = false;
+ }
+ else if ( insideSnapshot )
+ {
+ if ( "snapshot".equals( rawName ) )
+ {
+ insideSnapshot = false;
+ }
+ else if ( "buildNumber".equals( rawName ) )
+ {
+ try
+ {
+ metadata.setSnapshotBuildNumber( Integer.valueOf( getBodyText() ).intValue() );
+ }
+ catch ( NumberFormatException e )
+ {
+ // Ignore
+ }
+ }
+ else if ( "timestamp".equals( rawName ) )
+ {
+ metadata.setSnapshotTimestamp( getBodyText() );
+ }
+ else if ( "localCopy".equals( rawName ) )
+ {
+ metadata.setLocalCopy( Boolean.valueOf( getBodyText() ).booleanValue() );
+ }
+ }
+ else if ( insideVersions )
+ {
+ if ( "versions".equals( rawName ) )
+ {
+ insideVersions = false;
+ }
+ else if ( "version".equals( rawName ) )
+ {
+ metadata.addVersion( getBodyText() );
+ }
+ }
+ else if ( "latest".equals( rawName ) )
+ {
+ metadata.setLatestVersion( getBodyText() );
+ }
+ else if ( "release".equals( rawName ) )
+ {
+ metadata.setReleaseVersion( getBodyText() );
+ }
+ else if ( "lastUpdated".equals( rawName ) )
+ {
+ metadata.setLastUpdated( getBodyText() );
+ }
+ }
+ else if ( "groupId".equals( rawName ) )
+ {
+ metadata.setGroupId( getBodyText() );
+ }
+ else if ( "artifactId".equals( rawName ) )
+ {
+ metadata.setArtifactId( getBodyText() );
+ }
+ else if ( "version".equals( rawName ) )
+ {
+ metadata.setVersion( getBodyText() );
+ }
+ bodyText = new StringBuffer();
+ }
+
+ }
+
+ static class Writer
+ {
+ private final RepositoryMetadata metadata;
+
+ public Writer( RepositoryMetadata metadata )
+ {
+ this.metadata = metadata;
+ }
+
+ public void write( File file )
+ throws IOException
+ {
+ PrintWriter w = new PrintWriter( new FileWriter( file ) );
+
+ try
+ {
+ w.println( "File.separator
('/' under UNIX, '\' under Windows).
+ * For example, "abc/def/ghi/xyz.java" is split up in the segments "abc",
+ * "def","ghi" and "xyz.java".
+ * The same is done for the pattern against which should be matched.
+ *
+ * The segments of the name and the pattern are then matched against each
+ * other. When '**' is used for a path segment in the pattern, it matches
+ * zero or more path segments of the name.
+ *
+ * There is a special case regarding the use of File.separator
s
+ * at the beginning of the pattern and the string to match:
+ * When a pattern starts with a File.separator
, the string
+ * to match must also start with a File.separator
.
+ * When a pattern does not start with a File.separator
, the
+ * string to match may not start with a File.separator
.
+ * When one of these rules is not obeyed, the string will not
+ * match.
+ *
+ * When a name path segment is matched against a pattern path segment, the
+ * following special characters can be used:
+ * '*' matches zero or more characters
+ * '?' matches one character.
+ *
+ * Examples:
+ *
+ * "**\*.class" matches all .class files/dirs in a directory tree.
+ *
+ * "test\a??.java" matches all files/dirs which start with an 'a', then two
+ * more characters and then ".java", in a directory called test.
+ *
+ * "**" matches everything in a directory tree.
+ *
+ * "**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where
+ * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").
+ *
+ * Case sensitivity may be turned off if necessary. By default, it is
+ * turned on.
+ *
+ * Example of usage:
+ *
+ * String[] includes = {"**\\*.class"};
+ * String[] excludes = {"modules\\*\\**"};
+ * ds.setIncludes(includes);
+ * ds.setExcludes(excludes);
+ * ds.setBasedir(new File("test"));
+ * ds.setCaseSensitive(true);
+ * ds.scan();
+ *
+ * System.out.println("FILES:");
+ * String[] files = ds.getIncludedFiles();
+ * for (int i = 0; i < files.length; i++) {
+ * System.out.println(files[i]);
+ * }
+ *
+ * This will scan a directory called test for .class files, but excludes all
+ * files in all proper subdirectories of a directory called "modules"
+ *
+ * @author Arnout J. Kuiper
+ * ajkuiper@wxs.nl
+ * @author Magesh Umasankar
+ * @author Bruce Atherton
+ * @author Antoine Levy-Lambert
+ */
+public class DirectoryScanner
+{
+ /**
+ * Patterns which should be excluded by default.
+ *
+ * @see #addDefaultExcludes()
+ */
+ protected static final String[] DEFAULTEXCLUDES = {
+ // Miscellaneous typical temporary files
+ "**/*~",
+ "**/#*#",
+ "**/.#*",
+ "**/%*%",
+ "**/._*",
+
+ // CVS
+ "**/CVS",
+ "**/CVS/**",
+ "**/.cvsignore",
+
+ // SCCS
+ "**/SCCS",
+ "**/SCCS/**",
+
+ // Visual SourceSafe
+ "**/vssver.scc",
+
+ // Subversion
+ "**/.svn",
+ "**/.svn/**",
+
+ // Mac
+ "**/.DS_Store"
+ };
+
+ /**
+ * The base directory to be scanned.
+ */
+ protected File basedir;
+
+ /**
+ * The patterns for the files to be included.
+ */
+ protected String[] includes;
+
+ /**
+ * The patterns for the files to be excluded.
+ */
+ protected String[] excludes;
+
+ /**
+ * The files which matched at least one include and no excludes
+ * and were selected.
+ */
+ protected Vector filesIncluded;
+
+ /**
+ * The files which did not match any includes or selectors.
+ */
+ protected Vector filesNotIncluded;
+
+ /**
+ * The files which matched at least one include and at least
+ * one exclude.
+ */
+ protected Vector filesExcluded;
+
+ /**
+ * The directories which matched at least one include and no excludes
+ * and were selected.
+ */
+ protected Vector dirsIncluded;
+
+ /**
+ * The directories which were found and did not match any includes.
+ */
+ protected Vector dirsNotIncluded;
+
+ /**
+ * The directories which matched at least one include and at least one
+ * exclude.
+ */
+ protected Vector dirsExcluded;
+
+ /**
+ * The files which matched at least one include and no excludes and
+ * which a selector discarded.
+ */
+ protected Vector filesDeselected;
+
+ /**
+ * The directories which matched at least one include and no excludes
+ * but which a selector discarded.
+ */
+ protected Vector dirsDeselected;
+
+ /**
+ * Whether or not our results were built by a slow scan.
+ */
+ protected boolean haveSlowResults = false;
+
+ /**
+ * Whether or not the file system should be treated as a case sensitive
+ * one.
+ */
+ protected boolean isCaseSensitive = true;
+
+ /**
+ * Whether or not symbolic links should be followed.
+ *
+ * @since Ant 1.5
+ */
+ private boolean followSymlinks = true;
+
+ /**
+ * Whether or not everything tested so far has been included.
+ */
+ protected boolean everythingIncluded = true;
+
+ /**
+ * Sole constructor.
+ */
+ public DirectoryScanner()
+ {
+ }
+
+ /**
+ * Tests whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ *
+ * This is not a general purpose test and should only be used if you
+ * can live with false positives. For example, pattern=**\a
+ * and str=b
will yield true
.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ protected static boolean matchPatternStart( String pattern, String str )
+ {
+ return SelectorUtils.matchPatternStart( pattern, str );
+ }
+
+ /**
+ * Tests whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ *
+ * This is not a general purpose test and should only be used if you
+ * can live with false positives. For example, pattern=**\a
+ * and str=b
will yield true
.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ protected static boolean matchPatternStart( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ return SelectorUtils.matchPatternStart( pattern, str, isCaseSensitive );
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @return true
if the pattern matches against the string,
+ * or false
otherwise.
+ */
+ protected static boolean matchPath( String pattern, String str )
+ {
+ return SelectorUtils.matchPath( pattern, str );
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return true
if the pattern matches against the string,
+ * or false
otherwise.
+ */
+ protected static boolean matchPath( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ return SelectorUtils.matchPath( pattern, str, isCaseSensitive );
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:
+ * '*' means zero or more characters
+ * '?' means one and only one character
+ *
+ * @param pattern The pattern to match against.
+ * Must not be null
.
+ * @param str The string which must be matched against the pattern.
+ * Must not be null
.
+ * @return true
if the string matches against the pattern,
+ * or false
otherwise.
+ */
+ public static boolean match( String pattern, String str )
+ {
+ return SelectorUtils.match( pattern, str );
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:
+ * '*' means zero or more characters
+ * '?' means one and only one character
+ *
+ * @param pattern The pattern to match against.
+ * Must not be null
.
+ * @param str The string which must be matched against the pattern.
+ * Must not be null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return true
if the string matches against the pattern,
+ * or false
otherwise.
+ */
+ protected static boolean match( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ return SelectorUtils.match( pattern, str, isCaseSensitive );
+ }
+
+ /**
+ * Sets the base directory to be scanned. This is the directory which is
+ * scanned recursively. All '/' and '\' characters are replaced by
+ * File.separatorChar
, so the separator used need not match
+ * File.separatorChar
.
+ *
+ * @param basedir The base directory to scan.
+ * Must not be null
.
+ */
+ public void setBasedir( String basedir )
+ {
+ setBasedir( new File( basedir.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ) ) );
+ }
+
+ /**
+ * Sets the base directory to be scanned. This is the directory which is
+ * scanned recursively.
+ *
+ * @param basedir The base directory for scanning.
+ * Should not be null
.
+ */
+ public void setBasedir( File basedir )
+ {
+ this.basedir = basedir;
+ }
+
+ /**
+ * Returns the base directory to be scanned.
+ * This is the directory which is scanned recursively.
+ *
+ * @return the base directory to be scanned
+ */
+ public File getBasedir()
+ {
+ return basedir;
+ }
+
+ /**
+ * Sets whether or not the file system should be regarded as case sensitive.
+ *
+ * @param isCaseSensitive whether or not the file system should be
+ * regarded as a case sensitive one
+ */
+ public void setCaseSensitive( boolean isCaseSensitive )
+ {
+ this.isCaseSensitive = isCaseSensitive;
+ }
+
+ /**
+ * Sets whether or not symbolic links should be followed.
+ *
+ * @param followSymlinks whether or not symbolic links should be followed
+ */
+ public void setFollowSymlinks( boolean followSymlinks )
+ {
+ this.followSymlinks = followSymlinks;
+ }
+
+ /**
+ * Sets the list of include patterns to use. All '/' and '\' characters
+ * are replaced by File.separatorChar
, so the separator used
+ * need not match File.separatorChar
.
+ *
+ * When a pattern ends with a '/' or '\', "**" is appended.
+ *
+ * @param includes A list of include patterns.
+ * May be null
, indicating that all files
+ * should be included. If a non-null
+ * list is given, all elements must be
+ * non-null
.
+ */
+ public void setIncludes( String[] includes )
+ {
+ if ( includes == null )
+ {
+ this.includes = null;
+ }
+ else
+ {
+ this.includes = new String[includes.length];
+ for ( int i = 0; i < includes.length; i++ )
+ {
+ String pattern;
+ pattern = includes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
+ if ( pattern.endsWith( File.separator ) )
+ {
+ pattern += "**";
+ }
+ this.includes[i] = pattern;
+ }
+ }
+ }
+
+
+ /**
+ * Sets the list of exclude patterns to use. All '/' and '\' characters
+ * are replaced by File.separatorChar
, so the separator used
+ * need not match File.separatorChar
.
+ *
+ * When a pattern ends with a '/' or '\', "**" is appended.
+ *
+ * @param excludes A list of exclude patterns.
+ * May be null
, indicating that no files
+ * should be excluded. If a non-null
list is
+ * given, all elements must be non-null
.
+ */
+ public void setExcludes( String[] excludes )
+ {
+ if ( excludes == null )
+ {
+ this.excludes = null;
+ }
+ else
+ {
+ this.excludes = new String[excludes.length];
+ for ( int i = 0; i < excludes.length; i++ )
+ {
+ String pattern;
+ pattern = excludes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
+ if ( pattern.endsWith( File.separator ) )
+ {
+ pattern += "**";
+ }
+ this.excludes[i] = pattern;
+ }
+ }
+ }
+
+ /**
+ * Returns whether or not the scanner has included all the files or
+ * directories it has come across so far.
+ *
+ * @return true
if all files and directories which have
+ * been found so far have been included.
+ */
+ public boolean isEverythingIncluded()
+ {
+ return everythingIncluded;
+ }
+
+ /**
+ * Scans the base directory for files which match at least one include
+ * pattern and don't match any exclude patterns. If there are selectors
+ * then the files must pass muster there, as well.
+ *
+ * @throws IllegalStateException if the base directory was set
+ * incorrectly (i.e. if it is null
, doesn't exist,
+ * or isn't a directory).
+ */
+ public void scan() throws IllegalStateException
+ {
+ if ( basedir == null )
+ {
+ throw new IllegalStateException( "No basedir set" );
+ }
+ if ( !basedir.exists() )
+ {
+ throw new IllegalStateException( "basedir " + basedir
+ + " does not exist" );
+ }
+ if ( !basedir.isDirectory() )
+ {
+ throw new IllegalStateException( "basedir " + basedir
+ + " is not a directory" );
+ }
+
+ if ( includes == null )
+ {
+ // No includes supplied, so set it to 'matches all'
+ includes = new String[1];
+ includes[0] = "**";
+ }
+ if ( excludes == null )
+ {
+ excludes = new String[0];
+ }
+
+ filesIncluded = new Vector();
+ filesNotIncluded = new Vector();
+ filesExcluded = new Vector();
+ filesDeselected = new Vector();
+ dirsIncluded = new Vector();
+ dirsNotIncluded = new Vector();
+ dirsExcluded = new Vector();
+ dirsDeselected = new Vector();
+
+ if ( isIncluded( "" ) )
+ {
+ if ( !isExcluded( "" ) )
+ {
+ if ( isSelected( "", basedir ) )
+ {
+ dirsIncluded.addElement( "" );
+ }
+ else
+ {
+ dirsDeselected.addElement( "" );
+ }
+ }
+ else
+ {
+ dirsExcluded.addElement( "" );
+ }
+ }
+ else
+ {
+ dirsNotIncluded.addElement( "" );
+ }
+ scandir( basedir, "", true );
+ }
+
+ /**
+ * Top level invocation for a slow scan. A slow scan builds up a full
+ * list of excluded/included files/directories, whereas a fast scan
+ * will only have full results for included files, as it ignores
+ * directories which can't possibly hold any included files/directories.
+ *
+ * Returns immediately if a slow scan has already been completed.
+ */
+ protected void slowScan()
+ {
+ if ( haveSlowResults )
+ {
+ return;
+ }
+
+ String[] excl = new String[dirsExcluded.size()];
+ dirsExcluded.copyInto( excl );
+
+ String[] notIncl = new String[dirsNotIncluded.size()];
+ dirsNotIncluded.copyInto( notIncl );
+
+ for ( int i = 0; i < excl.length; i++ )
+ {
+ if ( !couldHoldIncluded( excl[i] ) )
+ {
+ scandir( new File( basedir, excl[i] ),
+ excl[i] + File.separator, false );
+ }
+ }
+
+ for ( int i = 0; i < notIncl.length; i++ )
+ {
+ if ( !couldHoldIncluded( notIncl[i] ) )
+ {
+ scandir( new File( basedir, notIncl[i] ),
+ notIncl[i] + File.separator, false );
+ }
+ }
+
+ haveSlowResults = true;
+ }
+
+ /**
+ * Scans the given directory for files and directories. Found files and
+ * directories are placed in their respective collections, based on the
+ * matching of includes, excludes, and the selectors. When a directory
+ * is found, it is scanned recursively.
+ *
+ * @param dir The directory to scan. Must not be null
.
+ * @param vpath The path relative to the base directory (needed to
+ * prevent problems with an absolute path when using
+ * dir). Must not be null
.
+ * @param fast Whether or not this call is part of a fast scan.
+ * @see #filesIncluded
+ * @see #filesNotIncluded
+ * @see #filesExcluded
+ * @see #dirsIncluded
+ * @see #dirsNotIncluded
+ * @see #dirsExcluded
+ * @see #slowScan
+ */
+ protected void scandir( File dir, String vpath, boolean fast )
+ {
+ String[] newfiles = dir.list();
+
+ if ( newfiles == null )
+ {
+ /*
+ * two reasons are mentioned in the API docs for File.list
+ * (1) dir is not a directory. This is impossible as
+ * we wouldn't get here in this case.
+ * (2) an IO error occurred (why doesn't it throw an exception
+ * then???)
+ */
+ //throw new Exception( "IO error scanning directory " + dir.getAbsolutePath() );
+ }
+
+ if ( !followSymlinks )
+ {
+ Vector noLinks = new Vector();
+ for ( int i = 0; i < newfiles.length; i++ )
+ {
+ try
+ {
+ if ( isSymbolicLink( dir, newfiles[i] ) )
+ {
+ String name = vpath + newfiles[i];
+ File file = new File( dir, newfiles[i] );
+ if ( file.isDirectory() )
+ {
+ dirsExcluded.addElement( name );
+ }
+ else
+ {
+ filesExcluded.addElement( name );
+ }
+ }
+ else
+ {
+ noLinks.addElement( newfiles[i] );
+ }
+ }
+ catch ( IOException ioe )
+ {
+ String msg = "IOException caught while checking "
+ + "for links, couldn't get cannonical path!";
+ // will be caught and redirected to Ant's logging system
+ System.err.println( msg );
+ noLinks.addElement( newfiles[i] );
+ }
+ }
+ newfiles = new String[noLinks.size()];
+ noLinks.copyInto( newfiles );
+ }
+
+ for ( int i = 0; i < newfiles.length; i++ )
+ {
+ String name = vpath + newfiles[i];
+ File file = new File( dir, newfiles[i] );
+ if ( file.isDirectory() )
+ {
+ if ( isIncluded( name ) )
+ {
+ if ( !isExcluded( name ) )
+ {
+ if ( isSelected( name, file ) )
+ {
+ dirsIncluded.addElement( name );
+ if ( fast )
+ {
+ scandir( file, name + File.separator, fast );
+ }
+ }
+ else
+ {
+ everythingIncluded = false;
+ dirsDeselected.addElement( name );
+ if ( fast && couldHoldIncluded( name ) )
+ {
+ scandir( file, name + File.separator, fast );
+ }
+ }
+
+ }
+ else
+ {
+ everythingIncluded = false;
+ dirsExcluded.addElement( name );
+ if ( fast && couldHoldIncluded( name ) )
+ {
+ scandir( file, name + File.separator, fast );
+ }
+ }
+ }
+ else
+ {
+ everythingIncluded = false;
+ dirsNotIncluded.addElement( name );
+ if ( fast && couldHoldIncluded( name ) )
+ {
+ scandir( file, name + File.separator, fast );
+ }
+ }
+ if ( !fast )
+ {
+ scandir( file, name + File.separator, fast );
+ }
+ }
+ else if ( file.isFile() )
+ {
+ if ( isIncluded( name ) )
+ {
+ if ( !isExcluded( name ) )
+ {
+ if ( isSelected( name, file ) )
+ {
+ filesIncluded.addElement( name );
+ }
+ else
+ {
+ everythingIncluded = false;
+ filesDeselected.addElement( name );
+ }
+ }
+ else
+ {
+ everythingIncluded = false;
+ filesExcluded.addElement( name );
+ }
+ }
+ else
+ {
+ everythingIncluded = false;
+ filesNotIncluded.addElement( name );
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests whether or not a name matches against at least one include
+ * pattern.
+ *
+ * @param name The name to match. Must not be null
.
+ * @return true
when the name matches against at least one
+ * include pattern, or false
otherwise.
+ */
+ protected boolean isIncluded( String name )
+ {
+ for ( int i = 0; i < includes.length; i++ )
+ {
+ if ( matchPath( includes[i], name, isCaseSensitive ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether or not a name matches the start of at least one include
+ * pattern.
+ *
+ * @param name The name to match. Must not be null
.
+ * @return true
when the name matches against the start of at
+ * least one include pattern, or false
otherwise.
+ */
+ protected boolean couldHoldIncluded( String name )
+ {
+ for ( int i = 0; i < includes.length; i++ )
+ {
+ if ( matchPatternStart( includes[i], name, isCaseSensitive ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether or not a name matches against at least one exclude
+ * pattern.
+ *
+ * @param name The name to match. Must not be null
.
+ * @return true
when the name matches against at least one
+ * exclude pattern, or false
otherwise.
+ */
+ protected boolean isExcluded( String name )
+ {
+ for ( int i = 0; i < excludes.length; i++ )
+ {
+ if ( matchPath( excludes[i], name, isCaseSensitive ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether a name should be selected.
+ *
+ * @param name the filename to check for selecting
+ * @param file the java.io.File object for this filename
+ * @return false
when the selectors says that the file
+ * should not be selected, true
otherwise.
+ */
+ protected boolean isSelected( String name, File file )
+ {
+ return true;
+ }
+
+ /**
+ * Returns the names of the files which matched at least one of the
+ * include patterns and none of the exclude patterns.
+ * The names are relative to the base directory.
+ *
+ * @return the names of the files which matched at least one of the
+ * include patterns and none of the exclude patterns.
+ */
+ public String[] getIncludedFiles()
+ {
+ String[] files = new String[filesIncluded.size()];
+ filesIncluded.copyInto( files );
+ return files;
+ }
+
+ /**
+ * Returns the names of the files which matched none of the include
+ * patterns. The names are relative to the base directory. This involves
+ * performing a slow scan if one has not already been completed.
+ *
+ * @return the names of the files which matched none of the include
+ * patterns.
+ * @see #slowScan
+ */
+ public String[] getNotIncludedFiles()
+ {
+ slowScan();
+ String[] files = new String[filesNotIncluded.size()];
+ filesNotIncluded.copyInto( files );
+ return files;
+ }
+
+ /**
+ * Returns the names of the files which matched at least one of the
+ * include patterns and at least one of the exclude patterns.
+ * The names are relative to the base directory. This involves
+ * performing a slow scan if one has not already been completed.
+ *
+ * @return the names of the files which matched at least one of the
+ * include patterns and at at least one of the exclude patterns.
+ * @see #slowScan
+ */
+ public String[] getExcludedFiles()
+ {
+ slowScan();
+ String[] files = new String[filesExcluded.size()];
+ filesExcluded.copyInto( files );
+ return files;
+ }
+
+ /**
+ * Path-related methods
+ *
+ * /www/hosted/mysite/index.html
, can be broken into:
+ *
+ *
+ * There are also methods to {@link #catPath concatenate two paths}, {@link #resolveFile resolve a
+ * path relative to a File} and {@link #normalize} a path.
+ * /www/hosted/mysite/
-- retrievable through {@link #getPath}index.html
-- retrievable through {@link #removePath}/www/hosted/mysite/index
-- retrievable through {@link #removeExtension}html
-- retrievable through {@link #getExtension}
File
manager.
+ */
+ public static File getFile( String fileName )
+ {
+ return new File( fileName );
+ }
+
+ /**
+ * Given a directory and an array of extensions return an array of compliant files.
+ *
+ * The given extensions should be like "java" and not like ".java"
+ */
+ public static String[] getFilesFromExtension( String directory, String[] extensions )
+ {
+
+ Vector files = new Vector();
+
+ java.io.File currentDir = new java.io.File( directory );
+
+ String[] unknownFiles = currentDir.list();
+
+ if ( unknownFiles == null )
+ {
+ return new String[0];
+ }
+
+ for ( int i = 0; i < unknownFiles.length; ++i )
+ {
+ String currentFileName = directory + System.getProperty( "file.separator" ) + unknownFiles[i];
+ java.io.File currentFile = new java.io.File( currentFileName );
+
+ if ( currentFile.isDirectory() )
+ {
+
+ //ignore all CVS directories...
+ if ( currentFile.getName().equals( "CVS" ) )
+ {
+ continue;
+ }
+
+ //ok... transverse into this directory and get all the files... then combine
+ //them with the current list.
+
+ String[] fetchFiles = getFilesFromExtension( currentFileName, extensions );
+ files = blendFilesToVector( files, fetchFiles );
+
+ }
+ else
+ {
+ //ok... add the file
+
+ String add = currentFile.getAbsolutePath();
+ if ( isValidFile( add, extensions ) )
+ {
+ files.addElement( add );
+
+ }
+
+ }
+ }
+
+ //ok... move the Vector into the files list...
+
+ String[] foundFiles = new String[files.size()];
+ files.copyInto( foundFiles );
+
+ return foundFiles;
+
+ }
+
+
+ /**
+ * Private hepler method for getFilesFromExtension()
+ */
+ private static Vector blendFilesToVector( Vector v, String[] files )
+ {
+
+ for ( int i = 0; i < files.length; ++i )
+ {
+ v.addElement( files[i] );
+ }
+
+ return v;
+ }
+
+ /**
+ * Checks to see if a file is of a particular type(s).
+ * Note that if the file does not have an extension, an empty string
+ * ("") is matched for.
+ */
+ private static boolean isValidFile( String file, String[] extensions )
+ {
+
+ String extension = extension( file );
+ if ( extension == null )
+ {
+ extension = "";
+ }
+
+ //ok.. now that we have the "extension" go through the current know
+ //excepted extensions and determine if this one is OK.
+
+ for ( int i = 0; i < extensions.length; ++i )
+ {
+ if ( extensions[i].equals( extension ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Simple way to make a directory
+ */
+ public static void mkdir( String dir )
+ {
+ File file = new File( dir );
+ if ( !file.exists() )
+ {
+ file.mkdirs();
+ }
+ }
+
+ /**
+ * Compare the contents of two files to determine if they are equal or not.
+ *
+ * @param file1 the first file
+ * @param file2 the second file
+ * @return true if the content of the files are equal or they both don't exist, false otherwise
+ */
+ public static boolean contentEquals( final File file1, final File file2 )
+ throws IOException
+ {
+ final boolean file1Exists = file1.exists();
+ if ( file1Exists != file2.exists() )
+ {
+ return false;
+ }
+
+ if ( !file1Exists )
+ {
+ // two not existing files are equal
+ return true;
+ }
+
+ if ( file1.isDirectory() || file2.isDirectory() )
+ {
+ // don't want to compare directory contents
+ return false;
+ }
+
+ InputStream input1 = null;
+ InputStream input2 = null;
+ try
+ {
+ input1 = new FileInputStream( file1 );
+ input2 = new FileInputStream( file2 );
+ return IOUtil.contentEquals( input1, input2 );
+
+ }
+ finally
+ {
+ input1.close();
+ input2.close();
+ }
+ }
+
+ /**
+ * Convert from a URL
to a File
.
+ *
+ * @param url File URL.
+ * @return The equivalent File
object, or null
if the URL's protocol
+ * is not file
+ */
+ public static File toFile( final URL url )
+ {
+ if ( url.getProtocol().equals( "file" ) == false )
+ {
+ return null;
+ }
+ else
+ {
+ final String filename = url.getFile().replace( '/', File.separatorChar );
+ return new File( filename );
+ }
+ }
+
+ /**
+ * Convert the array of Files into a list of URLs.
+ *
+ * @param files the array of files
+ * @return the array of URLs
+ * @throws IOException if an error occurs
+ */
+ public static URL[] toURLs( final File[] files )
+ throws IOException
+ {
+ final URL[] urls = new URL[files.length];
+
+ for ( int i = 0; i < urls.length; i++ )
+ {
+ urls[i] = files[i].toURL();
+ }
+
+ return urls;
+ }
+
+ /**
+ * Remove extension from filename.
+ * ie
+ * + * foo.txt --> foo + * a\b\c.jpg --> a\b\c + * a\b\c --> a\b\c + *+ * + * @param filename the filename + * @return the filename minus extension + */ + public static String removeExtension( final String filename ) + { + final int index = filename.lastIndexOf( '.' ); + + if ( -1 == index ) + { + return filename; + } + else + { + return filename.substring( 0, index ); + } + } + + /** + * Get extension from filename. + * ie + *
+ * foo.txt --> "txt" + * a\b\c.jpg --> "jpg" + * a\b\c --> "" + *+ * + * @param filename the filename + * @return the extension of filename or "" if none + */ + public static String getExtension( final String filename ) + { + final int index = filename.lastIndexOf( '.' ); + + if ( -1 == index ) + { + return ""; + } + else + { + return filename.substring( index + 1 ); + } + } + + /** + * Remove path from filename. Equivalent to the unix command
basename
+ * ie.
+ * + * a/b/c.txt --> c.txt + * a.txt --> a.txt + *+ * + * @param filepath the filepath + * @return the filename minus path + */ + public static String removePath( final String filepath ) + { + return removePath( filepath, File.separatorChar ); + } + + /** + * Remove path from filename. + * ie. + *
+ * a/b/c.txt --> c.txt + * a.txt --> a.txt + *+ * + * @param filepath the filepath + * @return the filename minus path + */ + public static String removePath( final String filepath, final char fileSeparatorChar ) + { + final int index = filepath.lastIndexOf( fileSeparatorChar ); + + if ( -1 == index ) + { + return filepath; + } + else + { + return filepath.substring( index + 1 ); + } + } + + /** + * Get path from filename. Roughly equivalent to the unix command
dirname
.
+ * ie.
+ * + * a/b/c.txt --> a/b + * a.txt --> "" + *+ * + * @param filepath the filepath + * @return the filename minus path + */ + public static String getPath( final String filepath ) + { + return getPath( filepath, File.separatorChar ); + } + + /** + * Get path from filename. + * ie. + *
+ * a/b/c.txt --> a/b + * a.txt --> "" + *+ * + * @param filepath the filepath + * @return the filename minus path + */ + public static String getPath( final String filepath, final char fileSeparatorChar ) + { + final int index = filepath.lastIndexOf( fileSeparatorChar ); + if ( -1 == index ) + { + return ""; + } + else + { + return filepath.substring( 0, index ); + } + } + + /** + * Copy file from source to destination. If
destinationDirectory
does not exist, it
+ * (and any parent directories) will be created. If a file source
in
+ * destinationDirectory
exists, it will be overwritten.
+ *
+ * @param source An existing File
to copy.
+ * @param destinationDirectory A directory to copy source
into.
+ * @throws java.io.FileNotFoundException if source
isn't a normal file.
+ * @throws IllegalArgumentException if destinationDirectory
isn't a directory.
+ * @throws IOException if source
does not exist, the file in
+ * destinationDirectory
cannot be written to, or an IO error occurs during copying.
+ */
+ public static void copyFileToDirectory( final String source, final String destinationDirectory )
+ throws IOException
+ {
+ copyFileToDirectory( new File( source ), new File( destinationDirectory ) );
+ }
+
+ /**
+ * Copy file from source to destination. If destinationDirectory
does not exist, it
+ * (and any parent directories) will be created. If a file source
in
+ * destinationDirectory
exists, it will be overwritten.
+ *
+ * @param source An existing File
to copy.
+ * @param destinationDirectory A directory to copy source
into.
+ * @throws java.io.FileNotFoundException if source
isn't a normal file.
+ * @throws IllegalArgumentException if destinationDirectory
isn't a directory.
+ * @throws IOException if source
does not exist, the file in
+ * destinationDirectory
cannot be written to, or an IO error occurs during copying.
+ */
+ public static void copyFileToDirectory( final File source, final File destinationDirectory )
+ throws IOException
+ {
+ if ( destinationDirectory.exists() && !destinationDirectory.isDirectory() )
+ {
+ throw new IllegalArgumentException( "Destination is not a directory" );
+ }
+
+ copyFile( source, new File( destinationDirectory, source.getName() ) );
+ }
+
+ /**
+ * Copy file from source to destination. The directories up to destination
will be
+ * created if they don't already exist. destination
will be overwritten if it
+ * already exists.
+ *
+ * @param source An existing non-directory File
to copy bytes from.
+ * @param destination A non-directory File
to write bytes to (possibly
+ * overwriting).
+ * @throws IOException if source
does not exist, destination
cannot be
+ * written to, or an IO error occurs during copying.
+ * @throws java.io.FileNotFoundException if destination
is a directory
+ * (use {@link #copyFileToDirectory}).
+ */
+ public static void copyFile( final File source, final File destination )
+ throws IOException
+ {
+ //check source exists
+ if ( !source.exists() )
+ {
+ final String message = "File " + source + " does not exist";
+ throw new IOException( message );
+ }
+
+ //does destinations directory exist ?
+ if ( destination.getParentFile() != null && !destination.getParentFile().exists() )
+ {
+ destination.getParentFile().mkdirs();
+ }
+
+ //make sure we can write to destination
+ if ( destination.exists() && !destination.canWrite() )
+ {
+ final String message = "Unable to open file " + destination + " for writing.";
+ throw new IOException( message );
+ }
+
+ final FileInputStream input = new FileInputStream( source );
+ final FileOutputStream output = new FileOutputStream( destination );
+ IOUtil.copy( input, output );
+
+ input.close();
+ output.close();
+
+ if ( source.length() != destination.length() )
+ {
+ final String message = "Failed to copy full contents from " + source + " to " + destination;
+ throw new IOException( message );
+ }
+ }
+
+ /**
+ * Copies bytes from the URL source
to a file destination
.
+ * The directories up to destination
will be created if they don't already exist.
+ * destination
will be overwritten if it already exists.
+ *
+ * @param source A URL
to copy bytes from.
+ * @param destination A non-directory File
to write bytes to (possibly
+ * overwriting).
+ * @throws IOException if
+ * source
URL cannot be openeddestination
cannot be written tonull
if the ..'s went past the
+ * root.
+ * Eg:
+ * + * /foo// --> /foo/ + * /foo/./ --> /foo/ + * /foo/../bar --> /bar + * /foo/../bar/ --> /bar/ + * /foo/../bar/../baz --> /baz + * //foo//./bar --> /foo/bar + * /../ --> null + *+ * + * @param path the path to normalize + * @return the normalized String, or
null
if too many ..'s.
+ */
+ public static String normalize( final String path )
+ {
+ String normalized = path;
+ // Resolve occurrences of "//" in the normalized path
+ while ( true )
+ {
+ int index = normalized.indexOf( "//" );
+ if ( index < 0 )
+ {
+ break;
+ }
+ normalized = normalized.substring( 0, index ) + normalized.substring( index + 1 );
+ }
+
+ // Resolve occurrences of "/./" in the normalized path
+ while ( true )
+ {
+ int index = normalized.indexOf( "/./" );
+ if ( index < 0 )
+ {
+ break;
+ }
+ normalized = normalized.substring( 0, index ) + normalized.substring( index + 2 );
+ }
+
+ // Resolve occurrences of "/../" in the normalized path
+ while ( true )
+ {
+ int index = normalized.indexOf( "/../" );
+ if ( index < 0 )
+ {
+ break;
+ }
+ if ( index == 0 )
+ {
+ return null; // Trying to go outside our context
+ }
+ int index2 = normalized.lastIndexOf( '/', index - 1 );
+ normalized = normalized.substring( 0, index2 ) + normalized.substring( index + 3 );
+ }
+
+ // Return the normalized path that we have completed
+ return normalized;
+ }
+
+ /**
+ * Will concatenate 2 paths. Paths with ..
will be
+ * properly handled.
+ * Eg.,
+ * /a/b/c
+ d
= /a/b/d
+ * /a/b/c
+ ../d
= /a/d
+ *
filename
to it's canonical form. If filename
is
+ * relative (doesn't start with /
), it will be resolved relative to
+ * baseFile
, otherwise it is treated as a normal root-relative path.
+ *
+ * @param baseFile Where to resolve filename
from, if filename
is
+ * relative.
+ * @param filename Absolute or relative file path to resolve.
+ * @return The canonical File
of filename
.
+ */
+ public static File resolveFile( final File baseFile, String filename )
+ {
+ String filenm = filename;
+ if ( '/' != File.separatorChar )
+ {
+ filenm = filename.replace( '/', File.separatorChar );
+ }
+
+ if ( '\\' != File.separatorChar )
+ {
+ filenm = filename.replace( '\\', File.separatorChar );
+ }
+
+ // deal with absolute files
+ if ( filenm.startsWith( File.separator ) )
+ {
+ File file = new File( filenm );
+
+ try
+ {
+ file = file.getCanonicalFile();
+ }
+ catch ( final IOException ioe )
+ {
+ }
+
+ return file;
+ }
+ final char[] chars = filename.toCharArray();
+ final StringBuffer sb = new StringBuffer();
+
+ //remove duplicate file separators in succession - except
+ //on win32 at start of filename as UNC filenames can
+ //be \\AComputer\AShare\myfile.txt
+ int start = 0;
+ if ( '\\' == File.separatorChar )
+ {
+ sb.append( filenm.charAt( 0 ) );
+ start++;
+ }
+
+ for ( int i = start; i < chars.length; i++ )
+ {
+ final boolean doubleSeparator = File.separatorChar == chars[i] && File.separatorChar == chars[i - 1];
+
+ if ( !doubleSeparator )
+ {
+ sb.append( chars[i] );
+ }
+ }
+
+ filenm = sb.toString();
+
+ //must be relative
+ File file = ( new File( baseFile, filenm ) ).getAbsoluteFile();
+
+ try
+ {
+ file = file.getCanonicalFile();
+ }
+ catch ( final IOException ioe )
+ {
+ }
+
+ return file;
+ }
+
+ /**
+ * Delete a file. If file is directory delete it and all sub-directories.
+ */
+ public static void forceDelete( final String file )
+ throws IOException
+ {
+ forceDelete( new File( file ) );
+ }
+
+ /**
+ * Delete a file. If file is directory delete it and all sub-directories.
+ */
+ public static void forceDelete( final File file )
+ throws IOException
+ {
+ if ( !file.exists() )
+ {
+ return;
+ }
+
+ if ( file.isDirectory() )
+ {
+ deleteDirectory( file );
+ }
+ else
+ {
+ if ( !file.delete() )
+ {
+ final String message = "File " + file + " unable to be deleted.";
+ throw new IOException( message );
+ }
+ }
+ }
+
+ /**
+ * Schedule a file to be deleted when JVM exits.
+ * If file is directory delete it and all sub-directories.
+ */
+ public static void forceDeleteOnExit( final File file )
+ throws IOException
+ {
+ if ( !file.exists() )
+ {
+ return;
+ }
+
+ if ( file.isDirectory() )
+ {
+ deleteDirectoryOnExit( file );
+ }
+ else
+ {
+ file.deleteOnExit();
+ }
+ }
+
+ /**
+ * Recursively schedule directory for deletion on JVM exit.
+ */
+ private static void deleteDirectoryOnExit( final File directory )
+ throws IOException
+ {
+ if ( !directory.exists() )
+ {
+ return;
+ }
+
+ cleanDirectoryOnExit( directory );
+ directory.deleteOnExit();
+ }
+
+ /**
+ * Clean a directory without deleting it.
+ */
+ private static void cleanDirectoryOnExit( final File directory )
+ throws IOException
+ {
+ if ( !directory.exists() )
+ {
+ final String message = directory + " does not exist";
+ throw new IllegalArgumentException( message );
+ }
+
+ if ( !directory.isDirectory() )
+ {
+ final String message = directory + " is not a directory";
+ throw new IllegalArgumentException( message );
+ }
+
+ IOException exception = null;
+
+ final File[] files = directory.listFiles();
+ for ( int i = 0; i < files.length; i++ )
+ {
+ final File file = files[i];
+ try
+ {
+ forceDeleteOnExit( file );
+ }
+ catch ( final IOException ioe )
+ {
+ exception = ioe;
+ }
+ }
+
+ if ( null != exception )
+ {
+ throw exception;
+ }
+ }
+
+
+ /**
+ * Make a directory. If there already exists a file with specified name or
+ * the directory is unable to be created then an exception is thrown.
+ */
+ public static void forceMkdir( final File file )
+ throws IOException
+ {
+ if ( file.exists() )
+ {
+ if ( file.isFile() )
+ {
+ final String message = "File " + file + " exists and is " +
+ "not a directory. Unable to create directory.";
+ throw new IOException( message );
+ }
+ }
+ else
+ {
+ if ( false == file.mkdirs() )
+ {
+ final String message = "Unable to create directory " + file;
+ throw new IOException( message );
+ }
+ }
+ }
+
+ /**
+ * Recursively delete a directory.
+ */
+ public static void deleteDirectory( final String directory )
+ throws IOException
+ {
+ deleteDirectory( new File( directory ) );
+ }
+
+ /**
+ * Recursively delete a directory.
+ */
+ public static void deleteDirectory( final File directory )
+ throws IOException
+ {
+ if ( !directory.exists() )
+ {
+ return;
+ }
+
+ cleanDirectory( directory );
+ if ( !directory.delete() )
+ {
+ final String message = "Directory " + directory + " unable to be deleted.";
+ throw new IOException( message );
+ }
+ }
+
+ /**
+ * Clean a directory without deleting it.
+ */
+ public static void cleanDirectory( final String directory )
+ throws IOException
+ {
+ cleanDirectory( new File( directory ) );
+ }
+
+ /**
+ * Clean a directory without deleting it.
+ */
+ public static void cleanDirectory( final File directory )
+ throws IOException
+ {
+ if ( !directory.exists() )
+ {
+ final String message = directory + " does not exist";
+ throw new IllegalArgumentException( message );
+ }
+
+ if ( !directory.isDirectory() )
+ {
+ final String message = directory + " is not a directory";
+ throw new IllegalArgumentException( message );
+ }
+
+ IOException exception = null;
+
+ final File[] files = directory.listFiles();
+ for ( int i = 0; i < files.length; i++ )
+ {
+ final File file = files[i];
+ try
+ {
+ forceDelete( file );
+ }
+ catch ( final IOException ioe )
+ {
+ exception = ioe;
+ }
+ }
+
+ if ( null != exception )
+ {
+ throw exception;
+ }
+ }
+
+ /**
+ * Recursively count size of a directory.
+ *
+ * @return size of directory in bytes.
+ */
+ public static long sizeOfDirectory( final String directory )
+ {
+ return sizeOfDirectory( new File( directory ) );
+ }
+
+ /**
+ * Recursively count size of a directory.
+ *
+ * @return size of directory in bytes.
+ */
+ public static long sizeOfDirectory( final File directory )
+ {
+ if ( !directory.exists() )
+ {
+ final String message = directory + " does not exist";
+ throw new IllegalArgumentException( message );
+ }
+
+ if ( !directory.isDirectory() )
+ {
+ final String message = directory + " is not a directory";
+ throw new IllegalArgumentException( message );
+ }
+
+ long size = 0;
+
+ final File[] files = directory.listFiles();
+ for ( int i = 0; i < files.length; i++ )
+ {
+ final File file = files[i];
+
+ if ( file.isDirectory() )
+ {
+ size += sizeOfDirectory( file );
+ }
+ else
+ {
+ size += file.length();
+ }
+ }
+
+ return size;
+ }
+
+ public static List getFiles( File directory, String includes, String excludes )
+ throws IOException
+ {
+ return getFiles( directory, includes, excludes, true );
+ }
+
+ public static List getFiles( File directory, String includes, String excludes, boolean includeBasedir )
+ throws IOException
+ {
+ List fileNames = getFileNames( directory, includes, excludes, includeBasedir );
+
+ List files = new ArrayList();
+
+ for ( Iterator i = fileNames.iterator(); i.hasNext(); )
+ {
+ files.add( new File( (String) i.next() ) );
+ }
+
+ return files;
+ }
+
+ public static String FS = System.getProperty( "file.separator" );
+
+ public static List getFileNames( File directory, String includes, String excludes, boolean includeBasedir )
+ throws IOException
+ {
+ DirectoryScanner scanner = new DirectoryScanner();
+
+ scanner.setBasedir( directory );
+
+ if ( includes != null )
+ {
+ scanner.setIncludes( StringUtils.split( includes, "," ) );
+ }
+
+ if ( excludes != null )
+ {
+ scanner.setExcludes( StringUtils.split( excludes, "," ) );
+ }
+
+ scanner.scan();
+
+ String[] files = scanner.getIncludedFiles();
+
+ List list = new ArrayList();
+
+ for ( int i = 0; i < files.length; i++ )
+ {
+ if ( includeBasedir )
+ {
+ list.add( directory + FS + files[i] );
+ }
+ else
+ {
+ list.add( files[i] );
+ }
+ }
+
+ return list;
+ }
+
+ public static void copyDirectory( File sourceDirectory, File destinationDirectory )
+ throws IOException
+ {
+ copyDirectory( sourceDirectory, destinationDirectory, "**", null );
+ }
+
+ public static void copyDirectory( File sourceDirectory, File destinationDirectory, String includes,
+ String excludes )
+ throws IOException
+ {
+ if ( !sourceDirectory.exists() )
+ {
+ return;
+ }
+
+ List files = getFiles( sourceDirectory, includes, excludes );
+
+ for ( Iterator i = files.iterator(); i.hasNext(); )
+ {
+ File file = (File) i.next();
+
+ copyFileToDirectory( file, destinationDirectory );
+ }
+ }
+
+ /**
+ * Copies a entire directory structure.
+ *
+ * Note:
+ * sourceDirectory
must exists.
+ * This will remove to
(if it exists), ensure that
+ * to
's parent directory exists and move
+ * from
, which involves deleting from
as
+ * well.
to
may have been deleted
+ * already when this happens.
+ */
+ public static void rename( File from, File to )
+ throws IOException
+ {
+ if ( to.exists() && !to.delete() )
+ {
+ throw new IOException( "Failed to delete " + to + " while trying to rename " + from );
+ }
+
+ File parent = to.getParentFile();
+ if ( parent != null && !parent.exists() && !parent.mkdirs() )
+ {
+ throw new IOException( "Failed to create directory " + parent + " while trying to rename " + from );
+ }
+
+ if ( !from.renameTo( to ) )
+ {
+ copyFile( from, to );
+ if ( !from.delete() )
+ {
+ throw new IOException( "Failed to delete " + from + " while trying to rename it." );
+ }
+ }
+ }
+
+ /**
+ * Create a temporary file in a given directory.
+ *
+ * The file denoted by the returned abstract pathname did not + * exist before this method was invoked, any subsequent invocation + * of this method will yield a different file name.
+ * + * The filename is prefixNNNNNsuffix where NNNN is a random number + * + *This method is different to File.createTempFile of JDK 1.2 + * as it doesn't create the file itself. + * It uses the location pointed to by java.io.tmpdir + * when the parentDir attribute is + * null.
+ * + * @param prefix prefix before the random number + * @param suffix file extension; include the '.' + * @param parentDir Directory to create the temporary file in - + * java.io.tmpdir used if not specificed + * @return a File reference to the new temporary file. + */ + public static File createTempFile( String prefix, String suffix, File parentDir ) + { + + File result = null; + String parent = System.getProperty( "java.io.tmpdir" ); + if ( parentDir != null ) + { + parent = parentDir.getPath(); + } + DecimalFormat fmt = new DecimalFormat( "#####" ); + Random rand = new Random( System.currentTimeMillis() + Runtime.getRuntime().freeMemory() ); + synchronized ( rand ) + { + do + { + result = new File( parent, prefix + fmt.format( Math.abs( rand.nextInt() ) ) + suffix ); + } + while ( result.exists() ); + } + return result; + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IOUtil.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IOUtil.java new file mode 100644 index 0000000000..15ed8ceeb1 --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IOUtil.java @@ -0,0 +1,782 @@ +package org.apache.maven.bootstrap.util; + +/* + * Copyright 2001-2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; + +/** + * General IO Stream manipulation. + * + * This class provides static utility methods for input/output operations, particularly buffered + * copying between sources (InputStream
, Reader
, String
and
+ * byte[]
) and destinations (OutputStream
, Writer
,
+ * String
and byte[]
).
+ *
+ *
+ * Unless otherwise noted, these copy
methods do not flush or close the
+ * streams. Often, doing so would require making non-portable assumptions about the streams' origin
+ * and further use. This means that both streams' close()
methods must be called after
+ * copying. if one omits this step, then the stream resources (sockets, file descriptors) are
+ * released when the associated Stream is garbage-collected. It is not a good idea to rely on this
+ * mechanism. For a good overview of the distinction between "memory management" and "resource
+ * management", see this
+ * UnixReview article
For each copy
method, a variant is provided that allows the caller to specify the
+ * buffer size (the default is 4k). As the buffer size can have a fairly large impact on speed, this
+ * may be worth tweaking. Often "large buffer -> faster" does not hold, even for large data
+ * transfers.
For byte-to-char methods, a copy
variant allows the encoding to be selected
+ * (otherwise the platform default is used).
The copy
methods use an internal buffer when copying. It is therefore advisable
+ * not to deliberately wrap the stream arguments to the copy
methods in
+ * Buffered*
streams. For example, don't do the
+ * following:
copy( new BufferedInputStream( in ), new BufferedOutputStream( out ) );
+ *
+ * The rationale is as follows:
+ * + *Imagine that an InputStream's read() is a very expensive operation, which would usually suggest
+ * wrapping in a BufferedInputStream. The BufferedInputStream works by issuing infrequent
+ * {@link java.io.InputStream#read(byte[] b, int off, int len)} requests on the underlying InputStream, to
+ * fill an internal buffer, from which further read
requests can inexpensively get
+ * their data (until the buffer runs out).
However, the copy
methods do the same thing, keeping an internal buffer,
+ * populated by {@link InputStream#read(byte[] b, int off, int len)} requests. Having two buffers
+ * (or three if the destination stream is also buffered) is pointless, and the unnecessary buffer
+ * management hurts performance slightly (about 3%, according to some simple experiments).
InputStream
to an OutputStream
.
+ */
+ public static void copy( final InputStream input, final OutputStream output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy bytes from an InputStream
to an OutputStream
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final InputStream input, final OutputStream output, final int bufferSize )
+ throws IOException
+ {
+ final byte[] buffer = new byte[bufferSize];
+ int n = 0;
+ while ( -1 != ( n = input.read( buffer ) ) )
+ {
+ output.write( buffer, 0, n );
+ }
+ }
+
+ /**
+ * Copy chars from a Reader
to a Writer
.
+ */
+ public static void copy( final Reader input, final Writer output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy chars from a Reader
to a Writer
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final Reader input, final Writer output, final int bufferSize )
+ throws IOException
+ {
+ final char[] buffer = new char[bufferSize];
+ int n = 0;
+ while ( -1 != ( n = input.read( buffer ) ) )
+ {
+ output.write( buffer, 0, n );
+ }
+ output.flush();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // InputStream -> *
+ ///////////////////////////////////////////////////////////////
+
+
+ ///////////////////////////////////////////////////////////////
+ // InputStream -> Writer
+
+ /**
+ * Copy and convert bytes from an InputStream
to chars on a
+ * Writer
.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ */
+ public static void copy( final InputStream input, final Writer output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy and convert bytes from an InputStream
to chars on a
+ * Writer
.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final InputStream input, final Writer output, final int bufferSize )
+ throws IOException
+ {
+ final InputStreamReader in = new InputStreamReader( input );
+ copy( in, output, bufferSize );
+ }
+
+ /**
+ * Copy and convert bytes from an InputStream
to chars on a
+ * Writer
, using the specified encoding.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ */
+ public static void copy( final InputStream input, final Writer output, final String encoding )
+ throws IOException
+ {
+ final InputStreamReader in = new InputStreamReader( input, encoding );
+ copy( in, output );
+ }
+
+ /**
+ * Copy and convert bytes from an InputStream
to chars on a
+ * Writer
, using the specified encoding.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final InputStream input, final Writer output, final String encoding, final int bufferSize )
+ throws IOException
+ {
+ final InputStreamReader in = new InputStreamReader( input, encoding );
+ copy( in, output, bufferSize );
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // InputStream -> String
+
+ /**
+ * Get the contents of an InputStream
as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ */
+ public static String toString( final InputStream input )
+ throws IOException
+ {
+ return toString( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of an InputStream
as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static String toString( final InputStream input, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, bufferSize );
+ return sw.toString();
+ }
+
+ /**
+ * Get the contents of an InputStream
as a String.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ */
+ public static String toString( final InputStream input, final String encoding )
+ throws IOException
+ {
+ return toString( input, encoding, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of an InputStream
as a String.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static String toString( final InputStream input, final String encoding, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, encoding, bufferSize );
+ return sw.toString();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // InputStream -> byte[]
+
+ /**
+ * Get the contents of an InputStream
as a byte[]
.
+ */
+ public static byte[] toByteArray( final InputStream input )
+ throws IOException
+ {
+ return toByteArray( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of an InputStream
as a byte[]
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static byte[] toByteArray( final InputStream input, final int bufferSize )
+ throws IOException
+ {
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy( input, output, bufferSize );
+ return output.toByteArray();
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // Reader -> *
+ ///////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////
+ // Reader -> OutputStream
+ /**
+ * Serialize chars from a Reader
to bytes on an OutputStream
, and
+ * flush the OutputStream
.
+ */
+ public static void copy( final Reader input, final OutputStream output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Serialize chars from a Reader
to bytes on an OutputStream
, and
+ * flush the OutputStream
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final Reader input, final OutputStream output, final int bufferSize )
+ throws IOException
+ {
+ final OutputStreamWriter out = new OutputStreamWriter( output );
+ copy( input, out, bufferSize );
+ // NOTE: Unless anyone is planning on rewriting OutputStreamWriter, we have to flush
+ // here.
+ out.flush();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Reader -> String
+ /**
+ * Get the contents of a Reader
as a String.
+ */
+ public static String toString( final Reader input )
+ throws IOException
+ {
+ return toString( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a Reader
as a String.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static String toString( final Reader input, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, bufferSize );
+ return sw.toString();
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Reader -> byte[]
+ /**
+ * Get the contents of a Reader
as a byte[]
.
+ */
+ public static byte[] toByteArray( final Reader input )
+ throws IOException
+ {
+ return toByteArray( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a Reader
as a byte[]
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static byte[] toByteArray( final Reader input, final int bufferSize )
+ throws IOException
+ {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy( input, output, bufferSize );
+ return output.toByteArray();
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // String -> *
+ ///////////////////////////////////////////////////////////////
+
+
+ ///////////////////////////////////////////////////////////////
+ // String -> OutputStream
+
+ /**
+ * Serialize chars from a String
to bytes on an OutputStream
, and
+ * flush the OutputStream
.
+ */
+ public static void copy( final String input, final OutputStream output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Serialize chars from a String
to bytes on an OutputStream
, and
+ * flush the OutputStream
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final String input, final OutputStream output, final int bufferSize )
+ throws IOException
+ {
+ final StringReader in = new StringReader( input );
+ final OutputStreamWriter out = new OutputStreamWriter( output );
+ copy( in, out, bufferSize );
+ // NOTE: Unless anyone is planning on rewriting OutputStreamWriter, we have to flush
+ // here.
+ out.flush();
+ }
+
+
+
+ ///////////////////////////////////////////////////////////////
+ // String -> Writer
+
+ /**
+ * Copy chars from a String
to a Writer
.
+ */
+ public static void copy( final String input, final Writer output )
+ throws IOException
+ {
+ output.write( input );
+ }
+
+ /**
+ * Copy bytes from an InputStream
to an
+ * OutputStream
, with buffering.
+ * This is equivalent to passing a
+ * {@link java.io.BufferedInputStream} and
+ * {@link java.io.BufferedOutputStream} to {@link #copy(InputStream, OutputStream)},
+ * and flushing the output stream afterwards. The streams are not closed
+ * after the copy.
+ *
+ * @deprecated Buffering streams is actively harmful! See the class description as to why. Use
+ * {@link #copy(InputStream, OutputStream)} instead.
+ */
+ public static void bufferedCopy( final InputStream input, final OutputStream output )
+ throws IOException
+ {
+ final BufferedInputStream in = new BufferedInputStream( input );
+ final BufferedOutputStream out = new BufferedOutputStream( output );
+ copy( in, out );
+ out.flush();
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // String -> byte[]
+ /**
+ * Get the contents of a String
as a byte[]
.
+ */
+ public static byte[] toByteArray( final String input )
+ throws IOException
+ {
+ return toByteArray( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a String
as a byte[]
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static byte[] toByteArray( final String input, final int bufferSize )
+ throws IOException
+ {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy( input, output, bufferSize );
+ return output.toByteArray();
+ }
+
+
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // byte[] -> *
+ ///////////////////////////////////////////////////////////////
+
+
+ ///////////////////////////////////////////////////////////////
+ // byte[] -> Writer
+
+ /**
+ * Copy and convert bytes from a byte[]
to chars on a
+ * Writer
.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ */
+ public static void copy( final byte[] input, final Writer output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy and convert bytes from a byte[]
to chars on a
+ * Writer
.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final byte[] input, final Writer output, final int bufferSize )
+ throws IOException
+ {
+ final ByteArrayInputStream in = new ByteArrayInputStream( input );
+ copy( in, output, bufferSize );
+ }
+
+ /**
+ * Copy and convert bytes from a byte[]
to chars on a
+ * Writer
, using the specified encoding.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ */
+ public static void copy( final byte[] input, final Writer output, final String encoding )
+ throws IOException
+ {
+ final ByteArrayInputStream in = new ByteArrayInputStream( input );
+ copy( in, output, encoding );
+ }
+
+ /**
+ * Copy and convert bytes from a byte[]
to chars on a
+ * Writer
, using the specified encoding.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final byte[] input, final Writer output, final String encoding, final int bufferSize )
+ throws IOException
+ {
+ final ByteArrayInputStream in = new ByteArrayInputStream( input );
+ copy( in, output, encoding, bufferSize );
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // byte[] -> String
+
+ /**
+ * Get the contents of a byte[]
as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ */
+ public static String toString( final byte[] input )
+ throws IOException
+ {
+ return toString( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a byte[]
as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static String toString( final byte[] input, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, bufferSize );
+ return sw.toString();
+ }
+
+ /**
+ * Get the contents of a byte[]
as a String.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ */
+ public static String toString( final byte[] input, final String encoding )
+ throws IOException
+ {
+ return toString( input, encoding, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a byte[]
as a String.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static String toString( final byte[] input, final String encoding, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, encoding, bufferSize );
+ return sw.toString();
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // byte[] -> OutputStream
+
+ /**
+ * Copy bytes from a byte[]
to an OutputStream
.
+ */
+ public static void copy( final byte[] input, final OutputStream output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy bytes from a byte[]
to an OutputStream
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final byte[] input, final OutputStream output, final int bufferSize )
+ throws IOException
+ {
+ output.write( input );
+ }
+
+ /**
+ * Compare the contents of two Streams to determine if they are equal or not.
+ *
+ * @param input1 the first stream
+ * @param input2 the second stream
+ * @return true if the content of the streams are equal or they both don't exist, false otherwise
+ */
+ public static boolean contentEquals( final InputStream input1, final InputStream input2 )
+ throws IOException
+ {
+ final InputStream bufferedInput1 = new BufferedInputStream( input1 );
+ final InputStream bufferedInput2 = new BufferedInputStream( input2 );
+
+ int ch = bufferedInput1.read();
+ while ( -1 != ch )
+ {
+ final int ch2 = bufferedInput2.read();
+ if ( ch != ch2 )
+ {
+ return false;
+ }
+ ch = bufferedInput1.read();
+ }
+
+ final int ch2 = bufferedInput2.read();
+ if ( -1 != ch2 )
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // closeXXX()
+ // ----------------------------------------------------------------------
+
+ /**
+ * Closes the input stream. The input stream can be null and any IOException's will be swallowed.
+ *
+ * @param inputStream The stream to close.
+ */
+ public static void close( InputStream inputStream )
+ {
+ if ( inputStream == null )
+ {
+ return;
+ }
+
+ try
+ {
+ inputStream.close();
+ }
+ catch ( IOException ex )
+ {
+ // ignore
+ }
+ }
+
+ /**
+ * Closes the output stream. The output stream can be null and any IOException's will be swallowed.
+ *
+ * @param outputStream The stream to close.
+ */
+ public static void close( OutputStream outputStream )
+ {
+ if ( outputStream == null )
+ {
+ return;
+ }
+
+ try
+ {
+ outputStream.close();
+ }
+ catch ( IOException ex )
+ {
+ // ignore
+ }
+ }
+
+ /**
+ * Closes the reader. The reader can be null and any IOException's will be swallowed.
+ *
+ * @param reader The reader to close.
+ */
+ public static void close( Reader reader )
+ {
+ if ( reader == null )
+ {
+ return;
+ }
+
+ try
+ {
+ reader.close();
+ }
+ catch ( IOException ex )
+ {
+ // ignore
+ }
+ }
+
+ /**
+ * Closes the writer. The writer can be null and any IOException's will be swallowed.
+ *
+ * @param wrtier The writer to close.
+ */
+ public static void close( Writer writer )
+ {
+ if ( writer == null )
+ {
+ return;
+ }
+
+ try
+ {
+ writer.close();
+ }
+ catch ( IOException ex )
+ {
+ // ignore
+ }
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IsolatedClassLoader.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IsolatedClassLoader.java
new file mode 100644
index 0000000000..e2a0816793
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IsolatedClassLoader.java
@@ -0,0 +1,74 @@
+package org.apache.maven.bootstrap.util;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class IsolatedClassLoader
+ extends URLClassLoader
+{
+ private ClassLoader parent;
+
+ public IsolatedClassLoader()
+ {
+ this( ClassLoader.getSystemClassLoader() );
+ }
+
+ public IsolatedClassLoader( ClassLoader parent )
+ {
+ super( new URL[0] );
+ this.parent = parent;
+ }
+
+ public void addURL( URL url )
+ {
+ super.addURL( url );
+ }
+
+ public synchronized Class loadClass( String className )
+ throws ClassNotFoundException
+ {
+ Class c = findLoadedClass( className );
+
+ ClassNotFoundException ex = null;
+
+ if ( c == null )
+ {
+ try
+ {
+ c = findClass( className );
+ }
+ catch ( ClassNotFoundException e )
+ {
+ ex = e;
+
+ if ( parent != null )
+ {
+ c = parent.loadClass( className );
+ }
+ }
+ }
+
+ if ( c == null )
+ {
+ throw ex;
+ }
+
+ return c;
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/JarMojo.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/JarMojo.java
new file mode 100644
index 0000000000..d440550613
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/JarMojo.java
@@ -0,0 +1,221 @@
+package org.apache.maven.bootstrap.util;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+public class JarMojo
+{
+ private byte[] buffer = new byte[4096];
+
+ public void execute( File basedir, File jarFile )
+ throws Exception
+ {
+ Map includes = new LinkedHashMap();
+
+ addDirectory( includes, "**/**", "**/package.html,**/.svn/**", "", basedir );
+
+ createJar( jarFile, includes );
+ }
+
+ /**
+ * Add all files in the specified directory to the archive.
+ *
+ * @param includes a map This is a utility class used by selectors and DirectoryScanner. The + * functionality more properly belongs just to selectors, but unfortunately + * DirectoryScanner exposed these as protected methods. Thus we have to + * support any subclasses of DirectoryScanner that may access these methods. + *
+ *This is a Singleton.
+ * + * @author Arnout J. Kuiper + * ajkuiper@wxs.nl + * @author Magesh Umasankar + * @author Bruce Atherton + * @since 1.5 + */ +public final class SelectorUtils +{ + + private static SelectorUtils instance = new SelectorUtils(); + + /** + * Private Constructor + */ + private SelectorUtils() + { + } + + /** + * Retrieves the manager of the Singleton. + */ + public static SelectorUtils getInstance() + { + return instance; + } + + /** + * Tests whether or not a given path matches the start of a given + * pattern up to the first "**". + * + * This is not a general purpose test and should only be used if you + * can live with false positives. For example,pattern=**\a
+ * and str=b
will yield true
.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ public static boolean matchPatternStart( String pattern, String str )
+ {
+ return matchPatternStart( pattern, str, true );
+ }
+
+ /**
+ * Tests whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ *
+ * This is not a general purpose test and should only be used if you
+ * can live with false positives. For example, pattern=**\a
+ * and str=b
will yield true
.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ public static boolean matchPatternStart( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ // When str starts with a File.separator, pattern has to start with a
+ // File.separator.
+ // When pattern starts with a File.separator, str has to start with a
+ // File.separator.
+ if ( str.startsWith( File.separator ) !=
+ pattern.startsWith( File.separator ) )
+ {
+ return false;
+ }
+
+ Vector patDirs = tokenizePath( pattern );
+ Vector strDirs = tokenizePath( str );
+
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.size() - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strDirs.size() - 1;
+
+ // up to first '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = (String) patDirs.elementAt( patIdxStart );
+ if ( patDir.equals( "**" ) )
+ {
+ break;
+ }
+ if ( !match( patDir, (String) strDirs.elementAt( strIdxStart ),
+ isCaseSensitive ) )
+ {
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+
+ if ( strIdxStart > strIdxEnd )
+ {
+ // String is exhausted
+ return true;
+ }
+ else if ( patIdxStart > patIdxEnd )
+ {
+ // String not exhausted, but pattern is. Failure.
+ return false;
+ }
+ else
+ {
+ // pattern now holds ** while string is not exhausted
+ // this will generate false positives but we can live with that.
+ return true;
+ }
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @return true
if the pattern matches against the string,
+ * or false
otherwise.
+ */
+ public static boolean matchPath( String pattern, String str )
+ {
+ return matchPath( pattern, str, true );
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return true
if the pattern matches against the string,
+ * or false
otherwise.
+ */
+ public static boolean matchPath( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ // When str starts with a File.separator, pattern has to start with a
+ // File.separator.
+ // When pattern starts with a File.separator, str has to start with a
+ // File.separator.
+ if ( str.startsWith( File.separator ) !=
+ pattern.startsWith( File.separator ) )
+ {
+ return false;
+ }
+
+ Vector patDirs = tokenizePath( pattern );
+ Vector strDirs = tokenizePath( str );
+
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.size() - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strDirs.size() - 1;
+
+ // up to first '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = (String) patDirs.elementAt( patIdxStart );
+ if ( patDir.equals( "**" ) )
+ {
+ break;
+ }
+ if ( !match( patDir, (String) strDirs.elementAt( strIdxStart ),
+ isCaseSensitive ) )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // String is exhausted
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !patDirs.elementAt( i ).equals( "**" ) )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+ return true;
+ }
+ else
+ {
+ if ( patIdxStart > patIdxEnd )
+ {
+ // String not exhausted, but pattern is. Failure.
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+
+ // up to last '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = (String) patDirs.elementAt( patIdxEnd );
+ if ( patDir.equals( "**" ) )
+ {
+ break;
+ }
+ if ( !match( patDir, (String) strDirs.elementAt( strIdxEnd ),
+ isCaseSensitive ) )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // String is exhausted
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !patDirs.elementAt( i ).equals( "**" ) )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ int patIdxTmp = -1;
+ for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
+ {
+ if ( patDirs.elementAt( i ).equals( "**" ) )
+ {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if ( patIdxTmp == patIdxStart + 1 )
+ {
+ // '**/**' situation, so skip one
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = ( patIdxTmp - patIdxStart - 1 );
+ int strLength = ( strIdxEnd - strIdxStart + 1 );
+ int foundIdx = -1;
+ strLoop:
+ for ( int i = 0; i <= strLength - patLength; i++ )
+ {
+ for ( int j = 0; j < patLength; j++ )
+ {
+ String subPat = (String) patDirs.elementAt( patIdxStart + j + 1 );
+ String subStr = (String) strDirs.elementAt( strIdxStart + i + j );
+ if ( !match( subPat, subStr, isCaseSensitive ) )
+ {
+ continue strLoop;
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if ( foundIdx == -1 )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !patDirs.elementAt( i ).equals( "**" ) )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:null
.
+ * @param str The string which must be matched against the pattern.
+ * Must not be null
.
+ * @return true
if the string matches against the pattern,
+ * or false
otherwise.
+ */
+ public static boolean match( String pattern, String str )
+ {
+ return match( pattern, str, true );
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:null
.
+ * @param str The string which must be matched against the pattern.
+ * Must not be null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return true
if the string matches against the pattern,
+ * or false
otherwise.
+ */
+ public static boolean match( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ char[] patArr = pattern.toCharArray();
+ char[] strArr = str.toCharArray();
+ int patIdxStart = 0;
+ int patIdxEnd = patArr.length - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strArr.length - 1;
+ char ch;
+
+ boolean containsStar = false;
+ for ( int i = 0; i < patArr.length; i++ )
+ {
+ if ( patArr[i] == '*' )
+ {
+ containsStar = true;
+ break;
+ }
+ }
+
+ if ( !containsStar )
+ {
+ // No '*'s, so we make a shortcut
+ if ( patIdxEnd != strIdxEnd )
+ {
+ return false; // Pattern and string do not have the same size
+ }
+ for ( int i = 0; i <= patIdxEnd; i++ )
+ {
+ ch = patArr[i];
+ if ( ch != '?' )
+ {
+ if ( isCaseSensitive && ch != strArr[i] )
+ {
+ return false;// Character mismatch
+ }
+ if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
+ Character.toUpperCase( strArr[i] ) )
+ {
+ return false; // Character mismatch
+ }
+ }
+ }
+ return true; // String matches against pattern
+ }
+
+ if ( patIdxEnd == 0 )
+ {
+ return true; // Pattern contains only '*', which matches anything
+ }
+
+ // Process characters before first star
+ while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
+ {
+ if ( ch != '?' )
+ {
+ if ( isCaseSensitive && ch != strArr[strIdxStart] )
+ {
+ return false;// Character mismatch
+ }
+ if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
+ Character.toUpperCase( strArr[strIdxStart] ) )
+ {
+ return false;// Character mismatch
+ }
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] != '*' )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Process characters after last star
+ while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
+ {
+ if ( ch != '?' )
+ {
+ if ( isCaseSensitive && ch != strArr[strIdxEnd] )
+ {
+ return false;// Character mismatch
+ }
+ if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
+ Character.toUpperCase( strArr[strIdxEnd] ) )
+ {
+ return false;// Character mismatch
+ }
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] != '*' )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // process pattern between stars. padIdxStart and patIdxEnd point
+ // always to a '*'.
+ while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ int patIdxTmp = -1;
+ for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] == '*' )
+ {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if ( patIdxTmp == patIdxStart + 1 )
+ {
+ // Two stars next to each other, skip the first one.
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = ( patIdxTmp - patIdxStart - 1 );
+ int strLength = ( strIdxEnd - strIdxStart + 1 );
+ int foundIdx = -1;
+ strLoop:
+ for ( int i = 0; i <= strLength - patLength; i++ )
+ {
+ for ( int j = 0; j < patLength; j++ )
+ {
+ ch = patArr[patIdxStart + j + 1];
+ if ( ch != '?' )
+ {
+ if ( isCaseSensitive && ch != strArr[strIdxStart + i + j] )
+ {
+ continue strLoop;
+ }
+ if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
+ Character.toUpperCase( strArr[strIdxStart + i + j] ) )
+ {
+ continue strLoop;
+ }
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if ( foundIdx == -1 )
+ {
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ // All characters in the string are used. Check if only '*'s are left
+ // in the pattern. If so, we succeeded. Otherwise failure.
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] != '*' )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Breaks a path up into a Vector of path elements, tokenizing on
+ * File.separator
.
+ *
+ * @param path Path to tokenize. Must not be null
.
+ * @return a Vector of path elements from the tokenized path
+ */
+ public static Vector tokenizePath( String path )
+ {
+ Vector ret = new Vector();
+ StringTokenizer st = new StringTokenizer( path, File.separator );
+ while ( st.hasMoreTokens() )
+ {
+ ret.addElement( st.nextToken() );
+ }
+ return ret;
+ }
+
+
+ /**
+ * Returns dependency information on these two files. If src has been
+ * modified later than target, it returns true. If target doesn't exist,
+ * it likewise returns true. Otherwise, target is newer than src and
+ * is not out of date, thus the method returns false. It also returns
+ * false if the src file doesn't even exist, since how could the
+ * target then be out of date.
+ *
+ * @param src the original file
+ * @param target the file being compared against
+ * @param granularity the amount in seconds of slack we will give in
+ * determining out of dateness
+ * @return whether the target is out of date
+ */
+ public static boolean isOutOfDate( File src, File target, int granularity )
+ {
+ if ( !src.exists() )
+ {
+ return false;
+ }
+ if ( !target.exists() )
+ {
+ return true;
+ }
+ if ( ( src.lastModified() - granularity ) > target.lastModified() )
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * "Flattens" a string by removing all whitespace (space, tab, linefeed,
+ * carriage return, and formfeed). This uses StringTokenizer and the
+ * default set of tokens as documented in the single arguement constructor.
+ *
+ * @param input a String to remove all whitespace.
+ * @return a String that has had all whitespace removed.
+ */
+ public static String removeWhitespace( String input )
+ {
+ StringBuffer result = new StringBuffer();
+ if ( input != null )
+ {
+ StringTokenizer st = new StringTokenizer( input );
+ while ( st.hasMoreTokens() )
+ {
+ result.append( st.nextToken() );
+ }
+ }
+ return result.toString();
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/StringUtils.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/StringUtils.java
new file mode 100644
index 0000000000..d4ca09752d
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/StringUtils.java
@@ -0,0 +1,136 @@
+package org.apache.maven.bootstrap.util;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+public class StringUtils
+{
+ public static String[] split( String str )
+ {
+ return split( str, null, -1 );
+ }
+
+ public static String[] split( String text, String separator )
+ {
+ return split( text, separator, -1 );
+ }
+
+ public static String[] split( String str, String separator, int max )
+ {
+ StringTokenizer tok = null;
+ if ( separator == null )
+ {
+ // Null separator means we're using StringTokenizer's default
+ // delimiter, which comprises all whitespace characters.
+ tok = new StringTokenizer( str );
+ }
+ else
+ {
+ tok = new StringTokenizer( str, separator );
+ }
+
+ int listSize = tok.countTokens();
+ if ( max > 0 && listSize > max )
+ {
+ listSize = max;
+ }
+
+ String[] list = new String[listSize];
+ int i = 0;
+ int lastTokenBegin = 0;
+ int lastTokenEnd = 0;
+ while ( tok.hasMoreTokens() )
+ {
+ if ( max > 0 && i == listSize - 1 )
+ {
+ // In the situation where we hit the max yet have
+ // tokens left over in our input, the last list
+ // element gets all remaining text.
+ String endToken = tok.nextToken();
+ lastTokenBegin = str.indexOf( endToken, lastTokenEnd );
+ list[i] = str.substring( lastTokenBegin );
+ break;
+ }
+ else
+ {
+ list[i] = tok.nextToken();
+ lastTokenBegin = str.indexOf( list[i], lastTokenEnd );
+ lastTokenEnd = lastTokenBegin + list[i].length();
+ }
+ i++;
+ }
+ return list;
+ }
+
+ public static String replaceOnce( String text, String repl, String with )
+ {
+ return replace( text, repl, with, 1 );
+ }
+
+ public static String replace( String text, String repl, String with )
+ {
+ return replace( text, repl, with, -1 );
+ }
+
+ public static String replace( String text, String repl, String with, int max )
+ {
+ if ( text == null || repl == null || with == null || repl.length() == 0 )
+ {
+ return text;
+ }
+
+ StringBuffer buf = new StringBuffer( text.length() );
+ int start = 0, end = 0;
+ while ( ( end = text.indexOf( repl, start ) ) != -1 )
+ {
+ buf.append( text.substring( start, end ) ).append( with );
+ start = end + repl.length();
+
+ if ( --max == 0 )
+ {
+ break;
+ }
+ }
+ buf.append( text.substring( start ) );
+ return buf.toString();
+ }
+
+ public static String interpolate( String text, Map namespace )
+ {
+ Iterator keys = namespace.keySet().iterator();
+
+ while ( keys.hasNext() )
+ {
+ String key = keys.next().toString();
+
+ Object obj = namespace.get( key );
+
+ String value = obj.toString();
+
+ text = StringUtils.replace( text, "${" + key + "}", value );
+
+ if ( key.indexOf( " " ) == -1 )
+ {
+ text = StringUtils.replace( text, "$" + key, value );
+ }
+ }
+ return text;
+ }
+}
diff --git a/bootstrap/src/main/resources/META-INF/MANIFEST.MF b/bootstrap/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..6af167fa0c
--- /dev/null
+++ b/bootstrap/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: org.apache.maven.bootstrap.Bootstrap