From 16c46a696654c5b31fcd4272f7c5c99c3a5c54a6 Mon Sep 17 00:00:00 2001 From: Brett Leslie Porter Date: Fri, 11 Nov 2005 15:05:53 +0000 Subject: [PATCH] add slimmed down bootstrap git-svn-id: https://svn.apache.org/repos/asf/maven/components/trunk@332580 13f79535-47bb-0310-9956-ffa450edef68 --- bootstrap/build | 13 + bootstrap/build.bat | 19 + .../org/apache/maven/bootstrap/Bootstrap.java | 641 ++++++++ .../bootstrap/compile/AbstractCompiler.java | 186 +++ .../maven/bootstrap/compile/Compiler.java | 35 + .../compile/CompilerConfiguration.java | 159 ++ .../bootstrap/compile/CompilerError.java | 196 +++ .../bootstrap/compile/JavacCompiler.java | 254 +++ .../download/AbstractArtifactResolver.java | 78 + .../bootstrap/download/ArtifactResolver.java | 44 + .../maven/bootstrap/download/Base64.java | 383 +++++ .../download/DownloadFailedException.java | 31 + .../maven/bootstrap/download/HttpUtils.java | 359 ++++ .../download/OfflineArtifactResolver.java | 44 + .../download/OnlineArtifactDownloader.java | 332 ++++ .../download/RepositoryMetadata.java | 395 +++++ .../maven/bootstrap/model/Dependency.java | 305 ++++ .../maven/bootstrap/model/Exclusion.java | 55 + .../maven/bootstrap/model/ModelReader.java | 660 ++++++++ .../apache/maven/bootstrap/model/Plugin.java | 130 ++ .../maven/bootstrap/model/Repository.java | 180 ++ .../maven/bootstrap/model/Resource.java | 66 + .../maven/bootstrap/settings/Mirror.java | 62 + .../maven/bootstrap/settings/Proxy.java | 86 + .../maven/bootstrap/settings/Settings.java | 288 ++++ .../maven/bootstrap/util/AbstractReader.java | 93 ++ .../bootstrap/util/DirectoryScanner.java | 1010 ++++++++++++ .../maven/bootstrap/util/FileUtils.java | 1442 +++++++++++++++++ .../apache/maven/bootstrap/util/IOUtil.java | 782 +++++++++ .../bootstrap/util/IsolatedClassLoader.java | 74 + .../apache/maven/bootstrap/util/JarMojo.java | 221 +++ .../maven/bootstrap/util/SelectorUtils.java | 626 +++++++ .../maven/bootstrap/util/StringUtils.java | 136 ++ .../src/main/resources/META-INF/MANIFEST.MF | 2 + 34 files changed, 9387 insertions(+) create mode 100755 bootstrap/build create mode 100644 bootstrap/build.bat create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/Bootstrap.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/compile/AbstractCompiler.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/compile/Compiler.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/compile/CompilerConfiguration.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/compile/CompilerError.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/compile/JavacCompiler.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/download/AbstractArtifactResolver.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/download/ArtifactResolver.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/download/Base64.java create mode 100755 bootstrap/src/main/java/org/apache/maven/bootstrap/download/DownloadFailedException.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/download/HttpUtils.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/download/OfflineArtifactResolver.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/download/OnlineArtifactDownloader.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/download/RepositoryMetadata.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/model/Dependency.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/model/Exclusion.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/model/ModelReader.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/model/Plugin.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/model/Repository.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/model/Resource.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Mirror.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Proxy.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Settings.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/util/AbstractReader.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/util/DirectoryScanner.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/util/FileUtils.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/util/IOUtil.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/util/IsolatedClassLoader.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/util/JarMojo.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/util/SelectorUtils.java create mode 100644 bootstrap/src/main/java/org/apache/maven/bootstrap/util/StringUtils.java create mode 100644 bootstrap/src/main/resources/META-INF/MANIFEST.MF 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 + * 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( "" ); + writeLine( w, " ", "groupId", metadata.getGroupId() ); + writeLine( w, " ", "artifactId", metadata.getArtifactId() ); + writeLine( w, " ", "version", metadata.getVersion() ); + w.println( " " ); + writeLine( w, " ", "latest", metadata.getLatestVersion() ); + writeLine( w, " ", "release", metadata.getReleaseVersion() ); + writeLine( w, " ", "lastUpdated", metadata.getLastUpdated() ); + w.println( " " ); + if ( metadata.isLocalCopy() ) + { + writeLine( w, " ", "localCopy", "true" ); + } + if ( metadata.getSnapshotBuildNumber() > 0 ) + { + writeLine( w, " ", "buildNumber", String.valueOf( metadata.getSnapshotBuildNumber() ) ); + } + writeLine( w, " ", "timestamp", metadata.getSnapshotTimestamp() ); + w.println( " " ); + w.println( " " ); + for ( Iterator i = metadata.getVersions().iterator(); i.hasNext(); ) + { + writeLine( w, " ", "version", (String) i.next() ); + } + w.println( " " ); + w.println( " " ); + w.println( "" ); + } + finally + { + w.close(); + } + } + + private void writeLine( PrintWriter w, String indent, String tag, String content ) + { + if ( content != null ) + { + w.println( indent + ( "<" + tag + ">" + content + "" ) ); + } + } + + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Dependency.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Dependency.java new file mode 100644 index 0000000000..259fdc00e6 --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Dependency.java @@ -0,0 +1,305 @@ +package org.apache.maven.bootstrap.model; + +/* + * 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.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Describes a dependency. + * + * @version $Id$ + */ +public class Dependency +{ + private String id; + + private String version; + + private String url; + + private String jar; + + private String artifactId; + + private String groupId; + + private String type = "jar"; + + private String scope = SCOPE_COMPILE; + + private String resolvedVersion; + + public static final String SCOPE_TEST = "test"; + + public static final String SCOPE_COMPILE = "compile"; + + public static final String SCOPE_RUNTIME = "runtime"; + + private Set exclusions = new HashSet(); + + private List chain; + + public Dependency( List chain ) + { + this.chain = new ArrayList( chain ); + this.chain.add( this ); + } + + public Dependency( String groupId, String artifactId, String version, String type, List chain ) + { + this( chain ); + this.version = version; + this.artifactId = artifactId; + this.groupId = groupId; + this.type = type; + } + + public void setId( String id ) + { + this.id = id; + } + + public String getId() + { + if ( isValid( getGroupId() ) && isValid( getArtifactId() ) ) + { + return getGroupId() + ":" + getArtifactId(); + } + + return id; + } + + public void setGroupId( String groupId ) + { + this.groupId = groupId; + } + + public String getGroupId() + { + return groupId; + } + + public String getArtifactDirectory() + { + if ( isValid( getGroupId() ) ) + { + return getGroupId(); + } + + return getId(); + } + + public String getArtifactId() + { + return artifactId; + } + + public void setArtifactId( String artifactId ) + { + this.artifactId = artifactId; + } + + public String getArtifact() + { + // If the jar name has been explicty set then use that. This + // is when the element is explicity used in the POM. + if ( jar != null ) + { + return jar; + } + + String artifact; + + if ( isValid( getArtifactId() ) ) + { + artifact = getArtifactId() + "-" + getResolvedVersion() + "."; + } + else + { + artifact = getId() + "-" + getResolvedVersion() + "."; + } + + if ( "jar".equals( getType() ) || "maven-plugin".equals( getType() ) ) + { + artifact += "jar"; + } + else + { + artifact += getType(); + } + return artifact; + } + + public void setVersion( String version ) + { + this.version = version; + } + + public String getVersion() + { + return version; + } + + public void setJar( String jar ) + { + // This is a check we need because of the jelly interpolation + // process. If we don't check an empty string will be set and + // screw up getArtifact() above. + if ( jar.trim().length() == 0 ) + { + return; + } + + this.jar = jar; + } + + public String getJar() + { + return jar; + } + + public String getScope() + { + return scope; + } + + public void setScope( String scope ) + { + this.scope = scope; + } + + public void setUrl( String url ) + { + this.url = url; + } + + public String getUrl() + { + return url; + } + + public String getType() + { + return type; + } + + public void setType( String type ) + { + this.type = type; + } + + private boolean isValid( String value ) + { + return value != null && !value.trim().equals( "" ); + + } + + public String toString() + { + return getId() + ":" + getVersion() + ":" + getType(); + } + + public int hashCode() + { + int result = 17; + result = 37 * result + groupId.hashCode(); + result = 37 * result + artifactId.hashCode(); + result = 37 * result + type.hashCode(); + result = 37 * result + version.hashCode(); + return result; + } + + public boolean equals( Object o ) + { + if ( o == this ) + { + return true; + } + + if ( !( o instanceof Dependency ) ) + { + return false; + } + + Dependency d = (Dependency) o; + + if ( !d.getGroupId().equals( groupId ) ) + { + return false; + } + else if ( !d.getArtifactId().equals( artifactId ) ) + { + return false; + } + else if ( !d.getVersion().equals( version ) ) + { + return false; + } + else if ( !d.getType().equals( type ) ) + { + return false; + } + return true; + } + + public String getConflictId() + { + return getGroupId() + ":" + getArtifactId() + ":" + getType(); + } + + public String getDependencyConflictId() + { + return getGroupId() + ":" + getArtifactId() + ":" + getType() + ":" + getVersion(); + } + + public void setResolvedVersion( String resolvedVersion ) + { + this.resolvedVersion = resolvedVersion; + } + + public String getResolvedVersion() + { + if ( resolvedVersion == null ) + { + resolvedVersion = getVersion(); + } + return resolvedVersion; + } + + public void addExclusion( Exclusion currentExclusion ) + { + exclusions.add( currentExclusion.getConflictId() ); + } + + public Set getExclusions() + { + return exclusions; + } + + public List getChain() + { + return chain; + } + + public Dependency getPomDependency() + { + return new Dependency( groupId, artifactId, version, "pom", chain ); + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Exclusion.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Exclusion.java new file mode 100644 index 0000000000..f57dfab796 --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Exclusion.java @@ -0,0 +1,55 @@ +package org.apache.maven.bootstrap.model; + +/* + * 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. + */ + +/** + * Excluded dependency. + * + * @author Brett Porter + * @version $Id$ + */ +public class Exclusion +{ + private String groupId; + + private String artifactId; + + 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 getConflictId() + { + return groupId + ":" + artifactId + ":jar"; + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/model/ModelReader.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/ModelReader.java new file mode 100644 index 0000000000..86785fb2d5 --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/ModelReader.java @@ -0,0 +1,660 @@ +package org.apache.maven.bootstrap.model; + +/* + * 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.download.DownloadFailedException; +import org.apache.maven.bootstrap.download.ArtifactResolver; +import org.apache.maven.bootstrap.util.AbstractReader; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.sun.corba.se.impl.ior.ObjectAdapterIdArray; + +/** + * Parse a POM. + * + * @version $Id$ + */ +public class ModelReader + extends AbstractReader +{ + private int depth = 0; + + private String artifactId; + + private String version; + + private String groupId; + + private String packaging = "jar"; + + private String parentGroupId; + + private String parentArtifactId; + + private String parentVersion; + + private Map dependencies = new HashMap(); + + private List repositories = new ArrayList(); + + private List resources = new ArrayList(); + + private Map managedDependencies = new HashMap(); + + private Dependency currentDependency; + + private Resource currentResource; + + private boolean insideParent = false; + + private boolean insideDependency = false; + + private boolean insideResource = false; + + private boolean insideRepository = false; + + private StringBuffer bodyText = new StringBuffer(); + + private final boolean resolveTransitiveDependencies; + + private Repository currentRepository; + + private final ArtifactResolver resolver; + + private static Set inProgress = new HashSet(); + + private Map parentDependencies = new HashMap(); + + private Map transitiveDependencies = new HashMap(); + + private boolean insideDependencyManagement = false; + + private boolean insideReleases; + + private boolean insideSnapshots; + + private boolean insideExclusion; + + private Exclusion currentExclusion; + + private final Set excluded; + + private final List chain; + + private final String inheritedScope; + + private Map plugins = new HashMap(); + + private boolean insideConfiguration; + + private boolean insideBuild; + + private Plugin currentPlugin; + + private boolean insidePlugin; + + private List modules = new ArrayList(); + + public ModelReader( ArtifactResolver resolver, boolean resolveTransitiveDependencies ) + { + this( resolver, null, resolveTransitiveDependencies, Collections.EMPTY_SET, Collections.EMPTY_LIST ); + } + + public ModelReader( ArtifactResolver resolver, String inheritedScope, boolean resolveTransitiveDependencies, + Set excluded, List chain ) + { + this.resolver = resolver; + + this.resolveTransitiveDependencies = resolveTransitiveDependencies; + + this.excluded = excluded; + + this.inheritedScope = inheritedScope; + + this.chain = chain; + } + + public List getRemoteRepositories() + { + return repositories; + } + + public Collection getDependencies() + { + Map m = new HashMap(); + m.putAll( transitiveDependencies ); + m.putAll( parentDependencies ); + m.putAll( dependencies ); + return m.values(); + } + + public Collection getManagedDependencies() + { + Map m = new HashMap(); + m.putAll( managedDependencies ); + return m.values(); + } + + public List getResources() + { + return resources; + } + + public void startElement( String uri, String localName, String rawName, Attributes attributes ) + { + if ( rawName.equals( "parent" ) ) + { + insideParent = true; + } + else if ( rawName.equals( "repository" ) ) + { + currentRepository = new Repository(); + + insideRepository = true; + } + else if ( rawName.equals( "dependency" ) ) + { + List chain = + Collections.singletonList( new Dependency( groupId, artifactId, version, packaging, this.chain ) ); + currentDependency = new Dependency( chain ); + + insideDependency = true; + } + else if ( rawName.equals( "build" ) && depth == 1 ) + { + insideBuild = true; + } + else if ( rawName.equals( "plugin" ) ) + { + currentPlugin = new Plugin(); + + insidePlugin = true; + } + else if ( rawName.equals( "dependencyManagement" ) ) + { + insideDependencyManagement = true; + } + else if ( rawName.equals( "resource" ) ) + { + currentResource = new Resource(); + + insideResource = true; + } + else if ( rawName.equals( "testResource" ) ) + { + currentResource = new Resource(); + + insideResource = true; + } + else if ( rawName.equals( "snapshots" ) && insideRepository ) + { + insideSnapshots = true; + } + else if ( rawName.equals( "releases" ) && insideRepository ) + { + insideReleases = true; + } + else if ( rawName.equals( "exclusion" ) && insideDependency ) + { + insideExclusion = true; + + currentExclusion = new Exclusion(); + } + else if ( rawName.equals( "configuration" ) && insidePlugin ) + { + insideConfiguration = true; + } + depth++; + } + + 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 + { + // support both v3 and v4 + if ( rawName.equals( "parent" ) ) + { + if ( parentArtifactId == null || parentArtifactId.trim().length() == 0 ) + { + throw new SAXException( "Missing required element in : artifactId." ); + } + + if ( parentGroupId == null || parentGroupId.trim().length() == 0 ) + { + throw new SAXException( "Missing required element in : groupId." ); + } + + if ( parentVersion == null || parentVersion.trim().length() == 0 ) + { + throw new SAXException( "Missing required element in : version." ); + } + + if ( groupId == null ) + { + groupId = parentGroupId; + } + + if ( version == null ) + { + version = parentVersion; + } + + // actually, these should be transtive (see MNG-77) - but some projects have circular deps that way + ModelReader p = retrievePom( parentGroupId, parentArtifactId, parentVersion, "pom", inheritedScope, false, + excluded, Collections.EMPTY_LIST ); + + addDependencies( p.getDependencies(), parentDependencies, inheritedScope, excluded ); + + addDependencies( p.getManagedDependencies(), managedDependencies, inheritedScope, Collections.EMPTY_SET ); + + resources.addAll( p.getResources() ); + + insideParent = false; + } + else if ( rawName.equals( "dependency" ) ) + { + insideDependency = false; + + if ( insideDependencyManagement ) + { + managedDependencies.put( currentDependency.getConflictId(), currentDependency ); + } + else + { + dependencies.put( currentDependency.getConflictId(), currentDependency ); + } + } + else if ( rawName.equals( "exclusion" ) ) + { + currentDependency.addExclusion( currentExclusion ); + insideExclusion = false; + } + else if ( rawName.equals( "dependencyManagement" ) ) + { + insideDependencyManagement = false; + } + else if ( rawName.equals( "resource" ) ) + { + resources.add( currentResource ); + + insideResource = false; + } + else if ( rawName.equals( "repository" ) ) + { + repositories.add( currentRepository ); + + insideRepository = false; + } + else if ( rawName.equals( "plugin" ) ) + { + plugins.put( currentPlugin.getId(), currentPlugin ); + + insidePlugin = false; + } + else if ( rawName.equals( "build" ) ) + { + insideBuild = false; + } + else if ( rawName.equals( "module" ) ) + { + modules.add( getBodyText() ); + } + else if ( insideParent ) + { + if ( rawName.equals( "groupId" ) ) + { + parentGroupId = getBodyText(); + } + else if ( rawName.equals( "artifactId" ) ) + { + parentArtifactId = getBodyText(); + } + else if ( rawName.equals( "version" ) ) + { + parentVersion = getBodyText(); + } + } + else if ( insideDependency ) + { + if ( insideExclusion ) + { + if ( rawName.equals( "groupId" ) ) + { + currentExclusion.setGroupId( getBodyText() ); + } + else if ( rawName.equals( "artifactId" ) ) + { + currentExclusion.setArtifactId( getBodyText() ); + } + } + else if ( rawName.equals( "id" ) ) + { + currentDependency.setId( getBodyText() ); + } + else if ( rawName.equals( "version" ) ) + { + currentDependency.setVersion( getBodyText() ); + } + else if ( rawName.equals( "jar" ) ) + { + currentDependency.setJar( getBodyText() ); + } + else if ( rawName.equals( "type" ) ) + { + currentDependency.setType( getBodyText() ); + } + else if ( rawName.equals( "groupId" ) ) + { + currentDependency.setGroupId( getBodyText() ); + } + else if ( rawName.equals( "artifactId" ) ) + { + currentDependency.setArtifactId( getBodyText() ); + } + else if ( rawName.equals( "scope" ) ) + { + currentDependency.setScope( getBodyText() ); + } + } + else if ( insideBuild && insidePlugin ) + { + if ( insideConfiguration ) + { + if ( rawName.equals( "configuration" ) ) + { + insideConfiguration = false; + } + else + { + currentPlugin.getConfiguration().put( rawName, getBodyText() ); + } + } + else if ( rawName.equals( "groupId" ) ) + { + currentPlugin.setGroupId( getBodyText() ); + } + else if ( rawName.equals( "artifactId" ) ) + { + currentPlugin.setArtifactId( getBodyText() ); + } + else if ( rawName.equals( "version" ) ) + { + currentPlugin.setVersion( getBodyText() ); + } + } + else if ( insideResource ) + { + if ( rawName.equals( "directory" ) ) + { + currentResource.setDirectory( getBodyText() ); + } + else if ( rawName.equals( "include" ) ) + { + currentResource.addInclude( getBodyText() ); + } + else if ( rawName.equals( "exclude" ) ) + { + currentResource.addExclude( getBodyText() ); + } + } + else if ( insideRepository ) + { + if ( rawName.equals( "id" ) ) + { + currentRepository.setId( getBodyText() ); + } + else if ( rawName.equals( "url" ) ) + { + currentRepository.setBasedir( getBodyText() ); + } + else if ( rawName.equals( "layout" ) ) + { + currentRepository.setLayout( getBodyText() ); + } + else if ( rawName.equals( "enabled" ) ) + { + if ( insideSnapshots ) + { + currentRepository.setSnapshots( Boolean.valueOf( getBodyText() ).booleanValue() ); + } + else if ( insideReleases ) + { + currentRepository.setReleases( Boolean.valueOf( getBodyText() ).booleanValue() ); + } + } + else if ( rawName.equals( "snapshots" ) ) + { + insideSnapshots = false; + } + else if ( rawName.equals( "releases" ) ) + { + insideReleases = false; + } + } + else if ( depth == 2 ) + { + if ( rawName.equals( "artifactId" ) ) + { + artifactId = getBodyText(); + } + else if ( rawName.equals( "version" ) ) + { + version = getBodyText(); + } + else if ( rawName.equals( "groupId" ) ) + { + groupId = getBodyText(); + } + else if ( rawName.equals( "packaging" ) ) + { + packaging = getBodyText(); + } + } + + if ( depth == 1 ) // model / project + { + resolver.addBuiltArtifact( groupId, artifactId, "pom", pomFile ); + + resolveDependencies(); + } + + bodyText = new StringBuffer(); + + depth--; + } + + private void resolveDependencies() + throws SAXException + { + for ( Iterator it = dependencies.values().iterator(); it.hasNext(); ) + { + Dependency dependency = (Dependency) it.next(); + + if ( !excluded.contains( dependency.getConflictId() ) ) + { + if ( !dependency.getScope().equals( Dependency.SCOPE_TEST ) || inheritedScope == null ) + { + if ( dependency.getVersion() == null ) + { + Dependency managedDependency = + (Dependency) managedDependencies.get( dependency.getConflictId() ); + if ( managedDependency == null ) + { + throw new NullPointerException( "[" + groupId + ":" + artifactId + ":" + packaging + ":" + + version + "] " + "Dependency " + dependency.getConflictId() + + " is missing a version, and nothing is found in dependencyManagement. " ); + } + dependency.setVersion( managedDependency.getVersion() ); + } + + if ( resolveTransitiveDependencies ) + { + Set excluded = new HashSet( this.excluded ); + excluded.addAll( dependency.getExclusions() ); + + ModelReader p = retrievePom( dependency.getGroupId(), dependency.getArtifactId(), + dependency.getVersion(), dependency.getType(), + dependency.getScope(), resolveTransitiveDependencies, excluded, + dependency.getChain() ); + + addDependencies( p.getDependencies(), transitiveDependencies, dependency.getScope(), excluded ); + } + } + } + } + } + + private void addDependencies( Collection dependencies, Map target, String inheritedScope, Set excluded ) + { + for ( Iterator i = dependencies.iterator(); i.hasNext(); ) + { + Dependency d = (Dependency) i.next(); + + // skip test deps + if ( !Dependency.SCOPE_TEST.equals( d.getScope() ) ) + { + // Do we care about runtime here? + if ( Dependency.SCOPE_TEST.equals( inheritedScope ) ) + { + d.setScope( Dependency.SCOPE_TEST ); + } + + if ( !hasDependency( d, target ) && !excluded.contains( d.getConflictId() ) ) + { + if ( !"plexus".equals( d.getGroupId() ) || ( !"plexus-utils".equals( d.getArtifactId() ) && + !"plexus-container-default".equals( d.getArtifactId() ) ) ) + { + target.put( d.getConflictId(), d ); + } + } + } + } + } + + private boolean hasDependency( Dependency d, Map dependencies ) + { + String conflictId = d.getConflictId(); + if ( dependencies.containsKey( conflictId ) ) + { + // We only care about pushing in compile scope dependencies I think + // if not, we'll need to be able to get the original and pick the appropriate scope + if ( d.getScope().equals( Dependency.SCOPE_COMPILE ) ) + { + dependencies.remove( conflictId ); + } + else + { + return true; + } + } + return false; + } + + private ModelReader retrievePom( String groupId, String artifactId, String version, String type, + String inheritedScope, boolean resolveTransitiveDependencies, Set excluded, + List chain ) + throws SAXException + { + String key = groupId + ":" + artifactId + ":" + version; + + if ( inProgress.contains( key ) ) + { + throw new SAXException( "Circular dependency found, looking for " + key + "\nIn progress:" + inProgress ); + } + + inProgress.add( key ); + + ModelReader p = new ModelReader( resolver, inheritedScope, resolveTransitiveDependencies, excluded, chain ); + + try + { + Dependency pom = new Dependency( groupId, artifactId, version, "pom", chain ); + + resolver.downloadDependencies( Collections.singletonList( pom ) ); + + p.parse( resolver.getArtifactFile( pom ) ); + } + catch ( IOException e ) + { + throw new SAXException( "Error getting parent POM", e ); + } + catch ( ParserConfigurationException e ) + { + throw new SAXException( "Error getting parent POM", e ); + } + catch ( DownloadFailedException e ) + { + throw new SAXException( "Error getting parent POM", e ); + } + + inProgress.remove( key ); + + return p; + } + + public String getArtifactId() + { + return artifactId; + } + + public String getVersion() + { + return version; + } + + public String getGroupId() + { + return groupId; + } + + public String getPackaging() + { + return packaging; + } + + public Map getPlugins() + { + return plugins; + } + + public List getModules() + { + return modules; + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Plugin.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Plugin.java new file mode 100644 index 0000000000..77dbd3236f --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Plugin.java @@ -0,0 +1,130 @@ +package org.apache.maven.bootstrap.model; + +/* + * 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.HashMap; +import java.util.Map; +import java.util.Collections; + +/** + * Describes a dependency. + * + * @version $Id$ + */ +public class Plugin +{ + private String version; + + private String artifactId; + + private String groupId; + + private Map configuration = new HashMap(); + + public String getId() + { + return getGroupId() + ":" + getArtifactId(); + } + + public void setGroupId( String groupId ) + { + this.groupId = groupId; + } + + public String getGroupId() + { + return groupId; + } + + public String getArtifactId() + { + return artifactId; + } + + public void setArtifactId( String artifactId ) + { + this.artifactId = artifactId; + } + + public void setVersion( String version ) + { + this.version = version; + } + + public String getVersion() + { + return version; + } + + public String toString() + { + return getId() + ":" + getVersion(); + } + + public int hashCode() + { + int result = 17; + result = 37 * result + groupId.hashCode(); + result = 37 * result + artifactId.hashCode(); + result = 37 * result + version.hashCode(); + return result; + } + + public boolean equals( Object o ) + { + if ( o == this ) + { + return true; + } + + if ( !( o instanceof Plugin ) ) + { + return false; + } + + Plugin d = (Plugin) o; + + if ( !d.getGroupId().equals( groupId ) ) + { + return false; + } + else if ( !d.getArtifactId().equals( artifactId ) ) + { + return false; + } + else if ( !d.getVersion().equals( version ) ) + { + return false; + } + return true; + } + + public Map getConfiguration() + { + return configuration; + } + + public Dependency asDependency() + { + return new Dependency( groupId, artifactId, version, "maven-plugin", Collections.EMPTY_LIST ); + } + + public Dependency asDependencyPom() + { + return new Dependency( groupId, artifactId, version, "pom", Collections.EMPTY_LIST ); + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Repository.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Repository.java new file mode 100644 index 0000000000..0028a28780 --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Repository.java @@ -0,0 +1,180 @@ +package org.apache.maven.bootstrap.model; + +/* + * 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.util.Collections; + +/** + * Repository path management. + * + * @author Brett Porter + * @version $Id$ + */ +public class Repository +{ + public static final String LAYOUT_LEGACY = "legacy"; + + public static final String LAYOUT_DEFAULT = "default"; + + private String basedir; + + private String layout; + + private String id; + + private boolean releases; + + private boolean snapshots; + + public Repository() + { + } + + public Repository( String id, String basedir, String layout, boolean snapshots, boolean releases ) + { + this.id = id; + this.basedir = basedir; + this.layout = layout; + this.snapshots = snapshots; + this.releases = releases; + } + + private File getArtifactFile( String groupId, String artifactId, String version, String type ) + { + Dependency d = new Dependency( groupId, artifactId, version, type, Collections.EMPTY_LIST ); + + return getArtifactFile( d ); + + } + + public File getArtifactFile( Dependency dependency ) + { + String repositoryPath = getArtifactPath( dependency ); + + return new File( basedir, repositoryPath ); + } + + public String getArtifactPath( Dependency dependency ) + { + String repositoryPath; + if ( LAYOUT_LEGACY.equals( layout ) ) + { + repositoryPath = dependency.getArtifactDirectory() + "/" + dependency.getType() + "s/" + + dependency.getArtifact(); + } + else if ( LAYOUT_DEFAULT.equals( layout ) ) + { + repositoryPath = dependency.getGroupId().replace( '.', '/' ); + repositoryPath = repositoryPath + "/" + dependency.getArtifactId() + "/" + dependency.getVersion(); + repositoryPath = repositoryPath + "/" + dependency.getArtifact(); + } + else + { + throw new IllegalStateException( "Unknown layout: " + layout ); + } + return repositoryPath; + } + + public File getMetadataFile( String groupId, String artifactId, String version, String type, String filename ) + { + String repositoryPath = getMetadataPath( groupId, artifactId, version, type, filename ); + + return new File( basedir, repositoryPath ); + } + + public String getMetadataPath( String groupId, String artifactId, String version, String type, String filename ) + { + Dependency dependency = new Dependency( groupId, artifactId, version, type, Collections.EMPTY_LIST ); + + String repositoryPath; + if ( LAYOUT_LEGACY.equals( layout ) ) + { + repositoryPath = dependency.getArtifactDirectory() + "/poms/" + filename; + } + else if ( LAYOUT_DEFAULT.equals( layout ) ) + { + repositoryPath = dependency.getGroupId().replace( '.', '/' ); + repositoryPath = repositoryPath + "/" + dependency.getArtifactId(); + if ( version != null ) + { + repositoryPath = repositoryPath + "/" + dependency.getVersion(); + } + repositoryPath = repositoryPath + "/" + filename; + } + else + { + throw new IllegalStateException( "Unknown layout: " + layout ); + } + return repositoryPath; + } + + public String toString() + { + return basedir; + } + + public String getBasedir() + { + return basedir; + } + + public void setBasedir( String basedir ) + { + this.basedir = basedir; + } + + public void setLayout( String layout ) + { + this.layout = layout; + } + + public String getId() + { + return id; + } + + public void setId( String id ) + { + this.id = id; + } + + public String getLayout() + { + return layout; + } + + public void setReleases( boolean releases ) + { + this.releases = releases; + } + + public void setSnapshots( boolean snapshots ) + { + this.snapshots = snapshots; + } + + public boolean isReleases() + { + return releases; + } + + public boolean isSnapshots() + { + return snapshots; + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Resource.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Resource.java new file mode 100644 index 0000000000..7b24441032 --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/model/Resource.java @@ -0,0 +1,66 @@ +package org.apache.maven.bootstrap.model; + +/* + * 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.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Describes a resource. + * + * @version $Id$ + */ +public class Resource + implements Serializable +{ + private String directory; + + private List includes = new ArrayList(); + + private List excludes = new ArrayList(); + + public void addInclude( String pattern ) + { + this.includes.add( pattern ); + } + + public void addExclude( String pattern ) + { + this.excludes.add( pattern ); + } + + public List getIncludes() + { + return this.includes; + } + + public List getExcludes() + { + return this.excludes; + } + + public void setDirectory( String directory ) + { + this.directory = directory; + } + + public String getDirectory() + { + return this.directory; + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Mirror.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Mirror.java new file mode 100644 index 0000000000..c182ef29d2 --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Mirror.java @@ -0,0 +1,62 @@ +package org.apache.maven.bootstrap.settings; + +/* + * 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. + */ + +/** + * Mirror definition. + * + * @author Brett Porter + * @version $Id$ + */ +public class Mirror +{ + private String id; + + private String mirrorOf; + + private String url; + + public String getId() + { + return id; + } + + public void setId( String id ) + { + this.id = id; + } + + public void setMirrorOf( String mirrorOf ) + { + this.mirrorOf = mirrorOf; + } + + public void setUrl( String url ) + { + this.url = url; + } + + public String getMirrorOf() + { + return mirrorOf; + } + + public String getUrl() + { + return url; + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Proxy.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Proxy.java new file mode 100644 index 0000000000..92a506493c --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Proxy.java @@ -0,0 +1,86 @@ +package org.apache.maven.bootstrap.settings; + +/* + * 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. + */ + +/** + * Proxy definition. + * + * @author Brett Porter + * @version $Id$ + */ + public class Proxy +{ + private boolean active; + + private String host; + + private String port; + + private String userName; + + private String password; + + public boolean isActive() + { + return active; + } + + public void setActive( boolean active ) + { + this.active = active; + } + + public void setHost( String host ) + { + this.host = host; + } + + public String getHost() + { + return host; + } + + public void setPort( String port ) + { + this.port = port; + } + + public String getPort() + { + return port; + } + + public void setUserName( String userName ) + { + this.userName = userName; + } + + public String getUserName() + { + return userName; + } + + public void setPassword( String password ) + { + this.password = password; + } + + public String getPassword() + { + return password; + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Settings.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Settings.java new file mode 100644 index 0000000000..6455e91bb5 --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/settings/Settings.java @@ -0,0 +1,288 @@ +package org.apache.maven.bootstrap.settings; + +/* + * 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.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Settings definition. + * + * @author Brett Porter + * @version $Id$ + */ +public class Settings +{ + private String localRepository; + + private List mirrors = new ArrayList(); + + private List proxies = new ArrayList(); + + private Proxy activeProxy = null; + + public Settings() + { + localRepository = System.getProperty( "maven.repo.local" ); + } + + public String getLocalRepository() + { + return localRepository; + } + + public void setLocalRepository( String localRepository ) + { + this.localRepository = localRepository; + } + + public void addProxy( Proxy proxy ) + { + proxies.add( proxy ); + } + + public void addMirror( Mirror mirror ) + { + mirrors.add( mirror ); + } + + public Proxy getActiveProxy() + { + if ( activeProxy == null ) + { + for ( Iterator it = proxies.iterator(); it.hasNext() && activeProxy == null; ) + { + Proxy proxy = (Proxy) it.next(); + if ( proxy.isActive() ) + { + activeProxy = proxy; + } + } + } + return activeProxy; + } + + public static Settings read( String userHome, File file ) + throws IOException, ParserConfigurationException, SAXException + { + return new Reader( userHome ).parseSettings( file ); + } + + public List getMirrors() + { + return mirrors; + } + + private static class Reader + extends AbstractReader + { + private Proxy currentProxy = null; + + private StringBuffer currentBody = new StringBuffer(); + + private Mirror currentMirror; + + private final Settings settings = new Settings(); + + private final String userHome; + + private Reader( String userHome ) + { + this.userHome = userHome; + } + + public void characters( char[] ch, int start, int length ) + throws SAXException + { + currentBody.append( ch, start, length ); + } + + public void endElement( String uri, String localName, String rawName ) + throws SAXException + { + if ( "localRepository".equals( rawName ) ) + { + if ( notEmpty( currentBody.toString() ) ) + { + String localRepository = currentBody.toString().trim(); + if ( settings.getLocalRepository() == null ) + { + settings.setLocalRepository( localRepository ); + } + } + else + { + throw new SAXException( + "Invalid profile entry. Missing one or more " + "fields: {localRepository}." ); + } + } + else if ( "proxy".equals( rawName ) ) + { + if ( notEmpty( currentProxy.getHost() ) && notEmpty( currentProxy.getPort() ) ) + { + settings.addProxy( currentProxy ); + currentProxy = null; + } + else + { + throw new SAXException( "Invalid proxy entry. Missing one or more fields: {host, port}." ); + } + } + else if ( currentProxy != null ) + { + if ( "active".equals( rawName ) ) + { + currentProxy.setActive( Boolean.valueOf( currentBody.toString().trim() ).booleanValue() ); + } + else if ( "host".equals( rawName ) ) + { + currentProxy.setHost( currentBody.toString().trim() ); + } + else if ( "port".equals( rawName ) ) + { + currentProxy.setPort( currentBody.toString().trim() ); + } + else if ( "username".equals( rawName ) ) + { + currentProxy.setUserName( currentBody.toString().trim() ); + } + else if ( "password".equals( rawName ) ) + { + currentProxy.setPassword( currentBody.toString().trim() ); + } + else if ( "protocol".equals( rawName ) ) + { + } + else if ( "nonProxyHosts".equals( rawName ) ) + { + } + else + { + throw new SAXException( "Illegal element inside proxy: \'" + rawName + "\'" ); + } + } + else if ( "mirror".equals( rawName ) ) + { + if ( notEmpty( currentMirror.getId() ) && notEmpty( currentMirror.getMirrorOf() ) && + notEmpty( currentMirror.getUrl() ) ) + { + settings.addMirror( currentMirror ); + currentMirror = null; + } + else + { + throw new SAXException( "Invalid mirror entry. Missing one or more fields: {id, mirrorOf, url}." ); + } + } + else if ( currentMirror != null ) + { + if ( "id".equals( rawName ) ) + { + currentMirror.setId( currentBody.toString().trim() ); + } + else if ( "mirrorOf".equals( rawName ) ) + { + currentMirror.setMirrorOf( currentBody.toString().trim() ); + } + else if ( "url".equals( rawName ) ) + { + currentMirror.setUrl( currentBody.toString().trim() ); + } + else if ( "name".equals( rawName ) ) + { + } + else + { + throw new SAXException( "Illegal element inside proxy: \'" + rawName + "\'" ); + } + } + + currentBody = new StringBuffer(); + } + + private boolean notEmpty( String test ) + { + return test != null && test.trim().length() > 0; + } + + public void startElement( String uri, String localName, String rawName, Attributes attributes ) + throws SAXException + { + if ( "proxy".equals( rawName ) ) + { + currentProxy = new Proxy(); + } + else if ( "mirror".equals( rawName ) ) + { + currentMirror = new Mirror(); + } + } + + public void reset() + { + this.currentBody = null; + this.currentMirror = null; + } + + public Settings parseSettings( File settingsXml ) + throws IOException, ParserConfigurationException, SAXException + { + if ( settingsXml.exists() ) + { + parse( settingsXml ); + } + if ( settings.getLocalRepository() == null ) + { + String m2LocalRepoPath = "/.m2/repository"; + + File repoDir = new File( userHome, m2LocalRepoPath ); + if ( !repoDir.exists() ) + { + repoDir.mkdirs(); + } + + settings.setLocalRepository( repoDir.getAbsolutePath() ); + + System.out.println( + "You SHOULD have a ~/.m2/settings.xml file and must contain at least the following information:" ); + System.out.println(); + + System.out.println( "" ); + System.out.println( " /path/to/your/repository" ); + System.out.println( "" ); + + System.out.println(); + + System.out.println( "Alternatively, you can specify -Dmaven.repo.local=/path/to/m2/repository" ); + + System.out.println(); + + System.out.println( "HOWEVER, since you did not specify a repository path, maven will use: " + + repoDir.getAbsolutePath() + " to store artifacts locally." ); + } + return settings; + } + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/AbstractReader.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/AbstractReader.java new file mode 100644 index 0000000000..2bc86d9633 --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/AbstractReader.java @@ -0,0 +1,93 @@ +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 org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; + +/** + * Parse an XML file. + * + * @version $Id$ + */ +public abstract class AbstractReader + extends DefaultHandler +{ + private SAXParserFactory saxFactory; + + protected File pomFile; + + public void parse( File file ) + throws ParserConfigurationException, SAXException, IOException + { + pomFile = file; + + saxFactory = SAXParserFactory.newInstance(); + + SAXParser parser = saxFactory.newSAXParser(); + + // Cheap and cheerful. Please add more to skip if the parser chokes (or use the actual + StringWriter output = new StringWriter(); + IOUtil.copy( new FileReader( file ), output); + String out = output.toString(); + out = StringUtils.replace( out, "ø", "\u00f8" ); + + InputSource is = new InputSource( new StringReader( out ) ); + + try + { + parser.parse( is, this ); + } + catch ( SAXException e ) + { + System.err.println( "Error reading POM: " + file ); + throw e; + } + } + + public void warning( SAXParseException spe ) + { + printParseError( "Warning", spe ); + } + + public void error( SAXParseException spe ) + { + printParseError( "Error", spe ); + } + + public void fatalError( SAXParseException spe ) + { + printParseError( "Fatal Error", spe ); + } + + private final void printParseError( String type, SAXParseException spe ) + { + System.err.println( type + " [line " + spe.getLineNumber() + ", row " + spe.getColumnNumber() + "]: " + + spe.getMessage() ); + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/DirectoryScanner.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/DirectoryScanner.java new file mode 100644 index 0000000000..f621ebc2ef --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/DirectoryScanner.java @@ -0,0 +1,1010 @@ +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.IOException; +import java.util.Vector; + +/** + * Class for scanning a directory for files/directories which match certain + * criteria. + *

+ * These criteria consist of selectors and patterns which have been specified. + * With the selectors you can select which files you want to have included. + * Files which are not selected are excluded. With patterns you can include + * or exclude files based on their filename. + *

+ * The idea is simple. A given directory is recursively scanned for all files + * and directories. Each file/directory is matched against a set of selectors, + * including special support for matching against filenames with include and + * and exclude patterns. Only files/directories which match at least one + * pattern of the include pattern list or other file selector, and don't match + * any pattern of the exclude pattern list or fail to match against a required + * selector will be placed in the list of files/directories found. + *

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

+ * The filename pattern matching is done as follows: + * The name to be matched is split up in path segments. A path segment is the + * name of a directory or file, which is bounded by + * File.separator ('/' under UNIX, '\' under Windows). + * 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.separators + * 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; + } + + /** + *

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.

+ * + * @return the names of the files which were deselected. + * @see #slowScan + */ + public String[] getDeselectedFiles() + { + slowScan(); + String[] files = new String[filesDeselected.size()]; + filesDeselected.copyInto( files ); + return files; + } + + /** + * Returns the names of the directories 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 directories which matched at least one of the + * include patterns and none of the exclude patterns. + */ + public String[] getIncludedDirectories() + { + String[] directories = new String[dirsIncluded.size()]; + dirsIncluded.copyInto( directories ); + return directories; + } + + /** + * Returns the names of the directories 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 directories which matched none of the include + * patterns. + * @see #slowScan + */ + public String[] getNotIncludedDirectories() + { + slowScan(); + String[] directories = new String[dirsNotIncluded.size()]; + dirsNotIncluded.copyInto( directories ); + return directories; + } + + /** + * Returns the names of the directories 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 directories which matched at least one of the + * include patterns and at least one of the exclude patterns. + * @see #slowScan + */ + public String[] getExcludedDirectories() + { + slowScan(); + String[] directories = new String[dirsExcluded.size()]; + dirsExcluded.copyInto( directories ); + return directories; + } + + /** + *

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.

+ * + * @return the names of the directories which were deselected. + * @see #slowScan + */ + public String[] getDeselectedDirectories() + { + slowScan(); + String[] directories = new String[dirsDeselected.size()]; + dirsDeselected.copyInto( directories ); + return directories; + } + + /** + * Adds default exclusions to the current exclusions set. + */ + public void addDefaultExcludes() + { + int excludesLength = excludes == null ? 0 : excludes.length; + String[] newExcludes; + newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length]; + if ( excludesLength > 0 ) + { + System.arraycopy( excludes, 0, newExcludes, 0, excludesLength ); + } + for ( int i = 0; i < DEFAULTEXCLUDES.length; i++ ) + { + newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i].replace( '/', + File.separatorChar ).replace( '\\', File.separatorChar ); + } + excludes = newExcludes; + } + + /** + * Checks whether a given file is a symbolic link. + *

+ *

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.

+ * + * @param parent the parent directory of the file to test + * @param name the name of the file to test. + * @since Ant 1.5 + */ + public boolean isSymbolicLink( File parent, String name ) + throws IOException + { + File resolvedParent = new File( parent.getCanonicalPath() ); + File toTest = new File( resolvedParent, name ); + return !toTest.getAbsolutePath().equals( toTest.getCanonicalPath() ); + } + +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/FileUtils.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/FileUtils.java new file mode 100644 index 0000000000..4ff0f5e83d --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/FileUtils.java @@ -0,0 +1,1442 @@ +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.io.InputStream; +import java.net.URL; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.Vector; + +/** + * This class provides basic facilities for manipulating files and file paths. + *

+ *

Path-related methods

+ *

+ *

Methods exist to retrieve the components of a typical file path. For example + * /www/hosted/mysite/index.html, can be broken into: + *

    + *
  • /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}
  • + *
+ * There are also methods to {@link #catPath concatenate two paths}, {@link #resolveFile resolve a + * path relative to a File} and {@link #normalize} a path. + *

+ *

+ *

File-related methods

+ *

+ * There are methods to create a {@link #toFile File from a URL}, copy a + * {@link #copyFileToDirectory File to a directory}, + * copy a {@link #copyFile File to another File}, + * copy a {@link #copyURLToFile URL's contents to a File}, + * as well as methods to {@link #deleteDirectory(File) delete} and {@link #cleanDirectory(File) + * clean} a directory. + *

+ *

+ * Common {@link java.io.File} manipulation routines. + *

+ * Taken from the commons-utils repo. + * Also code from Alexandria's FileUtils. + * And from Avalon Excalibur's IO. + * And from Ant. + * + * @author Kevin A. Burton + * @author Scott Sanders + * @author Daniel Rall + * @author Christoph.Reck + * @author Peter Donald + * @author Jeff Turner + * @version $Id$ + */ +public class FileUtils +{ + /** + * The number of bytes in a kilobyte. + */ + public static final int ONE_KB = 1024; + + /** + * The number of bytes in a megabyte. + */ + public static final int ONE_MB = ONE_KB * ONE_KB; + + /** + * The number of bytes in a gigabyte. + */ + public static final int ONE_GB = ONE_KB * ONE_MB; + + /** + * Returns a human-readable version of the file size (original is in + * bytes). + * + * @param size The number of bytes. + * @return A human-readable display value (includes units). + */ + public static String byteCountToDisplaySize( int size ) + { + String displaySize; + + if ( size / ONE_GB > 0 ) + { + displaySize = String.valueOf( size / ONE_GB ) + " GB"; + } + else if ( size / ONE_MB > 0 ) + { + displaySize = String.valueOf( size / ONE_MB ) + " MB"; + } + else if ( size / ONE_KB > 0 ) + { + displaySize = String.valueOf( size / ONE_KB ) + " KB"; + } + else + { + displaySize = String.valueOf( size ) + " bytes"; + } + + return displaySize; + } + + /** + * Returns the directory path portion of a file specification string. + * Matches the equally named unix command. + * + * @return The directory portion excluding the ending file separator. + */ + public static String dirname( String filename ) + { + int i = filename.lastIndexOf( File.separator ); + return ( i >= 0 ? filename.substring( 0, i ) : "" ); + } + + /** + * Returns the filename portion of a file specification string. + * + * @return The filename string with extension. + */ + public static String filename( String filename ) + { + int i = filename.lastIndexOf( File.separator ); + return ( i >= 0 ? filename.substring( i + 1 ) : filename ); + } + + /** + * Returns the filename portion of a file specification string. + * Matches the equally named unix command. + * + * @return The filename string without extension. + */ + public static String basename( String filename ) + { + return basename( filename, extension( filename ) ); + } + + /** + * Returns the filename portion of a file specification string. + * Matches the equally named unix command. + */ + public static String basename( String filename, String suffix ) + { + int i = filename.lastIndexOf( File.separator ) + 1; + int lastDot = ( ( suffix != null ) && ( suffix.length() > 0 ) ) ? filename.lastIndexOf( suffix ) : -1; + + if ( lastDot >= 0 ) + { + return filename.substring( i, lastDot ); + } + else if ( i > 0 ) + { + return filename.substring( i ); + } + else + { + return filename; // else returns all (no path and no extension) + } + } + + /** + * Returns the extension portion of a file specification string. + * This everything after the last dot '.' in the filename (NOT including + * the dot). + */ + public static String extension( String filename ) + { + int lastDot = filename.lastIndexOf( '.' ); + + if ( lastDot >= 0 ) + { + return filename.substring( lastDot + 1 ); + } + else + { + return ""; + } + } + + /** + * Check if a file exits. + * + * @param fileName The name of the file to check. + * @return true if file exists. + */ + public static boolean fileExists( String fileName ) + { + File file = new File( fileName ); + return file.exists(); + } + + public static String fileRead( String file ) + throws IOException + { + return fileRead( new File( file ) ); + } + + public static String fileRead( File file ) + throws IOException + { + StringBuffer buf = new StringBuffer(); + + FileInputStream in = new FileInputStream( file ); + + int count; + byte[] b = new byte[512]; + while ( ( count = in.read( b ) ) > 0 ) // blocking read + { + buf.append( new String( b, 0, count ) ); + } + + in.close(); + + return buf.toString(); + } + + /** + * Writes data to a file. The file will be created if it does not exist. + * + * @param fileName The name of the file to write. + * @param data The content to write to the file. + */ + public static void fileWrite( String fileName, String data ) + throws IOException + { + FileOutputStream out = new FileOutputStream( fileName ); + out.write( data.getBytes() ); + out.close(); + } + + /** + * Deletes a file. + * + * @param fileName The name of the file to delete. + */ + public static void fileDelete( String fileName ) + { + File file = new File( fileName ); + file.delete(); + } + + /** + * Waits for NFS to propagate a file creation, imposing a timeout. + * + * @param fileName The name of the file. + * @param seconds The maximum time in seconds to wait. + * @return True if file exists. + */ + public static boolean waitFor( String fileName, int seconds ) + { + return waitFor( new File( fileName ), seconds ); + } + + public static boolean waitFor( File file, int seconds ) + { + int timeout = 0; + int tick = 0; + while ( !file.exists() ) + { + if ( tick++ >= 10 ) + { + tick = 0; + if ( timeout++ > seconds ) + { + return false; + } + } + try + { + Thread.sleep( 100 ); + } + catch ( InterruptedException ignore ) + { + } + } + return true; + } + + /** + * Creates a file handle. + * + * @param fileName The name of the file. + * @return A 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 opened
  • + *
  • destination cannot be written to
  • + *
  • an IO error occurs during copying
  • + *
+ */ + public static void copyURLToFile( final URL source, final File destination ) + throws IOException + { + //does destination 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 InputStream input = source.openStream(); + final FileOutputStream output = new FileOutputStream( destination ); + IOUtil.copy( input, output ); + + input.close(); + output.close(); + } + + /** + * Normalize a path. + * Eliminates "/../" and "/./" in a string. Returns null 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
+ *

+ *

+ * Thieved from Tomcat sources... + * + * @return The concatenated paths, or null if error occurs + */ + public static String catPath( final String lookupPath, final String path ) + { + // Cut off the last slash and everything beyond + int index = lookupPath.lastIndexOf( "/" ); + String lookup = lookupPath.substring( 0, index ); + String pth = path; + + // Deal with .. by chopping dirs off the lookup path + while ( pth.startsWith( "../" ) ) + { + if ( lookup.length() > 0 ) + { + index = lookup.lastIndexOf( "/" ); + lookup = lookup.substring( 0, index ); + } + else + { + // More ..'s than dirs, return null + return null; + } + + index = pth.indexOf( "../" ) + 3; + pth = pth.substring( index ); + } + + return new StringBuffer( lookup ).append( "/" ).append( pth ).toString(); + } + + /** + * Resolve a file 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: + *

    + *
  • It will include empty directories. + *
  • The sourceDirectory must exists. + *
+ * + * @param sourceDirectory + * @param destinationDirectory + * @throws IOException + */ + public static void copyDirectoryStructure( File sourceDirectory, File destinationDirectory ) + throws IOException + { + if ( !sourceDirectory.exists() ) + { + throw new IOException( "Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ")." ); + } + + File[] files = sourceDirectory.listFiles(); + + String sourcePath = sourceDirectory.getAbsolutePath(); + + for ( int i = 0; i < files.length; i++ ) + { + File file = files[i]; + + String dest = file.getAbsolutePath(); + + dest = dest.substring( sourcePath.length() + 1 ); + + File destination = new File( destinationDirectory, dest ); + + if ( file.isFile() ) + { + destination = destination.getParentFile(); + + FileUtils.copyFileToDirectory( file, destination ); + } + else if ( file.isDirectory() ) + { + if ( !destination.exists() && !destination.mkdirs() ) + { + throw new IOException( + "Could not create destination directory '" + destination.getAbsolutePath() + "'." ); + } + + copyDirectoryStructure( file, destination ); + } + else + { + throw new IOException( "Unknown file type: " + file.getAbsolutePath() ); + } + } + } + + /** + * Renames a file, even if that involves crossing file system boundaries. + *

+ *

This will remove to (if it exists), ensure that + * to's parent directory exists and move + * from, which involves deleting from as + * well.

+ * + * @param from the file to move + * @param to the new file name + * @throws IOException if anything bad happens during this + * process. Note that 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).

+ * + * @author Peter Donald + * @author Jeff Turner + * @version CVS $Revision$ $Date$ + * @since 4.0 + */ + +/* + * Behold, intrepid explorers; a map of this class: + * + * Method Input Output Dependency + * ------ ----- ------ ------- + * 1 copy InputStream OutputStream (primitive) + * 2 copy Reader Writer (primitive) + * + * 3 copy InputStream Writer 2 + * 4 toString InputStream String 3 + * 5 toByteArray InputStream byte[] 1 + * + * 6 copy Reader OutputStream 2 + * 7 toString Reader String 2 + * 8 toByteArray Reader byte[] 6 + * + * 9 copy String OutputStream 2 + * 10 copy String Writer (trivial) + * 11 toByteArray String byte[] 9 + * + * 12 copy byte[] Writer 3 + * 13 toString byte[] String 12 + * 14 copy byte[] OutputStream (trivial) + * + * + * Note that only the first two methods shuffle bytes; the rest use these two, or (if possible) copy + * using native Java copy methods. As there are method variants to specify buffer size and encoding, + * each row may correspond to up to 4 methods. + * + */ + +public final class IOUtil +{ + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + /** + * Private constructor to prevent instantiation. + */ + private IOUtil() + { + } + + /////////////////////////////////////////////////////////////// + // Core copy methods + /////////////////////////////////////////////////////////////// + + /** + * Copy bytes from an 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 of items to be include in the outpur + * @param baseDir the directory to add + */ + protected void addDirectory( Map includes, File baseDir ) throws IOException + { + addDirectory( includes, "", baseDir ); + } + + /** + * Add all files in the specified directory to the archive. + * + * @param includes a map of items to be include in the outpur + * @param prefix value to be added to the front of jar entry names + * @param baseDir the directory to add + */ + protected void addDirectory( Map includes, String prefix, File baseDir ) throws IOException + { + addDirectory( includes, null, null, prefix, baseDir ); + } + + /** + * Add all files in the specified directory to the archive. + * + * @param includes a map of items to be include in the outpur + * @param includesPattern Sets the list of include patterns to use + * @param excludesPattern Sets the list of exclude patterns to use + * @param prefix value to be added to the front of jar entry names + * @param baseDir the directory to add + */ + protected void addDirectory( Map includes, String includesPattern, String excludesPattern, String prefix, File baseDir ) + throws IOException + { + if ( !baseDir.exists() ) + { + return; + } + + DirectoryScanner scanner = new DirectoryScanner(); + scanner.setBasedir( baseDir ); + if ( includesPattern != null ) + { + scanner.setIncludes( StringUtils.split( includesPattern, "," ) ); + } + + if ( excludesPattern != null ) + { + scanner.setExcludes( StringUtils.split( excludesPattern, "," ) ); + } + scanner.scan(); + String[] files = scanner.getIncludedFiles(); + for ( int i = 0; i < files.length; i++ ) + { + String file = files[i]; + file = file.replace( '\\', '/' ); // todo shouldn't the scanner return platform independent names? + includes.put( prefix + file, new File( baseDir, file ) ); + } + } + + /** + * Create the jar file specified and include the listed files. + * + * @param jarFile the jar file to create + * @param includes a Mapof items to include; the key is the jar entry name + * @throws IOException if there is a problem writing the archive or reading the sources + */ + protected void createJar( File jarFile, Map includes ) throws IOException + { + File parentJarFile = jarFile.getParentFile(); + if ( !parentJarFile.exists() ) + { + parentJarFile.mkdirs(); + } + JarOutputStream jos = createJar( jarFile, createManifest() ); + try + { + addEntries( jos, includes ); + } + finally + { + jos.close(); + } + } + + /** + * Create a manifest for the jar file + * + * @return a default manifest; the Manifest-Version and Created-By attributes are initialized + */ + protected Manifest createManifest() + { + Manifest mf = new Manifest(); + Attributes attrs = mf.getMainAttributes(); + attrs.putValue( Attributes.Name.MANIFEST_VERSION.toString(), "1.0" ); + attrs.putValue( "Created-By", "2.0 (Apache Maven)" ); + return mf; + } + + /** + * Create the specified jar file and return a JarOutputStream to it + * + * @param jarFile the jar file to create + * @param mf the manifest to use + * @return a JarOutputStream that can be used to write to that file + * @throws IOException if there was a problem opening the file + */ + protected JarOutputStream createJar( File jarFile, Manifest mf ) throws IOException + { + jarFile.getParentFile().mkdirs(); + FileOutputStream fos = new FileOutputStream( jarFile ); + try + { + return new JarOutputStream( fos, mf ); + } + catch ( IOException e ) + { + try + { + fos.close(); + jarFile.delete(); + } + catch ( IOException e1 ) + { + // ignore + } + throw e; + } + } + + /** + * Add all entries in the supplied Map to the jar + * + * @param jos a JarOutputStream that can be used to write to the jar + * @param includes a Map of entries to add + * @throws IOException if there is a problem writing the archive or reading the sources + */ + protected void addEntries( JarOutputStream jos, Map includes ) throws IOException + { + for ( Iterator i = includes.entrySet().iterator(); i.hasNext(); ) + { + Map.Entry entry = (Map.Entry) i.next(); + String name = (String) entry.getKey(); + File file = (File) entry.getValue(); + addEntry( jos, name, file ); + } + } + + /** + * Add a single entry to the jar + * + * @param jos a JarOutputStream that can be used to write to the jar + * @param name the entry name to use; must be '/' delimited + * @param source the file to add + * @throws IOException if there is a problem writing the archive or reading the sources + */ + protected void addEntry( JarOutputStream jos, String name, File source ) throws IOException + { + FileInputStream fis = new FileInputStream( source ); + try + { + jos.putNextEntry( new JarEntry( name ) ); + int count; + while ( ( count = fis.read( buffer ) ) > 0 ) + { + jos.write( buffer, 0, count ); + } + jos.closeEntry(); + } + finally + { + fis.close(); + } + } + +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/SelectorUtils.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/SelectorUtils.java new file mode 100644 index 0000000000..b404d03a68 --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/SelectorUtils.java @@ -0,0 +1,626 @@ +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.util.StringTokenizer; +import java.util.Vector; + +/** + *

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:
+ * '*' 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 match( pattern, str, true ); + } + + /** + * 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. + */ + 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