From 16c46a696654c5b31fcd4272f7c5c99c3a5c54a6 Mon Sep 17 00:00:00 2001
From: Brett Leslie Porter Returns the names of the files which were selected out and
+ * therefore not ultimately included. The names are relative to the base directory. This involves
+ * performing a slow scan if one has not already been completed. Returns the names of the directories which were selected out and
+ * therefore not ultimately included. The names are relative to the base directory. This involves
+ * performing a slow scan if one has not already been completed. It doesn't really test for symbolic links but whether the
+ * canonical and absolute paths of the file are identical - this
+ * may lead to false positives on some platforms. Methods exist to retrieve the components of a typical file path. For example
+ * destinationFile
against the remote source
+ * @param proxyHost Proxy Host (if proxy is required), or null
+ * @param proxyPort Proxy Port (if proxy is required), or null
+ * @param proxyUserName Proxy Username (if authentification is required),
+ * or null.
+ * @param proxyPassword Proxy Password (if authentification is required),
+ * or null.
+ * @param useChecksum Flag to indicate the use of the checksum for the retrieved
+ * artifact if it is available.
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static void getFile( String url, File destinationFile, boolean ignoreErrors, boolean useTimestamp,
+ String proxyHost, String proxyPort, String proxyUserName, String proxyPassword,
+ boolean useChecksum )
+ throws IOException
+ {
+ // Get the requested file.
+ getFile( url, destinationFile, ignoreErrors, useTimestamp, proxyHost, proxyPort, proxyUserName, proxyPassword );
+
+ // Get the checksum if requested.
+ if ( useChecksum )
+ {
+ File checksumFile = new File( destinationFile + ".md5" );
+
+ try
+ {
+ getFile( url + ".md5", checksumFile, ignoreErrors, useTimestamp, proxyHost, proxyPort, proxyUserName,
+ proxyPassword );
+ }
+ catch ( Exception e )
+ {
+ // do nothing we will check later in the process
+ // for the checksums.
+ }
+ }
+ }
+
+ /**
+ * Retrieve a remote file. Throws an Exception on errors unless the
+ * ifnoreErrors flag is set to True
+ *
+ * @param url the of the file to retrieve
+ * @param destinationFile where to store it
+ * @param ignoreErrors whether to ignore errors during I/O or throw an
+ * exception when they happen
+ * @param useTimestamp whether to check the modified timestamp on the
+ * destinationFile
against the remote source
+ * @param proxyHost Proxy Host (if proxy is required), or null
+ * @param proxyPort Proxy Port (if proxy is required), or null
+ * @param proxyUserName Proxy Username (if authentification is required),
+ * or null
+ * @param proxyPassword Proxy Password (if authentification is required),
+ * or null
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static void getFile( String url, File destinationFile, boolean ignoreErrors, boolean useTimestamp,
+ String proxyHost, String proxyPort, String proxyUserName, String proxyPassword )
+ throws IOException
+ {
+ //set the timestamp to the file date.
+ long timestamp = -1;
+ if ( useTimestamp && destinationFile.exists() )
+ {
+ timestamp = destinationFile.lastModified();
+ }
+
+ try
+ {
+ getFile( url, destinationFile, timestamp, proxyHost, proxyPort, proxyUserName, proxyPassword );
+ }
+ catch ( IOException ex )
+ {
+ if ( !ignoreErrors )
+ {
+ throw ex;
+ }
+ }
+ }
+
+ /**
+ * Retrieve a remote file.
+ *
+ * @param url the URL of the file to retrieve
+ * @param destinationFile where to store it
+ * @param timestamp if provided, the remote URL is only retrieved if it was
+ * modified more recently than timestamp. Otherwise, negative value indicates that
+ * the remote URL should be retrieved unconditionally.
+ * @param proxyHost Proxy Host (if proxy is required), or null
+ * @param proxyPort Proxy Port (if proxy is required), or null
+ * @param proxyUserName Proxy Username (if authentification is required),
+ * or null
+ * @param proxyPassword Proxy Password (if authentification is required),
+ * or null
+ * @throws IOException If an I/O exception occurs.
+ */
+ public static void getFile( String url, File destinationFile, long timestamp, String proxyHost, String proxyPort,
+ String proxyUserName, String proxyPassword )
+ throws IOException
+ {
+ String[] s = parseUrl( url );
+ String username = s[0];
+ String password = s[1];
+ String parsedUrl = s[2];
+
+ URL source = new URL( parsedUrl );
+
+ //set proxy connection
+ useProxyUser( proxyHost, proxyPort, proxyUserName, proxyPassword );
+
+ //set up the URL connection
+ URLConnection connection = source.openConnection();
+
+ //modify the headers
+ if ( timestamp >= 0 )
+ {
+ connection.setIfModifiedSince( timestamp );
+ }
+ // prepare Java 1.1 style credentials
+ if ( username != null || password != null )
+ {
+ String up = username + ":" + password;
+ String encoding = Base64.encode( up.getBytes(), false );
+ connection.setRequestProperty( "Authorization", "Basic " + encoding );
+ }
+
+ connection.setRequestProperty( "Pragma", "no-cache" );
+
+ //connect to the remote site (may take some time)
+ connection.connect();
+ //next test for a 304 result (HTTP only)
+ if ( connection instanceof HttpURLConnection )
+ {
+ HttpURLConnection httpConnection = (HttpURLConnection) connection;
+ // although HTTPUrlConnection javadocs says FileNotFoundException should be
+ // thrown on a 404 error, that certainly does not appear to be the case, so
+ // test for 404 ourselves, and throw FileNotFoundException as needed
+ if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND )
+ {
+ throw new FileNotFoundException( url + " (HTTP Error: " + httpConnection.getResponseCode() +
+ " " + httpConnection.getResponseMessage() + ")" );
+ }
+ if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED )
+ {
+ return;
+ }
+ // test for 401 result (HTTP only)
+ if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED )
+ {
+ throw new IOException( "Not authorized." );
+ }
+ // test for 407 result (HTTP only)
+ if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_PROXY_AUTH )
+ {
+ throw new IOException( "Not authorized by proxy." );
+ }
+ }
+
+ // REVISIT: at this point even non HTTP connections may support the
+ // if-modified-since behaviour - we just check the date of the
+ // content and skip the write if it is not newer.
+ // Some protocols (FTP) dont include dates, of course.
+
+ InputStream is = null;
+ IOException isException = null;
+ for ( int i = 0; i < 3; i++ )
+ {
+ try
+ {
+ is = connection.getInputStream();
+ break;
+ }
+ catch ( IOException ex )
+ {
+ isException = ex;
+ }
+ }
+ if ( is == null )
+ {
+ throw isException;
+ }
+
+ if ( connection.getLastModified() <= timestamp && connection.getLastModified() != 0 )
+ {
+ return;
+ }
+
+ FileOutputStream fos = new FileOutputStream( destinationFile );
+
+ byte[] buffer = new byte[100 * 1024];
+ int length;
+
+ while ( ( length = is.read( buffer ) ) >= 0 )
+ {
+ fos.write( buffer, 0, length );
+ System.out.print( "." );
+ }
+
+ System.out.println();
+ fos.close();
+ is.close();
+
+ // if (and only if) the use file time option is set, then the
+ // saved file now has its timestamp set to that of the downloaded
+ // file
+ if ( timestamp >= 0 )
+ {
+ long remoteTimestamp = connection.getLastModified();
+ if ( remoteTimestamp != 0 )
+ {
+ touchFile( destinationFile, remoteTimestamp );
+ }
+ }
+ }
+
+ /**
+ * Parse an url which might contain a username and password. If the
+ * given url doesn't contain a username and password then return the
+ * origin url unchanged.
+ *
+ * @param url The url to parse.
+ * @return The username, password and url.
+ * @throws RuntimeException if the url is (very) invalid
+ */
+ static String[] parseUrl( String url )
+ {
+ String[] parsedUrl = new String[3];
+ parsedUrl[0] = null;
+ parsedUrl[1] = null;
+ parsedUrl[2] = url;
+
+ // We want to be able to deal with Basic Auth where the username
+ // and password are part of the URL. An example of the URL string
+ // we would like to be able to parse is like the following:
+ //
+ // http://username:password@repository.mycompany.com
+
+ int i = url.indexOf( "@" );
+ if ( i > 0 )
+ {
+ String protocol = url.substring( 0, url.indexOf( "://" ) ) + "://";
+ String s = url.substring( protocol.length(), i );
+ int j = s.indexOf( ":" );
+ parsedUrl[0] = s.substring( 0, j );
+ parsedUrl[1] = s.substring( j + 1 );
+ parsedUrl[2] = protocol + url.substring( i + 1 );
+ }
+
+ return parsedUrl;
+ }
+
+ /**
+ * set the timestamp of a named file to a specified time.
+ *
+ * @param file the file to touch
+ * @param timemillis in milliseconds since the start of the era
+ * @return true if it succeeded. False means that this is a java1.1 system
+ * and that file times can not be set
+ * @throws RuntimeException Thrown in unrecoverable error. Likely this
+ * comes from file access failures.
+ */
+ private static boolean touchFile( File file, long timemillis )
+ {
+ long modifiedTime;
+
+ if ( timemillis < 0 )
+ {
+ modifiedTime = System.currentTimeMillis();
+ }
+ else
+ {
+ modifiedTime = timemillis;
+ }
+
+ file.setLastModified( modifiedTime );
+ return true;
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OfflineArtifactResolver.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OfflineArtifactResolver.java
new file mode 100644
index 0000000000..4855095281
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OfflineArtifactResolver.java
@@ -0,0 +1,44 @@
+package org.apache.maven.bootstrap.download;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.bootstrap.model.Repository;
+
+import java.util.Collection;
+
+/**
+ * Resolve from the local repository - don't attempt to download or check out.
+ */
+public class OfflineArtifactResolver
+ extends AbstractArtifactResolver
+{
+ public OfflineArtifactResolver( Repository localRepository )
+ {
+ super( localRepository );
+ }
+
+ public void downloadDependencies( Collection dependencies )
+ throws DownloadFailedException
+ {
+ // Nothing to see here
+ }
+
+ public boolean isOnline()
+ {
+ return false;
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OnlineArtifactDownloader.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OnlineArtifactDownloader.java
new file mode 100644
index 0000000000..f60a7d591a
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/OnlineArtifactDownloader.java
@@ -0,0 +1,332 @@
+package org.apache.maven.bootstrap.download;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.bootstrap.model.Dependency;
+import org.apache.maven.bootstrap.model.Repository;
+import org.apache.maven.bootstrap.util.FileUtils;
+import org.apache.maven.bootstrap.util.StringUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class OnlineArtifactDownloader
+ extends AbstractArtifactResolver
+{
+ public static final String SNAPSHOT_SIGNATURE = "-SNAPSHOT";
+
+ private boolean useTimestamp = true;
+
+ private boolean ignoreErrors = false;
+
+ private String proxyHost;
+
+ private String proxyPort;
+
+ private String proxyUserName;
+
+ private String proxyPassword;
+
+ private static final String REPO_URL = "http://repo1.maven.org/maven2";
+
+ private Map downloadedArtifacts = new HashMap();
+
+ private List remoteRepositories;
+
+ public OnlineArtifactDownloader( Repository localRepository )
+ throws Exception
+ {
+ super( localRepository );
+ }
+
+ public void setProxy( String host, String port, String userName, String password )
+ {
+ proxyHost = host;
+ proxyPort = port;
+ proxyUserName = userName;
+ proxyPassword = password;
+ System.out.println( "Using the following proxy : " + proxyHost + "/" + proxyPort );
+ }
+
+ public void downloadDependencies( Collection dependencies )
+ throws DownloadFailedException
+ {
+ for ( Iterator j = dependencies.iterator(); j.hasNext(); )
+ {
+ Dependency dep = (Dependency) j.next();
+
+ if ( isAlreadyBuilt( dep ) )
+ {
+ continue;
+ }
+
+ String dependencyConflictId = dep.getDependencyConflictId();
+ if ( !downloadedArtifacts.containsKey( dependencyConflictId ) )
+ {
+ File destinationFile = getLocalRepository().getArtifactFile( dep );
+ // The directory structure for this project may
+ // not exists so create it if missing.
+ File directory = destinationFile.getParentFile();
+
+ if ( !directory.exists() )
+ {
+ directory.mkdirs();
+ }
+
+ if ( !getRemoteArtifact( dep, destinationFile ) )
+ {
+ throw new DownloadFailedException( "Failed to download " + dep );
+ }
+
+ downloadedArtifacts.put( dependencyConflictId, dep );
+ }
+ else
+ {
+ Dependency d = (Dependency) downloadedArtifacts.get( dependencyConflictId );
+ dep.setResolvedVersion( d.getResolvedVersion() );
+ }
+ }
+ }
+
+ public boolean isOnline()
+ {
+ return true;
+ }
+
+ private static boolean isSnapshot( Dependency dep )
+ {
+ return dep.getVersion().indexOf( SNAPSHOT_SIGNATURE ) >= 0;
+ }
+
+ private boolean getRemoteArtifact( Dependency dep, File destinationFile )
+ {
+ boolean fileFound = false;
+
+ for ( Iterator i = getRemoteRepositories().iterator(); i.hasNext(); )
+ {
+ Repository remoteRepo = (Repository) i.next();
+
+ boolean snapshot = isSnapshot( dep );
+ if ( snapshot && !remoteRepo.isSnapshots() )
+ {
+ continue;
+ }
+ if ( !snapshot && !remoteRepo.isReleases() )
+ {
+ continue;
+ }
+
+ // The username and password parameters are not being used here.
+ String url = remoteRepo.getBasedir() + "/" + remoteRepo.getArtifactPath( dep );
+
+ // Attempt to retrieve the artifact and set the checksum if retrieval
+ // of the checksum file was successful.
+ try
+ {
+ String version = dep.getVersion();
+ if ( snapshot )
+ {
+ String filename = "maven-metadata-" + remoteRepo.getId() + ".xml";
+ File localFile = getLocalRepository().getMetadataFile( dep.getGroupId(), dep.getArtifactId(),
+ dep.getVersion(), dep.getType(),
+ "maven-metadata-local.xml" );
+ File remoteFile = getLocalRepository().getMetadataFile( dep.getGroupId(), dep.getArtifactId(),
+ dep.getVersion(), dep.getType(), filename );
+ String metadataPath = remoteRepo.getMetadataPath( dep.getGroupId(), dep.getArtifactId(),
+ dep.getVersion(), dep.getType(),
+ "maven-metadata.xml" );
+ String metaUrl = remoteRepo.getBasedir() + "/" + metadataPath;
+ log( "Downloading " + metaUrl );
+ try
+ {
+ HttpUtils.getFile( metaUrl, remoteFile, ignoreErrors, true, proxyHost, proxyPort, proxyUserName,
+ proxyPassword, false );
+ }
+ catch ( IOException e )
+ {
+ log( "WARNING: remote metadata version not found, using local: " + e.getMessage() );
+ remoteFile.delete();
+ }
+
+ File file = localFile;
+ if ( remoteFile.exists() )
+ {
+ if ( !localFile.exists() )
+ {
+ file = remoteFile;
+ }
+ else
+ {
+ RepositoryMetadata localMetadata = RepositoryMetadata.read( localFile );
+
+ RepositoryMetadata remoteMetadata = RepositoryMetadata.read( remoteFile );
+
+ if ( remoteMetadata.getLastUpdatedUtc() > localMetadata.getLastUpdatedUtc() )
+ {
+ file = remoteFile;
+ }
+ else
+ {
+ file = localFile;
+ }
+ }
+ }
+
+ if ( file.exists() )
+ {
+ log( "Using metadata: " + file );
+
+ RepositoryMetadata metadata = RepositoryMetadata.read( file );
+
+ if ( !file.equals( localFile ) )
+ {
+ version = metadata.constructVersion( version );
+ }
+ log( "Resolved version: " + version );
+ dep.setResolvedVersion( version );
+ if ( !version.endsWith( "SNAPSHOT" ) )
+ {
+ String ver =
+ version.substring( version.lastIndexOf( "-", version.lastIndexOf( "-" ) - 1 ) + 1 );
+ String extension = url.substring( url.length() - 4 );
+ url = getSnapshotMetadataFile( url, ver + extension );
+ }
+ else if ( destinationFile.exists() )
+ {
+ // It's already there
+ return true;
+ }
+ }
+ }
+ if ( !"pom".equals( dep.getType() ) )
+ {
+ String name = dep.getArtifactId() + "-" + dep.getResolvedVersion() + ".pom";
+ File file = getLocalRepository().getMetadataFile( dep.getGroupId(), dep.getArtifactId(),
+ dep.getVersion(), dep.getType(), name );
+
+ file.getParentFile().mkdirs();
+
+ if ( !file.exists() || version.indexOf( "SNAPSHOT" ) >= 0 )
+ {
+ String filename = dep.getArtifactId() + "-" + version + ".pom";
+ String metadataPath = remoteRepo.getMetadataPath( dep.getGroupId(), dep.getArtifactId(),
+ dep.getVersion(), dep.getType(), filename );
+ String metaUrl = remoteRepo.getBasedir() + "/" + metadataPath;
+ log( "Downloading " + metaUrl );
+
+ try
+ {
+ HttpUtils.getFile( metaUrl, file, ignoreErrors, false, proxyHost, proxyPort, proxyUserName,
+ proxyPassword, false );
+ }
+ catch ( IOException e )
+ {
+ log( "Couldn't find POM - ignoring: " + e.getMessage() );
+ }
+ }
+ }
+
+ destinationFile = getLocalRepository().getArtifactFile( dep );
+ if ( !destinationFile.exists() )
+ {
+ log( "Downloading " + url );
+ HttpUtils.getFile( url, destinationFile, ignoreErrors, useTimestamp, proxyHost, proxyPort,
+ proxyUserName, proxyPassword, true );
+ if ( dep.getVersion().indexOf( "SNAPSHOT" ) >= 0 )
+ {
+ String name = StringUtils.replace( destinationFile.getName(), version, dep.getVersion() );
+ FileUtils.copyFile( destinationFile, new File( destinationFile.getParentFile(), name ) );
+ }
+ }
+
+ // Artifact was found, continue checking additional remote repos (if any)
+ // in case there is a newer version (i.e. snapshots) in another repo
+ fileFound = true;
+ }
+ catch ( FileNotFoundException e )
+ {
+ log( "Artifact not found at [" + url + "]" );
+ // Ignore
+ }
+ catch ( Exception e )
+ {
+ // If there are additional remote repos, then ignore exception
+ // as artifact may be found in another remote repo. If there
+ // are no more remote repos to check and the artifact wasn't found in
+ // a previous remote repo, then artifactFound is false indicating
+ // that the artifact could not be found in any of the remote repos
+ //
+ // arguably, we need to give the user better control (another command-
+ // line switch perhaps) of what to do in this case? Maven already has
+ // a command-line switch to work in offline mode, but what about when
+ // one of two or more remote repos is unavailable? There may be multiple
+ // remote repos for redundancy, in which case you probably want the build
+ // to continue. There may however be multiple remote repos because some
+ // artifacts are on one, and some are on another. In this case, you may
+ // want the build to break.
+ //
+ // print a warning, in any case, so user catches on to mistyped
+ // hostnames, or other snafus
+ log( "Error retrieving artifact from [" + url + "]: " + e );
+ }
+ }
+
+ return fileFound;
+ }
+
+ private static String getSnapshotMetadataFile( String filename, String s )
+ {
+ int index = filename.lastIndexOf( "SNAPSHOT" );
+ return filename.substring( 0, index ) + s;
+ }
+
+ private void log( String message )
+ {
+ System.out.println( message );
+ }
+
+ public List getRemoteRepositories()
+ {
+ if ( remoteRepositories == null )
+ {
+ remoteRepositories = new ArrayList();
+ }
+
+ if ( remoteRepositories.isEmpty() )
+ {
+ // TODO: use super POM?
+ remoteRepositories.add( new Repository( "central", REPO_URL, Repository.LAYOUT_DEFAULT, false, true ) );
+ // TODO: use maven root POM?
+ remoteRepositories.add( new Repository( "snapshots", "http://snapshots.maven.codehaus.org/maven2/",
+ Repository.LAYOUT_DEFAULT, true, false ) );
+ }
+
+ return remoteRepositories;
+ }
+
+ public void setRemoteRepositories( List remoteRepositories )
+ {
+ this.remoteRepositories = remoteRepositories;
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/download/RepositoryMetadata.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/RepositoryMetadata.java
new file mode 100644
index 0000000000..307b99a2cd
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/download/RepositoryMetadata.java
@@ -0,0 +1,395 @@
+package org.apache.maven.bootstrap.download;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.bootstrap.util.AbstractReader;
+import org.apache.maven.bootstrap.util.StringUtils;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * I/O for repository metadata.
+ *
+ * @author Brett Porter
+ * @version $Id$
+ */
+public class RepositoryMetadata
+{
+ private String snapshotTimestamp;
+
+ private int snapshotBuildNumber;
+
+ private String releaseVersion;
+
+ private String groupId;
+
+ private String artifactId;
+
+ private String version;
+
+ private List versions = new ArrayList();
+
+ private String latestVersion;
+
+ private boolean localCopy;
+
+ private String lastUpdated;
+
+ public String getSnapshotTimestamp()
+ {
+ return snapshotTimestamp;
+ }
+
+ public void setSnapshotTimestamp( String snapshotTimestamp )
+ {
+ this.snapshotTimestamp = snapshotTimestamp;
+ }
+
+ public int getSnapshotBuildNumber()
+ {
+ return snapshotBuildNumber;
+ }
+
+ public void setSnapshotBuildNumber( int snapshotBuildNumber )
+ {
+ this.snapshotBuildNumber = snapshotBuildNumber;
+ }
+
+ public String getGroupId()
+ {
+ return groupId;
+ }
+
+ public void setGroupId( String groupId )
+ {
+ this.groupId = groupId;
+ }
+
+ public String getArtifactId()
+ {
+ return artifactId;
+ }
+
+ public void setArtifactId( String artifactId )
+ {
+ this.artifactId = artifactId;
+ }
+
+ public String getVersion()
+ {
+ return version;
+ }
+
+ public void setVersion( String version )
+ {
+ this.version = version;
+ }
+
+ public List getVersions()
+ {
+ return versions;
+ }
+
+ public void setVersions( List versions )
+ {
+ this.versions = versions;
+ }
+
+ public String getReleaseVersion()
+ {
+ return releaseVersion;
+ }
+
+ public void setReleaseVersion( String releaseVersion )
+ {
+ this.releaseVersion = releaseVersion;
+ }
+
+ public String getLatestVersion()
+ {
+ return latestVersion;
+ }
+
+ public void setLatestVersion( String latestVersion )
+ {
+ this.latestVersion = latestVersion;
+ }
+
+ public void addVersion( String version )
+ {
+ versions.add( version );
+ }
+
+ public boolean isLocalCopy()
+ {
+ return localCopy;
+ }
+
+ public void setLocalCopy( boolean localCopy )
+ {
+ this.localCopy = localCopy;
+ }
+
+ public static RepositoryMetadata read( File file )
+ throws IOException, ParserConfigurationException, SAXException
+ {
+ return new Reader().parseMetadata( file );
+ }
+
+ public void write( File file )
+ throws IOException
+ {
+ new Writer( this ).write( file );
+ }
+
+ public String constructVersion( String baseVersion )
+ {
+ if ( snapshotTimestamp != null )
+ {
+ baseVersion = StringUtils.replace( baseVersion, "SNAPSHOT", snapshotTimestamp + "-" + snapshotBuildNumber );
+ }
+ return baseVersion;
+ }
+
+ public long getLastUpdatedUtc()
+ {
+ TimeZone timezone = TimeZone.getTimeZone( "UTC" );
+ DateFormat fmt = new SimpleDateFormat( "yyyyMMddHHmmss" );
+ fmt.setTimeZone( timezone );
+
+ try
+ {
+ return fmt.parse( lastUpdated ).getTime();
+ }
+ catch ( ParseException e )
+ {
+ return -1;
+ }
+ }
+
+ public void setLastUpdated( String lastUpdated )
+ {
+ this.lastUpdated = lastUpdated;
+ }
+
+ public String getLastUpdated()
+ {
+ return lastUpdated;
+ }
+
+ static class Reader
+ extends AbstractReader
+ {
+ private boolean insideVersioning;
+
+ private StringBuffer bodyText = new StringBuffer();
+
+ private boolean insideSnapshot;
+
+ private final RepositoryMetadata metadata = new RepositoryMetadata();
+
+ private boolean insideVersions;
+
+ public RepositoryMetadata parseMetadata( File metadataFile )
+ throws IOException, ParserConfigurationException, SAXException
+ {
+ parse( metadataFile );
+ return metadata;
+ }
+
+ public void startElement( String uri, String localName, String rawName, Attributes attributes )
+ {
+ if ( insideVersioning )
+ {
+ if ( "snapshot".equals( rawName ) )
+ {
+ insideSnapshot = true;
+ }
+ else if ( "versions".equals( rawName ) )
+ {
+ insideVersions = true;
+ }
+ }
+ else
+ {
+ // root element
+ if ( "versioning".equals( rawName ) )
+ {
+ insideVersioning = true;
+ }
+ }
+ }
+
+ public void characters( char buffer[], int start, int length )
+ {
+ bodyText.append( buffer, start, length );
+ }
+
+ private String getBodyText()
+ {
+ return bodyText.toString().trim();
+ }
+
+ public void endElement( String uri, String localName, String rawName )
+ throws SAXException
+ {
+ if ( insideVersioning )
+ {
+ if ( "versioning".equals( rawName ) )
+ {
+ insideVersioning = false;
+ }
+ else if ( insideSnapshot )
+ {
+ if ( "snapshot".equals( rawName ) )
+ {
+ insideSnapshot = false;
+ }
+ else if ( "buildNumber".equals( rawName ) )
+ {
+ try
+ {
+ metadata.setSnapshotBuildNumber( Integer.valueOf( getBodyText() ).intValue() );
+ }
+ catch ( NumberFormatException e )
+ {
+ // Ignore
+ }
+ }
+ else if ( "timestamp".equals( rawName ) )
+ {
+ metadata.setSnapshotTimestamp( getBodyText() );
+ }
+ else if ( "localCopy".equals( rawName ) )
+ {
+ metadata.setLocalCopy( Boolean.valueOf( getBodyText() ).booleanValue() );
+ }
+ }
+ else if ( insideVersions )
+ {
+ if ( "versions".equals( rawName ) )
+ {
+ insideVersions = false;
+ }
+ else if ( "version".equals( rawName ) )
+ {
+ metadata.addVersion( getBodyText() );
+ }
+ }
+ else if ( "latest".equals( rawName ) )
+ {
+ metadata.setLatestVersion( getBodyText() );
+ }
+ else if ( "release".equals( rawName ) )
+ {
+ metadata.setReleaseVersion( getBodyText() );
+ }
+ else if ( "lastUpdated".equals( rawName ) )
+ {
+ metadata.setLastUpdated( getBodyText() );
+ }
+ }
+ else if ( "groupId".equals( rawName ) )
+ {
+ metadata.setGroupId( getBodyText() );
+ }
+ else if ( "artifactId".equals( rawName ) )
+ {
+ metadata.setArtifactId( getBodyText() );
+ }
+ else if ( "version".equals( rawName ) )
+ {
+ metadata.setVersion( getBodyText() );
+ }
+ bodyText = new StringBuffer();
+ }
+
+ }
+
+ static class Writer
+ {
+ private final RepositoryMetadata metadata;
+
+ public Writer( RepositoryMetadata metadata )
+ {
+ this.metadata = metadata;
+ }
+
+ public void write( File file )
+ throws IOException
+ {
+ PrintWriter w = new PrintWriter( new FileWriter( file ) );
+
+ try
+ {
+ w.println( "File.separator
('/' under UNIX, '\' under Windows).
+ * For example, "abc/def/ghi/xyz.java" is split up in the segments "abc",
+ * "def","ghi" and "xyz.java".
+ * The same is done for the pattern against which should be matched.
+ *
+ * The segments of the name and the pattern are then matched against each
+ * other. When '**' is used for a path segment in the pattern, it matches
+ * zero or more path segments of the name.
+ *
+ * There is a special case regarding the use of File.separator
s
+ * at the beginning of the pattern and the string to match:
+ * When a pattern starts with a File.separator
, the string
+ * to match must also start with a File.separator
.
+ * When a pattern does not start with a File.separator
, the
+ * string to match may not start with a File.separator
.
+ * When one of these rules is not obeyed, the string will not
+ * match.
+ *
+ * When a name path segment is matched against a pattern path segment, the
+ * following special characters can be used:
+ * '*' matches zero or more characters
+ * '?' matches one character.
+ *
+ * Examples:
+ *
+ * "**\*.class" matches all .class files/dirs in a directory tree.
+ *
+ * "test\a??.java" matches all files/dirs which start with an 'a', then two
+ * more characters and then ".java", in a directory called test.
+ *
+ * "**" matches everything in a directory tree.
+ *
+ * "**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where
+ * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").
+ *
+ * Case sensitivity may be turned off if necessary. By default, it is
+ * turned on.
+ *
+ * Example of usage:
+ *
+ * String[] includes = {"**\\*.class"};
+ * String[] excludes = {"modules\\*\\**"};
+ * ds.setIncludes(includes);
+ * ds.setExcludes(excludes);
+ * ds.setBasedir(new File("test"));
+ * ds.setCaseSensitive(true);
+ * ds.scan();
+ *
+ * System.out.println("FILES:");
+ * String[] files = ds.getIncludedFiles();
+ * for (int i = 0; i < files.length; i++) {
+ * System.out.println(files[i]);
+ * }
+ *
+ * This will scan a directory called test for .class files, but excludes all
+ * files in all proper subdirectories of a directory called "modules"
+ *
+ * @author Arnout J. Kuiper
+ * ajkuiper@wxs.nl
+ * @author Magesh Umasankar
+ * @author Bruce Atherton
+ * @author Antoine Levy-Lambert
+ */
+public class DirectoryScanner
+{
+ /**
+ * Patterns which should be excluded by default.
+ *
+ * @see #addDefaultExcludes()
+ */
+ protected static final String[] DEFAULTEXCLUDES = {
+ // Miscellaneous typical temporary files
+ "**/*~",
+ "**/#*#",
+ "**/.#*",
+ "**/%*%",
+ "**/._*",
+
+ // CVS
+ "**/CVS",
+ "**/CVS/**",
+ "**/.cvsignore",
+
+ // SCCS
+ "**/SCCS",
+ "**/SCCS/**",
+
+ // Visual SourceSafe
+ "**/vssver.scc",
+
+ // Subversion
+ "**/.svn",
+ "**/.svn/**",
+
+ // Mac
+ "**/.DS_Store"
+ };
+
+ /**
+ * The base directory to be scanned.
+ */
+ protected File basedir;
+
+ /**
+ * The patterns for the files to be included.
+ */
+ protected String[] includes;
+
+ /**
+ * The patterns for the files to be excluded.
+ */
+ protected String[] excludes;
+
+ /**
+ * The files which matched at least one include and no excludes
+ * and were selected.
+ */
+ protected Vector filesIncluded;
+
+ /**
+ * The files which did not match any includes or selectors.
+ */
+ protected Vector filesNotIncluded;
+
+ /**
+ * The files which matched at least one include and at least
+ * one exclude.
+ */
+ protected Vector filesExcluded;
+
+ /**
+ * The directories which matched at least one include and no excludes
+ * and were selected.
+ */
+ protected Vector dirsIncluded;
+
+ /**
+ * The directories which were found and did not match any includes.
+ */
+ protected Vector dirsNotIncluded;
+
+ /**
+ * The directories which matched at least one include and at least one
+ * exclude.
+ */
+ protected Vector dirsExcluded;
+
+ /**
+ * The files which matched at least one include and no excludes and
+ * which a selector discarded.
+ */
+ protected Vector filesDeselected;
+
+ /**
+ * The directories which matched at least one include and no excludes
+ * but which a selector discarded.
+ */
+ protected Vector dirsDeselected;
+
+ /**
+ * Whether or not our results were built by a slow scan.
+ */
+ protected boolean haveSlowResults = false;
+
+ /**
+ * Whether or not the file system should be treated as a case sensitive
+ * one.
+ */
+ protected boolean isCaseSensitive = true;
+
+ /**
+ * Whether or not symbolic links should be followed.
+ *
+ * @since Ant 1.5
+ */
+ private boolean followSymlinks = true;
+
+ /**
+ * Whether or not everything tested so far has been included.
+ */
+ protected boolean everythingIncluded = true;
+
+ /**
+ * Sole constructor.
+ */
+ public DirectoryScanner()
+ {
+ }
+
+ /**
+ * Tests whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ *
+ * This is not a general purpose test and should only be used if you
+ * can live with false positives. For example, pattern=**\a
+ * and str=b
will yield true
.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ protected static boolean matchPatternStart( String pattern, String str )
+ {
+ return SelectorUtils.matchPatternStart( pattern, str );
+ }
+
+ /**
+ * Tests whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ *
+ * This is not a general purpose test and should only be used if you
+ * can live with false positives. For example, pattern=**\a
+ * and str=b
will yield true
.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ protected static boolean matchPatternStart( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ return SelectorUtils.matchPatternStart( pattern, str, isCaseSensitive );
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @return true
if the pattern matches against the string,
+ * or false
otherwise.
+ */
+ protected static boolean matchPath( String pattern, String str )
+ {
+ return SelectorUtils.matchPath( pattern, str );
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return true
if the pattern matches against the string,
+ * or false
otherwise.
+ */
+ protected static boolean matchPath( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ return SelectorUtils.matchPath( pattern, str, isCaseSensitive );
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:
+ * '*' means zero or more characters
+ * '?' means one and only one character
+ *
+ * @param pattern The pattern to match against.
+ * Must not be null
.
+ * @param str The string which must be matched against the pattern.
+ * Must not be null
.
+ * @return true
if the string matches against the pattern,
+ * or false
otherwise.
+ */
+ public static boolean match( String pattern, String str )
+ {
+ return SelectorUtils.match( pattern, str );
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:
+ * '*' means zero or more characters
+ * '?' means one and only one character
+ *
+ * @param pattern The pattern to match against.
+ * Must not be null
.
+ * @param str The string which must be matched against the pattern.
+ * Must not be null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return true
if the string matches against the pattern,
+ * or false
otherwise.
+ */
+ protected static boolean match( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ return SelectorUtils.match( pattern, str, isCaseSensitive );
+ }
+
+ /**
+ * Sets the base directory to be scanned. This is the directory which is
+ * scanned recursively. All '/' and '\' characters are replaced by
+ * File.separatorChar
, so the separator used need not match
+ * File.separatorChar
.
+ *
+ * @param basedir The base directory to scan.
+ * Must not be null
.
+ */
+ public void setBasedir( String basedir )
+ {
+ setBasedir( new File( basedir.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ) ) );
+ }
+
+ /**
+ * Sets the base directory to be scanned. This is the directory which is
+ * scanned recursively.
+ *
+ * @param basedir The base directory for scanning.
+ * Should not be null
.
+ */
+ public void setBasedir( File basedir )
+ {
+ this.basedir = basedir;
+ }
+
+ /**
+ * Returns the base directory to be scanned.
+ * This is the directory which is scanned recursively.
+ *
+ * @return the base directory to be scanned
+ */
+ public File getBasedir()
+ {
+ return basedir;
+ }
+
+ /**
+ * Sets whether or not the file system should be regarded as case sensitive.
+ *
+ * @param isCaseSensitive whether or not the file system should be
+ * regarded as a case sensitive one
+ */
+ public void setCaseSensitive( boolean isCaseSensitive )
+ {
+ this.isCaseSensitive = isCaseSensitive;
+ }
+
+ /**
+ * Sets whether or not symbolic links should be followed.
+ *
+ * @param followSymlinks whether or not symbolic links should be followed
+ */
+ public void setFollowSymlinks( boolean followSymlinks )
+ {
+ this.followSymlinks = followSymlinks;
+ }
+
+ /**
+ * Sets the list of include patterns to use. All '/' and '\' characters
+ * are replaced by File.separatorChar
, so the separator used
+ * need not match File.separatorChar
.
+ *
+ * When a pattern ends with a '/' or '\', "**" is appended.
+ *
+ * @param includes A list of include patterns.
+ * May be null
, indicating that all files
+ * should be included. If a non-null
+ * list is given, all elements must be
+ * non-null
.
+ */
+ public void setIncludes( String[] includes )
+ {
+ if ( includes == null )
+ {
+ this.includes = null;
+ }
+ else
+ {
+ this.includes = new String[includes.length];
+ for ( int i = 0; i < includes.length; i++ )
+ {
+ String pattern;
+ pattern = includes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
+ if ( pattern.endsWith( File.separator ) )
+ {
+ pattern += "**";
+ }
+ this.includes[i] = pattern;
+ }
+ }
+ }
+
+
+ /**
+ * Sets the list of exclude patterns to use. All '/' and '\' characters
+ * are replaced by File.separatorChar
, so the separator used
+ * need not match File.separatorChar
.
+ *
+ * When a pattern ends with a '/' or '\', "**" is appended.
+ *
+ * @param excludes A list of exclude patterns.
+ * May be null
, indicating that no files
+ * should be excluded. If a non-null
list is
+ * given, all elements must be non-null
.
+ */
+ public void setExcludes( String[] excludes )
+ {
+ if ( excludes == null )
+ {
+ this.excludes = null;
+ }
+ else
+ {
+ this.excludes = new String[excludes.length];
+ for ( int i = 0; i < excludes.length; i++ )
+ {
+ String pattern;
+ pattern = excludes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
+ if ( pattern.endsWith( File.separator ) )
+ {
+ pattern += "**";
+ }
+ this.excludes[i] = pattern;
+ }
+ }
+ }
+
+ /**
+ * Returns whether or not the scanner has included all the files or
+ * directories it has come across so far.
+ *
+ * @return true
if all files and directories which have
+ * been found so far have been included.
+ */
+ public boolean isEverythingIncluded()
+ {
+ return everythingIncluded;
+ }
+
+ /**
+ * Scans the base directory for files which match at least one include
+ * pattern and don't match any exclude patterns. If there are selectors
+ * then the files must pass muster there, as well.
+ *
+ * @throws IllegalStateException if the base directory was set
+ * incorrectly (i.e. if it is null
, doesn't exist,
+ * or isn't a directory).
+ */
+ public void scan() throws IllegalStateException
+ {
+ if ( basedir == null )
+ {
+ throw new IllegalStateException( "No basedir set" );
+ }
+ if ( !basedir.exists() )
+ {
+ throw new IllegalStateException( "basedir " + basedir
+ + " does not exist" );
+ }
+ if ( !basedir.isDirectory() )
+ {
+ throw new IllegalStateException( "basedir " + basedir
+ + " is not a directory" );
+ }
+
+ if ( includes == null )
+ {
+ // No includes supplied, so set it to 'matches all'
+ includes = new String[1];
+ includes[0] = "**";
+ }
+ if ( excludes == null )
+ {
+ excludes = new String[0];
+ }
+
+ filesIncluded = new Vector();
+ filesNotIncluded = new Vector();
+ filesExcluded = new Vector();
+ filesDeselected = new Vector();
+ dirsIncluded = new Vector();
+ dirsNotIncluded = new Vector();
+ dirsExcluded = new Vector();
+ dirsDeselected = new Vector();
+
+ if ( isIncluded( "" ) )
+ {
+ if ( !isExcluded( "" ) )
+ {
+ if ( isSelected( "", basedir ) )
+ {
+ dirsIncluded.addElement( "" );
+ }
+ else
+ {
+ dirsDeselected.addElement( "" );
+ }
+ }
+ else
+ {
+ dirsExcluded.addElement( "" );
+ }
+ }
+ else
+ {
+ dirsNotIncluded.addElement( "" );
+ }
+ scandir( basedir, "", true );
+ }
+
+ /**
+ * Top level invocation for a slow scan. A slow scan builds up a full
+ * list of excluded/included files/directories, whereas a fast scan
+ * will only have full results for included files, as it ignores
+ * directories which can't possibly hold any included files/directories.
+ *
+ * Returns immediately if a slow scan has already been completed.
+ */
+ protected void slowScan()
+ {
+ if ( haveSlowResults )
+ {
+ return;
+ }
+
+ String[] excl = new String[dirsExcluded.size()];
+ dirsExcluded.copyInto( excl );
+
+ String[] notIncl = new String[dirsNotIncluded.size()];
+ dirsNotIncluded.copyInto( notIncl );
+
+ for ( int i = 0; i < excl.length; i++ )
+ {
+ if ( !couldHoldIncluded( excl[i] ) )
+ {
+ scandir( new File( basedir, excl[i] ),
+ excl[i] + File.separator, false );
+ }
+ }
+
+ for ( int i = 0; i < notIncl.length; i++ )
+ {
+ if ( !couldHoldIncluded( notIncl[i] ) )
+ {
+ scandir( new File( basedir, notIncl[i] ),
+ notIncl[i] + File.separator, false );
+ }
+ }
+
+ haveSlowResults = true;
+ }
+
+ /**
+ * Scans the given directory for files and directories. Found files and
+ * directories are placed in their respective collections, based on the
+ * matching of includes, excludes, and the selectors. When a directory
+ * is found, it is scanned recursively.
+ *
+ * @param dir The directory to scan. Must not be null
.
+ * @param vpath The path relative to the base directory (needed to
+ * prevent problems with an absolute path when using
+ * dir). Must not be null
.
+ * @param fast Whether or not this call is part of a fast scan.
+ * @see #filesIncluded
+ * @see #filesNotIncluded
+ * @see #filesExcluded
+ * @see #dirsIncluded
+ * @see #dirsNotIncluded
+ * @see #dirsExcluded
+ * @see #slowScan
+ */
+ protected void scandir( File dir, String vpath, boolean fast )
+ {
+ String[] newfiles = dir.list();
+
+ if ( newfiles == null )
+ {
+ /*
+ * two reasons are mentioned in the API docs for File.list
+ * (1) dir is not a directory. This is impossible as
+ * we wouldn't get here in this case.
+ * (2) an IO error occurred (why doesn't it throw an exception
+ * then???)
+ */
+ //throw new Exception( "IO error scanning directory " + dir.getAbsolutePath() );
+ }
+
+ if ( !followSymlinks )
+ {
+ Vector noLinks = new Vector();
+ for ( int i = 0; i < newfiles.length; i++ )
+ {
+ try
+ {
+ if ( isSymbolicLink( dir, newfiles[i] ) )
+ {
+ String name = vpath + newfiles[i];
+ File file = new File( dir, newfiles[i] );
+ if ( file.isDirectory() )
+ {
+ dirsExcluded.addElement( name );
+ }
+ else
+ {
+ filesExcluded.addElement( name );
+ }
+ }
+ else
+ {
+ noLinks.addElement( newfiles[i] );
+ }
+ }
+ catch ( IOException ioe )
+ {
+ String msg = "IOException caught while checking "
+ + "for links, couldn't get cannonical path!";
+ // will be caught and redirected to Ant's logging system
+ System.err.println( msg );
+ noLinks.addElement( newfiles[i] );
+ }
+ }
+ newfiles = new String[noLinks.size()];
+ noLinks.copyInto( newfiles );
+ }
+
+ for ( int i = 0; i < newfiles.length; i++ )
+ {
+ String name = vpath + newfiles[i];
+ File file = new File( dir, newfiles[i] );
+ if ( file.isDirectory() )
+ {
+ if ( isIncluded( name ) )
+ {
+ if ( !isExcluded( name ) )
+ {
+ if ( isSelected( name, file ) )
+ {
+ dirsIncluded.addElement( name );
+ if ( fast )
+ {
+ scandir( file, name + File.separator, fast );
+ }
+ }
+ else
+ {
+ everythingIncluded = false;
+ dirsDeselected.addElement( name );
+ if ( fast && couldHoldIncluded( name ) )
+ {
+ scandir( file, name + File.separator, fast );
+ }
+ }
+
+ }
+ else
+ {
+ everythingIncluded = false;
+ dirsExcluded.addElement( name );
+ if ( fast && couldHoldIncluded( name ) )
+ {
+ scandir( file, name + File.separator, fast );
+ }
+ }
+ }
+ else
+ {
+ everythingIncluded = false;
+ dirsNotIncluded.addElement( name );
+ if ( fast && couldHoldIncluded( name ) )
+ {
+ scandir( file, name + File.separator, fast );
+ }
+ }
+ if ( !fast )
+ {
+ scandir( file, name + File.separator, fast );
+ }
+ }
+ else if ( file.isFile() )
+ {
+ if ( isIncluded( name ) )
+ {
+ if ( !isExcluded( name ) )
+ {
+ if ( isSelected( name, file ) )
+ {
+ filesIncluded.addElement( name );
+ }
+ else
+ {
+ everythingIncluded = false;
+ filesDeselected.addElement( name );
+ }
+ }
+ else
+ {
+ everythingIncluded = false;
+ filesExcluded.addElement( name );
+ }
+ }
+ else
+ {
+ everythingIncluded = false;
+ filesNotIncluded.addElement( name );
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests whether or not a name matches against at least one include
+ * pattern.
+ *
+ * @param name The name to match. Must not be null
.
+ * @return true
when the name matches against at least one
+ * include pattern, or false
otherwise.
+ */
+ protected boolean isIncluded( String name )
+ {
+ for ( int i = 0; i < includes.length; i++ )
+ {
+ if ( matchPath( includes[i], name, isCaseSensitive ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether or not a name matches the start of at least one include
+ * pattern.
+ *
+ * @param name The name to match. Must not be null
.
+ * @return true
when the name matches against the start of at
+ * least one include pattern, or false
otherwise.
+ */
+ protected boolean couldHoldIncluded( String name )
+ {
+ for ( int i = 0; i < includes.length; i++ )
+ {
+ if ( matchPatternStart( includes[i], name, isCaseSensitive ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether or not a name matches against at least one exclude
+ * pattern.
+ *
+ * @param name The name to match. Must not be null
.
+ * @return true
when the name matches against at least one
+ * exclude pattern, or false
otherwise.
+ */
+ protected boolean isExcluded( String name )
+ {
+ for ( int i = 0; i < excludes.length; i++ )
+ {
+ if ( matchPath( excludes[i], name, isCaseSensitive ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether a name should be selected.
+ *
+ * @param name the filename to check for selecting
+ * @param file the java.io.File object for this filename
+ * @return false
when the selectors says that the file
+ * should not be selected, true
otherwise.
+ */
+ protected boolean isSelected( String name, File file )
+ {
+ return true;
+ }
+
+ /**
+ * Returns the names of the files which matched at least one of the
+ * include patterns and none of the exclude patterns.
+ * The names are relative to the base directory.
+ *
+ * @return the names of the files which matched at least one of the
+ * include patterns and none of the exclude patterns.
+ */
+ public String[] getIncludedFiles()
+ {
+ String[] files = new String[filesIncluded.size()];
+ filesIncluded.copyInto( files );
+ return files;
+ }
+
+ /**
+ * Returns the names of the files which matched none of the include
+ * patterns. The names are relative to the base directory. This involves
+ * performing a slow scan if one has not already been completed.
+ *
+ * @return the names of the files which matched none of the include
+ * patterns.
+ * @see #slowScan
+ */
+ public String[] getNotIncludedFiles()
+ {
+ slowScan();
+ String[] files = new String[filesNotIncluded.size()];
+ filesNotIncluded.copyInto( files );
+ return files;
+ }
+
+ /**
+ * Returns the names of the files which matched at least one of the
+ * include patterns and at least one of the exclude patterns.
+ * The names are relative to the base directory. This involves
+ * performing a slow scan if one has not already been completed.
+ *
+ * @return the names of the files which matched at least one of the
+ * include patterns and at at least one of the exclude patterns.
+ * @see #slowScan
+ */
+ public String[] getExcludedFiles()
+ {
+ slowScan();
+ String[] files = new String[filesExcluded.size()];
+ filesExcluded.copyInto( files );
+ return files;
+ }
+
+ /**
+ * Path-related methods
+ *
+ * /www/hosted/mysite/index.html
, can be broken into:
+ *
+ *
+ * There are also methods to {@link #catPath concatenate two paths}, {@link #resolveFile resolve a
+ * path relative to a File} and {@link #normalize} a path.
+ * /www/hosted/mysite/
-- retrievable through {@link #getPath}index.html
-- retrievable through {@link #removePath}/www/hosted/mysite/index
-- retrievable through {@link #removeExtension}html
-- retrievable through {@link #getExtension}
File
manager.
+ */
+ public static File getFile( String fileName )
+ {
+ return new File( fileName );
+ }
+
+ /**
+ * Given a directory and an array of extensions return an array of compliant files.
+ *
+ * The given extensions should be like "java" and not like ".java"
+ */
+ public static String[] getFilesFromExtension( String directory, String[] extensions )
+ {
+
+ Vector files = new Vector();
+
+ java.io.File currentDir = new java.io.File( directory );
+
+ String[] unknownFiles = currentDir.list();
+
+ if ( unknownFiles == null )
+ {
+ return new String[0];
+ }
+
+ for ( int i = 0; i < unknownFiles.length; ++i )
+ {
+ String currentFileName = directory + System.getProperty( "file.separator" ) + unknownFiles[i];
+ java.io.File currentFile = new java.io.File( currentFileName );
+
+ if ( currentFile.isDirectory() )
+ {
+
+ //ignore all CVS directories...
+ if ( currentFile.getName().equals( "CVS" ) )
+ {
+ continue;
+ }
+
+ //ok... transverse into this directory and get all the files... then combine
+ //them with the current list.
+
+ String[] fetchFiles = getFilesFromExtension( currentFileName, extensions );
+ files = blendFilesToVector( files, fetchFiles );
+
+ }
+ else
+ {
+ //ok... add the file
+
+ String add = currentFile.getAbsolutePath();
+ if ( isValidFile( add, extensions ) )
+ {
+ files.addElement( add );
+
+ }
+
+ }
+ }
+
+ //ok... move the Vector into the files list...
+
+ String[] foundFiles = new String[files.size()];
+ files.copyInto( foundFiles );
+
+ return foundFiles;
+
+ }
+
+
+ /**
+ * Private hepler method for getFilesFromExtension()
+ */
+ private static Vector blendFilesToVector( Vector v, String[] files )
+ {
+
+ for ( int i = 0; i < files.length; ++i )
+ {
+ v.addElement( files[i] );
+ }
+
+ return v;
+ }
+
+ /**
+ * Checks to see if a file is of a particular type(s).
+ * Note that if the file does not have an extension, an empty string
+ * ("") is matched for.
+ */
+ private static boolean isValidFile( String file, String[] extensions )
+ {
+
+ String extension = extension( file );
+ if ( extension == null )
+ {
+ extension = "";
+ }
+
+ //ok.. now that we have the "extension" go through the current know
+ //excepted extensions and determine if this one is OK.
+
+ for ( int i = 0; i < extensions.length; ++i )
+ {
+ if ( extensions[i].equals( extension ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Simple way to make a directory
+ */
+ public static void mkdir( String dir )
+ {
+ File file = new File( dir );
+ if ( !file.exists() )
+ {
+ file.mkdirs();
+ }
+ }
+
+ /**
+ * Compare the contents of two files to determine if they are equal or not.
+ *
+ * @param file1 the first file
+ * @param file2 the second file
+ * @return true if the content of the files are equal or they both don't exist, false otherwise
+ */
+ public static boolean contentEquals( final File file1, final File file2 )
+ throws IOException
+ {
+ final boolean file1Exists = file1.exists();
+ if ( file1Exists != file2.exists() )
+ {
+ return false;
+ }
+
+ if ( !file1Exists )
+ {
+ // two not existing files are equal
+ return true;
+ }
+
+ if ( file1.isDirectory() || file2.isDirectory() )
+ {
+ // don't want to compare directory contents
+ return false;
+ }
+
+ InputStream input1 = null;
+ InputStream input2 = null;
+ try
+ {
+ input1 = new FileInputStream( file1 );
+ input2 = new FileInputStream( file2 );
+ return IOUtil.contentEquals( input1, input2 );
+
+ }
+ finally
+ {
+ input1.close();
+ input2.close();
+ }
+ }
+
+ /**
+ * Convert from a URL
to a File
.
+ *
+ * @param url File URL.
+ * @return The equivalent File
object, or null
if the URL's protocol
+ * is not file
+ */
+ public static File toFile( final URL url )
+ {
+ if ( url.getProtocol().equals( "file" ) == false )
+ {
+ return null;
+ }
+ else
+ {
+ final String filename = url.getFile().replace( '/', File.separatorChar );
+ return new File( filename );
+ }
+ }
+
+ /**
+ * Convert the array of Files into a list of URLs.
+ *
+ * @param files the array of files
+ * @return the array of URLs
+ * @throws IOException if an error occurs
+ */
+ public static URL[] toURLs( final File[] files )
+ throws IOException
+ {
+ final URL[] urls = new URL[files.length];
+
+ for ( int i = 0; i < urls.length; i++ )
+ {
+ urls[i] = files[i].toURL();
+ }
+
+ return urls;
+ }
+
+ /**
+ * Remove extension from filename.
+ * ie
+ * + * foo.txt --> foo + * a\b\c.jpg --> a\b\c + * a\b\c --> a\b\c + *+ * + * @param filename the filename + * @return the filename minus extension + */ + public static String removeExtension( final String filename ) + { + final int index = filename.lastIndexOf( '.' ); + + if ( -1 == index ) + { + return filename; + } + else + { + return filename.substring( 0, index ); + } + } + + /** + * Get extension from filename. + * ie + *
+ * foo.txt --> "txt" + * a\b\c.jpg --> "jpg" + * a\b\c --> "" + *+ * + * @param filename the filename + * @return the extension of filename or "" if none + */ + public static String getExtension( final String filename ) + { + final int index = filename.lastIndexOf( '.' ); + + if ( -1 == index ) + { + return ""; + } + else + { + return filename.substring( index + 1 ); + } + } + + /** + * Remove path from filename. Equivalent to the unix command
basename
+ * ie.
+ * + * a/b/c.txt --> c.txt + * a.txt --> a.txt + *+ * + * @param filepath the filepath + * @return the filename minus path + */ + public static String removePath( final String filepath ) + { + return removePath( filepath, File.separatorChar ); + } + + /** + * Remove path from filename. + * ie. + *
+ * a/b/c.txt --> c.txt + * a.txt --> a.txt + *+ * + * @param filepath the filepath + * @return the filename minus path + */ + public static String removePath( final String filepath, final char fileSeparatorChar ) + { + final int index = filepath.lastIndexOf( fileSeparatorChar ); + + if ( -1 == index ) + { + return filepath; + } + else + { + return filepath.substring( index + 1 ); + } + } + + /** + * Get path from filename. Roughly equivalent to the unix command
dirname
.
+ * ie.
+ * + * a/b/c.txt --> a/b + * a.txt --> "" + *+ * + * @param filepath the filepath + * @return the filename minus path + */ + public static String getPath( final String filepath ) + { + return getPath( filepath, File.separatorChar ); + } + + /** + * Get path from filename. + * ie. + *
+ * a/b/c.txt --> a/b + * a.txt --> "" + *+ * + * @param filepath the filepath + * @return the filename minus path + */ + public static String getPath( final String filepath, final char fileSeparatorChar ) + { + final int index = filepath.lastIndexOf( fileSeparatorChar ); + if ( -1 == index ) + { + return ""; + } + else + { + return filepath.substring( 0, index ); + } + } + + /** + * Copy file from source to destination. If
destinationDirectory
does not exist, it
+ * (and any parent directories) will be created. If a file source
in
+ * destinationDirectory
exists, it will be overwritten.
+ *
+ * @param source An existing File
to copy.
+ * @param destinationDirectory A directory to copy source
into.
+ * @throws java.io.FileNotFoundException if source
isn't a normal file.
+ * @throws IllegalArgumentException if destinationDirectory
isn't a directory.
+ * @throws IOException if source
does not exist, the file in
+ * destinationDirectory
cannot be written to, or an IO error occurs during copying.
+ */
+ public static void copyFileToDirectory( final String source, final String destinationDirectory )
+ throws IOException
+ {
+ copyFileToDirectory( new File( source ), new File( destinationDirectory ) );
+ }
+
+ /**
+ * Copy file from source to destination. If destinationDirectory
does not exist, it
+ * (and any parent directories) will be created. If a file source
in
+ * destinationDirectory
exists, it will be overwritten.
+ *
+ * @param source An existing File
to copy.
+ * @param destinationDirectory A directory to copy source
into.
+ * @throws java.io.FileNotFoundException if source
isn't a normal file.
+ * @throws IllegalArgumentException if destinationDirectory
isn't a directory.
+ * @throws IOException if source
does not exist, the file in
+ * destinationDirectory
cannot be written to, or an IO error occurs during copying.
+ */
+ public static void copyFileToDirectory( final File source, final File destinationDirectory )
+ throws IOException
+ {
+ if ( destinationDirectory.exists() && !destinationDirectory.isDirectory() )
+ {
+ throw new IllegalArgumentException( "Destination is not a directory" );
+ }
+
+ copyFile( source, new File( destinationDirectory, source.getName() ) );
+ }
+
+ /**
+ * Copy file from source to destination. The directories up to destination
will be
+ * created if they don't already exist. destination
will be overwritten if it
+ * already exists.
+ *
+ * @param source An existing non-directory File
to copy bytes from.
+ * @param destination A non-directory File
to write bytes to (possibly
+ * overwriting).
+ * @throws IOException if source
does not exist, destination
cannot be
+ * written to, or an IO error occurs during copying.
+ * @throws java.io.FileNotFoundException if destination
is a directory
+ * (use {@link #copyFileToDirectory}).
+ */
+ public static void copyFile( final File source, final File destination )
+ throws IOException
+ {
+ //check source exists
+ if ( !source.exists() )
+ {
+ final String message = "File " + source + " does not exist";
+ throw new IOException( message );
+ }
+
+ //does destinations directory exist ?
+ if ( destination.getParentFile() != null && !destination.getParentFile().exists() )
+ {
+ destination.getParentFile().mkdirs();
+ }
+
+ //make sure we can write to destination
+ if ( destination.exists() && !destination.canWrite() )
+ {
+ final String message = "Unable to open file " + destination + " for writing.";
+ throw new IOException( message );
+ }
+
+ final FileInputStream input = new FileInputStream( source );
+ final FileOutputStream output = new FileOutputStream( destination );
+ IOUtil.copy( input, output );
+
+ input.close();
+ output.close();
+
+ if ( source.length() != destination.length() )
+ {
+ final String message = "Failed to copy full contents from " + source + " to " + destination;
+ throw new IOException( message );
+ }
+ }
+
+ /**
+ * Copies bytes from the URL source
to a file destination
.
+ * The directories up to destination
will be created if they don't already exist.
+ * destination
will be overwritten if it already exists.
+ *
+ * @param source A URL
to copy bytes from.
+ * @param destination A non-directory File
to write bytes to (possibly
+ * overwriting).
+ * @throws IOException if
+ * source
URL cannot be openeddestination
cannot be written tonull
if the ..'s went past the
+ * root.
+ * Eg:
+ * + * /foo// --> /foo/ + * /foo/./ --> /foo/ + * /foo/../bar --> /bar + * /foo/../bar/ --> /bar/ + * /foo/../bar/../baz --> /baz + * //foo//./bar --> /foo/bar + * /../ --> null + *+ * + * @param path the path to normalize + * @return the normalized String, or
null
if too many ..'s.
+ */
+ public static String normalize( final String path )
+ {
+ String normalized = path;
+ // Resolve occurrences of "//" in the normalized path
+ while ( true )
+ {
+ int index = normalized.indexOf( "//" );
+ if ( index < 0 )
+ {
+ break;
+ }
+ normalized = normalized.substring( 0, index ) + normalized.substring( index + 1 );
+ }
+
+ // Resolve occurrences of "/./" in the normalized path
+ while ( true )
+ {
+ int index = normalized.indexOf( "/./" );
+ if ( index < 0 )
+ {
+ break;
+ }
+ normalized = normalized.substring( 0, index ) + normalized.substring( index + 2 );
+ }
+
+ // Resolve occurrences of "/../" in the normalized path
+ while ( true )
+ {
+ int index = normalized.indexOf( "/../" );
+ if ( index < 0 )
+ {
+ break;
+ }
+ if ( index == 0 )
+ {
+ return null; // Trying to go outside our context
+ }
+ int index2 = normalized.lastIndexOf( '/', index - 1 );
+ normalized = normalized.substring( 0, index2 ) + normalized.substring( index + 3 );
+ }
+
+ // Return the normalized path that we have completed
+ return normalized;
+ }
+
+ /**
+ * Will concatenate 2 paths. Paths with ..
will be
+ * properly handled.
+ * Eg.,
+ * /a/b/c
+ d
= /a/b/d
+ * /a/b/c
+ ../d
= /a/d
+ *
filename
to it's canonical form. If filename
is
+ * relative (doesn't start with /
), it will be resolved relative to
+ * baseFile
, otherwise it is treated as a normal root-relative path.
+ *
+ * @param baseFile Where to resolve filename
from, if filename
is
+ * relative.
+ * @param filename Absolute or relative file path to resolve.
+ * @return The canonical File
of filename
.
+ */
+ public static File resolveFile( final File baseFile, String filename )
+ {
+ String filenm = filename;
+ if ( '/' != File.separatorChar )
+ {
+ filenm = filename.replace( '/', File.separatorChar );
+ }
+
+ if ( '\\' != File.separatorChar )
+ {
+ filenm = filename.replace( '\\', File.separatorChar );
+ }
+
+ // deal with absolute files
+ if ( filenm.startsWith( File.separator ) )
+ {
+ File file = new File( filenm );
+
+ try
+ {
+ file = file.getCanonicalFile();
+ }
+ catch ( final IOException ioe )
+ {
+ }
+
+ return file;
+ }
+ final char[] chars = filename.toCharArray();
+ final StringBuffer sb = new StringBuffer();
+
+ //remove duplicate file separators in succession - except
+ //on win32 at start of filename as UNC filenames can
+ //be \\AComputer\AShare\myfile.txt
+ int start = 0;
+ if ( '\\' == File.separatorChar )
+ {
+ sb.append( filenm.charAt( 0 ) );
+ start++;
+ }
+
+ for ( int i = start; i < chars.length; i++ )
+ {
+ final boolean doubleSeparator = File.separatorChar == chars[i] && File.separatorChar == chars[i - 1];
+
+ if ( !doubleSeparator )
+ {
+ sb.append( chars[i] );
+ }
+ }
+
+ filenm = sb.toString();
+
+ //must be relative
+ File file = ( new File( baseFile, filenm ) ).getAbsoluteFile();
+
+ try
+ {
+ file = file.getCanonicalFile();
+ }
+ catch ( final IOException ioe )
+ {
+ }
+
+ return file;
+ }
+
+ /**
+ * Delete a file. If file is directory delete it and all sub-directories.
+ */
+ public static void forceDelete( final String file )
+ throws IOException
+ {
+ forceDelete( new File( file ) );
+ }
+
+ /**
+ * Delete a file. If file is directory delete it and all sub-directories.
+ */
+ public static void forceDelete( final File file )
+ throws IOException
+ {
+ if ( !file.exists() )
+ {
+ return;
+ }
+
+ if ( file.isDirectory() )
+ {
+ deleteDirectory( file );
+ }
+ else
+ {
+ if ( !file.delete() )
+ {
+ final String message = "File " + file + " unable to be deleted.";
+ throw new IOException( message );
+ }
+ }
+ }
+
+ /**
+ * Schedule a file to be deleted when JVM exits.
+ * If file is directory delete it and all sub-directories.
+ */
+ public static void forceDeleteOnExit( final File file )
+ throws IOException
+ {
+ if ( !file.exists() )
+ {
+ return;
+ }
+
+ if ( file.isDirectory() )
+ {
+ deleteDirectoryOnExit( file );
+ }
+ else
+ {
+ file.deleteOnExit();
+ }
+ }
+
+ /**
+ * Recursively schedule directory for deletion on JVM exit.
+ */
+ private static void deleteDirectoryOnExit( final File directory )
+ throws IOException
+ {
+ if ( !directory.exists() )
+ {
+ return;
+ }
+
+ cleanDirectoryOnExit( directory );
+ directory.deleteOnExit();
+ }
+
+ /**
+ * Clean a directory without deleting it.
+ */
+ private static void cleanDirectoryOnExit( final File directory )
+ throws IOException
+ {
+ if ( !directory.exists() )
+ {
+ final String message = directory + " does not exist";
+ throw new IllegalArgumentException( message );
+ }
+
+ if ( !directory.isDirectory() )
+ {
+ final String message = directory + " is not a directory";
+ throw new IllegalArgumentException( message );
+ }
+
+ IOException exception = null;
+
+ final File[] files = directory.listFiles();
+ for ( int i = 0; i < files.length; i++ )
+ {
+ final File file = files[i];
+ try
+ {
+ forceDeleteOnExit( file );
+ }
+ catch ( final IOException ioe )
+ {
+ exception = ioe;
+ }
+ }
+
+ if ( null != exception )
+ {
+ throw exception;
+ }
+ }
+
+
+ /**
+ * Make a directory. If there already exists a file with specified name or
+ * the directory is unable to be created then an exception is thrown.
+ */
+ public static void forceMkdir( final File file )
+ throws IOException
+ {
+ if ( file.exists() )
+ {
+ if ( file.isFile() )
+ {
+ final String message = "File " + file + " exists and is " +
+ "not a directory. Unable to create directory.";
+ throw new IOException( message );
+ }
+ }
+ else
+ {
+ if ( false == file.mkdirs() )
+ {
+ final String message = "Unable to create directory " + file;
+ throw new IOException( message );
+ }
+ }
+ }
+
+ /**
+ * Recursively delete a directory.
+ */
+ public static void deleteDirectory( final String directory )
+ throws IOException
+ {
+ deleteDirectory( new File( directory ) );
+ }
+
+ /**
+ * Recursively delete a directory.
+ */
+ public static void deleteDirectory( final File directory )
+ throws IOException
+ {
+ if ( !directory.exists() )
+ {
+ return;
+ }
+
+ cleanDirectory( directory );
+ if ( !directory.delete() )
+ {
+ final String message = "Directory " + directory + " unable to be deleted.";
+ throw new IOException( message );
+ }
+ }
+
+ /**
+ * Clean a directory without deleting it.
+ */
+ public static void cleanDirectory( final String directory )
+ throws IOException
+ {
+ cleanDirectory( new File( directory ) );
+ }
+
+ /**
+ * Clean a directory without deleting it.
+ */
+ public static void cleanDirectory( final File directory )
+ throws IOException
+ {
+ if ( !directory.exists() )
+ {
+ final String message = directory + " does not exist";
+ throw new IllegalArgumentException( message );
+ }
+
+ if ( !directory.isDirectory() )
+ {
+ final String message = directory + " is not a directory";
+ throw new IllegalArgumentException( message );
+ }
+
+ IOException exception = null;
+
+ final File[] files = directory.listFiles();
+ for ( int i = 0; i < files.length; i++ )
+ {
+ final File file = files[i];
+ try
+ {
+ forceDelete( file );
+ }
+ catch ( final IOException ioe )
+ {
+ exception = ioe;
+ }
+ }
+
+ if ( null != exception )
+ {
+ throw exception;
+ }
+ }
+
+ /**
+ * Recursively count size of a directory.
+ *
+ * @return size of directory in bytes.
+ */
+ public static long sizeOfDirectory( final String directory )
+ {
+ return sizeOfDirectory( new File( directory ) );
+ }
+
+ /**
+ * Recursively count size of a directory.
+ *
+ * @return size of directory in bytes.
+ */
+ public static long sizeOfDirectory( final File directory )
+ {
+ if ( !directory.exists() )
+ {
+ final String message = directory + " does not exist";
+ throw new IllegalArgumentException( message );
+ }
+
+ if ( !directory.isDirectory() )
+ {
+ final String message = directory + " is not a directory";
+ throw new IllegalArgumentException( message );
+ }
+
+ long size = 0;
+
+ final File[] files = directory.listFiles();
+ for ( int i = 0; i < files.length; i++ )
+ {
+ final File file = files[i];
+
+ if ( file.isDirectory() )
+ {
+ size += sizeOfDirectory( file );
+ }
+ else
+ {
+ size += file.length();
+ }
+ }
+
+ return size;
+ }
+
+ public static List getFiles( File directory, String includes, String excludes )
+ throws IOException
+ {
+ return getFiles( directory, includes, excludes, true );
+ }
+
+ public static List getFiles( File directory, String includes, String excludes, boolean includeBasedir )
+ throws IOException
+ {
+ List fileNames = getFileNames( directory, includes, excludes, includeBasedir );
+
+ List files = new ArrayList();
+
+ for ( Iterator i = fileNames.iterator(); i.hasNext(); )
+ {
+ files.add( new File( (String) i.next() ) );
+ }
+
+ return files;
+ }
+
+ public static String FS = System.getProperty( "file.separator" );
+
+ public static List getFileNames( File directory, String includes, String excludes, boolean includeBasedir )
+ throws IOException
+ {
+ DirectoryScanner scanner = new DirectoryScanner();
+
+ scanner.setBasedir( directory );
+
+ if ( includes != null )
+ {
+ scanner.setIncludes( StringUtils.split( includes, "," ) );
+ }
+
+ if ( excludes != null )
+ {
+ scanner.setExcludes( StringUtils.split( excludes, "," ) );
+ }
+
+ scanner.scan();
+
+ String[] files = scanner.getIncludedFiles();
+
+ List list = new ArrayList();
+
+ for ( int i = 0; i < files.length; i++ )
+ {
+ if ( includeBasedir )
+ {
+ list.add( directory + FS + files[i] );
+ }
+ else
+ {
+ list.add( files[i] );
+ }
+ }
+
+ return list;
+ }
+
+ public static void copyDirectory( File sourceDirectory, File destinationDirectory )
+ throws IOException
+ {
+ copyDirectory( sourceDirectory, destinationDirectory, "**", null );
+ }
+
+ public static void copyDirectory( File sourceDirectory, File destinationDirectory, String includes,
+ String excludes )
+ throws IOException
+ {
+ if ( !sourceDirectory.exists() )
+ {
+ return;
+ }
+
+ List files = getFiles( sourceDirectory, includes, excludes );
+
+ for ( Iterator i = files.iterator(); i.hasNext(); )
+ {
+ File file = (File) i.next();
+
+ copyFileToDirectory( file, destinationDirectory );
+ }
+ }
+
+ /**
+ * Copies a entire directory structure.
+ *
+ * Note:
+ * sourceDirectory
must exists.
+ * This will remove to
(if it exists), ensure that
+ * to
's parent directory exists and move
+ * from
, which involves deleting from
as
+ * well.
to
may have been deleted
+ * already when this happens.
+ */
+ public static void rename( File from, File to )
+ throws IOException
+ {
+ if ( to.exists() && !to.delete() )
+ {
+ throw new IOException( "Failed to delete " + to + " while trying to rename " + from );
+ }
+
+ File parent = to.getParentFile();
+ if ( parent != null && !parent.exists() && !parent.mkdirs() )
+ {
+ throw new IOException( "Failed to create directory " + parent + " while trying to rename " + from );
+ }
+
+ if ( !from.renameTo( to ) )
+ {
+ copyFile( from, to );
+ if ( !from.delete() )
+ {
+ throw new IOException( "Failed to delete " + from + " while trying to rename it." );
+ }
+ }
+ }
+
+ /**
+ * Create a temporary file in a given directory.
+ *
+ * The file denoted by the returned abstract pathname did not + * exist before this method was invoked, any subsequent invocation + * of this method will yield a different file name.
+ * + * The filename is prefixNNNNNsuffix where NNNN is a random number + * + *This method is different to File.createTempFile of JDK 1.2 + * as it doesn't create the file itself. + * It uses the location pointed to by java.io.tmpdir + * when the parentDir attribute is + * null.
+ * + * @param prefix prefix before the random number + * @param suffix file extension; include the '.' + * @param parentDir Directory to create the temporary file in - + * java.io.tmpdir used if not specificed + * @return a File reference to the new temporary file. + */ + public static File createTempFile( String prefix, String suffix, File parentDir ) + { + + File result = null; + String parent = System.getProperty( "java.io.tmpdir" ); + if ( parentDir != null ) + { + parent = parentDir.getPath(); + } + DecimalFormat fmt = new DecimalFormat( "#####" ); + Random rand = new Random( System.currentTimeMillis() + Runtime.getRuntime().freeMemory() ); + synchronized ( rand ) + { + do + { + result = new File( parent, prefix + fmt.format( Math.abs( rand.nextInt() ) ) + suffix ); + } + while ( result.exists() ); + } + return result; + } +} diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IOUtil.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IOUtil.java new file mode 100644 index 0000000000..15ed8ceeb1 --- /dev/null +++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IOUtil.java @@ -0,0 +1,782 @@ +package org.apache.maven.bootstrap.util; + +/* + * Copyright 2001-2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; + +/** + * General IO Stream manipulation. + * + * This class provides static utility methods for input/output operations, particularly buffered + * copying between sources (InputStream
, Reader
, String
and
+ * byte[]
) and destinations (OutputStream
, Writer
,
+ * String
and byte[]
).
+ *
+ *
+ * Unless otherwise noted, these copy
methods do not flush or close the
+ * streams. Often, doing so would require making non-portable assumptions about the streams' origin
+ * and further use. This means that both streams' close()
methods must be called after
+ * copying. if one omits this step, then the stream resources (sockets, file descriptors) are
+ * released when the associated Stream is garbage-collected. It is not a good idea to rely on this
+ * mechanism. For a good overview of the distinction between "memory management" and "resource
+ * management", see this
+ * UnixReview article
For each copy
method, a variant is provided that allows the caller to specify the
+ * buffer size (the default is 4k). As the buffer size can have a fairly large impact on speed, this
+ * may be worth tweaking. Often "large buffer -> faster" does not hold, even for large data
+ * transfers.
For byte-to-char methods, a copy
variant allows the encoding to be selected
+ * (otherwise the platform default is used).
The copy
methods use an internal buffer when copying. It is therefore advisable
+ * not to deliberately wrap the stream arguments to the copy
methods in
+ * Buffered*
streams. For example, don't do the
+ * following:
copy( new BufferedInputStream( in ), new BufferedOutputStream( out ) );
+ *
+ * The rationale is as follows:
+ * + *Imagine that an InputStream's read() is a very expensive operation, which would usually suggest
+ * wrapping in a BufferedInputStream. The BufferedInputStream works by issuing infrequent
+ * {@link java.io.InputStream#read(byte[] b, int off, int len)} requests on the underlying InputStream, to
+ * fill an internal buffer, from which further read
requests can inexpensively get
+ * their data (until the buffer runs out).
However, the copy
methods do the same thing, keeping an internal buffer,
+ * populated by {@link InputStream#read(byte[] b, int off, int len)} requests. Having two buffers
+ * (or three if the destination stream is also buffered) is pointless, and the unnecessary buffer
+ * management hurts performance slightly (about 3%, according to some simple experiments).
InputStream
to an OutputStream
.
+ */
+ public static void copy( final InputStream input, final OutputStream output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy bytes from an InputStream
to an OutputStream
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final InputStream input, final OutputStream output, final int bufferSize )
+ throws IOException
+ {
+ final byte[] buffer = new byte[bufferSize];
+ int n = 0;
+ while ( -1 != ( n = input.read( buffer ) ) )
+ {
+ output.write( buffer, 0, n );
+ }
+ }
+
+ /**
+ * Copy chars from a Reader
to a Writer
.
+ */
+ public static void copy( final Reader input, final Writer output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy chars from a Reader
to a Writer
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final Reader input, final Writer output, final int bufferSize )
+ throws IOException
+ {
+ final char[] buffer = new char[bufferSize];
+ int n = 0;
+ while ( -1 != ( n = input.read( buffer ) ) )
+ {
+ output.write( buffer, 0, n );
+ }
+ output.flush();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // InputStream -> *
+ ///////////////////////////////////////////////////////////////
+
+
+ ///////////////////////////////////////////////////////////////
+ // InputStream -> Writer
+
+ /**
+ * Copy and convert bytes from an InputStream
to chars on a
+ * Writer
.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ */
+ public static void copy( final InputStream input, final Writer output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy and convert bytes from an InputStream
to chars on a
+ * Writer
.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final InputStream input, final Writer output, final int bufferSize )
+ throws IOException
+ {
+ final InputStreamReader in = new InputStreamReader( input );
+ copy( in, output, bufferSize );
+ }
+
+ /**
+ * Copy and convert bytes from an InputStream
to chars on a
+ * Writer
, using the specified encoding.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ */
+ public static void copy( final InputStream input, final Writer output, final String encoding )
+ throws IOException
+ {
+ final InputStreamReader in = new InputStreamReader( input, encoding );
+ copy( in, output );
+ }
+
+ /**
+ * Copy and convert bytes from an InputStream
to chars on a
+ * Writer
, using the specified encoding.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final InputStream input, final Writer output, final String encoding, final int bufferSize )
+ throws IOException
+ {
+ final InputStreamReader in = new InputStreamReader( input, encoding );
+ copy( in, output, bufferSize );
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // InputStream -> String
+
+ /**
+ * Get the contents of an InputStream
as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ */
+ public static String toString( final InputStream input )
+ throws IOException
+ {
+ return toString( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of an InputStream
as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static String toString( final InputStream input, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, bufferSize );
+ return sw.toString();
+ }
+
+ /**
+ * Get the contents of an InputStream
as a String.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ */
+ public static String toString( final InputStream input, final String encoding )
+ throws IOException
+ {
+ return toString( input, encoding, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of an InputStream
as a String.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static String toString( final InputStream input, final String encoding, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, encoding, bufferSize );
+ return sw.toString();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // InputStream -> byte[]
+
+ /**
+ * Get the contents of an InputStream
as a byte[]
.
+ */
+ public static byte[] toByteArray( final InputStream input )
+ throws IOException
+ {
+ return toByteArray( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of an InputStream
as a byte[]
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static byte[] toByteArray( final InputStream input, final int bufferSize )
+ throws IOException
+ {
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy( input, output, bufferSize );
+ return output.toByteArray();
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // Reader -> *
+ ///////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////
+ // Reader -> OutputStream
+ /**
+ * Serialize chars from a Reader
to bytes on an OutputStream
, and
+ * flush the OutputStream
.
+ */
+ public static void copy( final Reader input, final OutputStream output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Serialize chars from a Reader
to bytes on an OutputStream
, and
+ * flush the OutputStream
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final Reader input, final OutputStream output, final int bufferSize )
+ throws IOException
+ {
+ final OutputStreamWriter out = new OutputStreamWriter( output );
+ copy( input, out, bufferSize );
+ // NOTE: Unless anyone is planning on rewriting OutputStreamWriter, we have to flush
+ // here.
+ out.flush();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Reader -> String
+ /**
+ * Get the contents of a Reader
as a String.
+ */
+ public static String toString( final Reader input )
+ throws IOException
+ {
+ return toString( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a Reader
as a String.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static String toString( final Reader input, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, bufferSize );
+ return sw.toString();
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Reader -> byte[]
+ /**
+ * Get the contents of a Reader
as a byte[]
.
+ */
+ public static byte[] toByteArray( final Reader input )
+ throws IOException
+ {
+ return toByteArray( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a Reader
as a byte[]
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static byte[] toByteArray( final Reader input, final int bufferSize )
+ throws IOException
+ {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy( input, output, bufferSize );
+ return output.toByteArray();
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // String -> *
+ ///////////////////////////////////////////////////////////////
+
+
+ ///////////////////////////////////////////////////////////////
+ // String -> OutputStream
+
+ /**
+ * Serialize chars from a String
to bytes on an OutputStream
, and
+ * flush the OutputStream
.
+ */
+ public static void copy( final String input, final OutputStream output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Serialize chars from a String
to bytes on an OutputStream
, and
+ * flush the OutputStream
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final String input, final OutputStream output, final int bufferSize )
+ throws IOException
+ {
+ final StringReader in = new StringReader( input );
+ final OutputStreamWriter out = new OutputStreamWriter( output );
+ copy( in, out, bufferSize );
+ // NOTE: Unless anyone is planning on rewriting OutputStreamWriter, we have to flush
+ // here.
+ out.flush();
+ }
+
+
+
+ ///////////////////////////////////////////////////////////////
+ // String -> Writer
+
+ /**
+ * Copy chars from a String
to a Writer
.
+ */
+ public static void copy( final String input, final Writer output )
+ throws IOException
+ {
+ output.write( input );
+ }
+
+ /**
+ * Copy bytes from an InputStream
to an
+ * OutputStream
, with buffering.
+ * This is equivalent to passing a
+ * {@link java.io.BufferedInputStream} and
+ * {@link java.io.BufferedOutputStream} to {@link #copy(InputStream, OutputStream)},
+ * and flushing the output stream afterwards. The streams are not closed
+ * after the copy.
+ *
+ * @deprecated Buffering streams is actively harmful! See the class description as to why. Use
+ * {@link #copy(InputStream, OutputStream)} instead.
+ */
+ public static void bufferedCopy( final InputStream input, final OutputStream output )
+ throws IOException
+ {
+ final BufferedInputStream in = new BufferedInputStream( input );
+ final BufferedOutputStream out = new BufferedOutputStream( output );
+ copy( in, out );
+ out.flush();
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // String -> byte[]
+ /**
+ * Get the contents of a String
as a byte[]
.
+ */
+ public static byte[] toByteArray( final String input )
+ throws IOException
+ {
+ return toByteArray( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a String
as a byte[]
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static byte[] toByteArray( final String input, final int bufferSize )
+ throws IOException
+ {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy( input, output, bufferSize );
+ return output.toByteArray();
+ }
+
+
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // byte[] -> *
+ ///////////////////////////////////////////////////////////////
+
+
+ ///////////////////////////////////////////////////////////////
+ // byte[] -> Writer
+
+ /**
+ * Copy and convert bytes from a byte[]
to chars on a
+ * Writer
.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ */
+ public static void copy( final byte[] input, final Writer output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy and convert bytes from a byte[]
to chars on a
+ * Writer
.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final byte[] input, final Writer output, final int bufferSize )
+ throws IOException
+ {
+ final ByteArrayInputStream in = new ByteArrayInputStream( input );
+ copy( in, output, bufferSize );
+ }
+
+ /**
+ * Copy and convert bytes from a byte[]
to chars on a
+ * Writer
, using the specified encoding.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ */
+ public static void copy( final byte[] input, final Writer output, final String encoding )
+ throws IOException
+ {
+ final ByteArrayInputStream in = new ByteArrayInputStream( input );
+ copy( in, output, encoding );
+ }
+
+ /**
+ * Copy and convert bytes from a byte[]
to chars on a
+ * Writer
, using the specified encoding.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final byte[] input, final Writer output, final String encoding, final int bufferSize )
+ throws IOException
+ {
+ final ByteArrayInputStream in = new ByteArrayInputStream( input );
+ copy( in, output, encoding, bufferSize );
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // byte[] -> String
+
+ /**
+ * Get the contents of a byte[]
as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ */
+ public static String toString( final byte[] input )
+ throws IOException
+ {
+ return toString( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a byte[]
as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static String toString( final byte[] input, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, bufferSize );
+ return sw.toString();
+ }
+
+ /**
+ * Get the contents of a byte[]
as a String.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ */
+ public static String toString( final byte[] input, final String encoding )
+ throws IOException
+ {
+ return toString( input, encoding, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a byte[]
as a String.
+ *
+ * @param encoding The name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static String toString( final byte[] input, final String encoding, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, encoding, bufferSize );
+ return sw.toString();
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // byte[] -> OutputStream
+
+ /**
+ * Copy bytes from a byte[]
to an OutputStream
.
+ */
+ public static void copy( final byte[] input, final OutputStream output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy bytes from a byte[]
to an OutputStream
.
+ *
+ * @param bufferSize Size of internal buffer to use.
+ */
+ public static void copy( final byte[] input, final OutputStream output, final int bufferSize )
+ throws IOException
+ {
+ output.write( input );
+ }
+
+ /**
+ * Compare the contents of two Streams to determine if they are equal or not.
+ *
+ * @param input1 the first stream
+ * @param input2 the second stream
+ * @return true if the content of the streams are equal or they both don't exist, false otherwise
+ */
+ public static boolean contentEquals( final InputStream input1, final InputStream input2 )
+ throws IOException
+ {
+ final InputStream bufferedInput1 = new BufferedInputStream( input1 );
+ final InputStream bufferedInput2 = new BufferedInputStream( input2 );
+
+ int ch = bufferedInput1.read();
+ while ( -1 != ch )
+ {
+ final int ch2 = bufferedInput2.read();
+ if ( ch != ch2 )
+ {
+ return false;
+ }
+ ch = bufferedInput1.read();
+ }
+
+ final int ch2 = bufferedInput2.read();
+ if ( -1 != ch2 )
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // closeXXX()
+ // ----------------------------------------------------------------------
+
+ /**
+ * Closes the input stream. The input stream can be null and any IOException's will be swallowed.
+ *
+ * @param inputStream The stream to close.
+ */
+ public static void close( InputStream inputStream )
+ {
+ if ( inputStream == null )
+ {
+ return;
+ }
+
+ try
+ {
+ inputStream.close();
+ }
+ catch ( IOException ex )
+ {
+ // ignore
+ }
+ }
+
+ /**
+ * Closes the output stream. The output stream can be null and any IOException's will be swallowed.
+ *
+ * @param outputStream The stream to close.
+ */
+ public static void close( OutputStream outputStream )
+ {
+ if ( outputStream == null )
+ {
+ return;
+ }
+
+ try
+ {
+ outputStream.close();
+ }
+ catch ( IOException ex )
+ {
+ // ignore
+ }
+ }
+
+ /**
+ * Closes the reader. The reader can be null and any IOException's will be swallowed.
+ *
+ * @param reader The reader to close.
+ */
+ public static void close( Reader reader )
+ {
+ if ( reader == null )
+ {
+ return;
+ }
+
+ try
+ {
+ reader.close();
+ }
+ catch ( IOException ex )
+ {
+ // ignore
+ }
+ }
+
+ /**
+ * Closes the writer. The writer can be null and any IOException's will be swallowed.
+ *
+ * @param wrtier The writer to close.
+ */
+ public static void close( Writer writer )
+ {
+ if ( writer == null )
+ {
+ return;
+ }
+
+ try
+ {
+ writer.close();
+ }
+ catch ( IOException ex )
+ {
+ // ignore
+ }
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IsolatedClassLoader.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IsolatedClassLoader.java
new file mode 100644
index 0000000000..e2a0816793
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/IsolatedClassLoader.java
@@ -0,0 +1,74 @@
+package org.apache.maven.bootstrap.util;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class IsolatedClassLoader
+ extends URLClassLoader
+{
+ private ClassLoader parent;
+
+ public IsolatedClassLoader()
+ {
+ this( ClassLoader.getSystemClassLoader() );
+ }
+
+ public IsolatedClassLoader( ClassLoader parent )
+ {
+ super( new URL[0] );
+ this.parent = parent;
+ }
+
+ public void addURL( URL url )
+ {
+ super.addURL( url );
+ }
+
+ public synchronized Class loadClass( String className )
+ throws ClassNotFoundException
+ {
+ Class c = findLoadedClass( className );
+
+ ClassNotFoundException ex = null;
+
+ if ( c == null )
+ {
+ try
+ {
+ c = findClass( className );
+ }
+ catch ( ClassNotFoundException e )
+ {
+ ex = e;
+
+ if ( parent != null )
+ {
+ c = parent.loadClass( className );
+ }
+ }
+ }
+
+ if ( c == null )
+ {
+ throw ex;
+ }
+
+ return c;
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/JarMojo.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/JarMojo.java
new file mode 100644
index 0000000000..d440550613
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/JarMojo.java
@@ -0,0 +1,221 @@
+package org.apache.maven.bootstrap.util;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+public class JarMojo
+{
+ private byte[] buffer = new byte[4096];
+
+ public void execute( File basedir, File jarFile )
+ throws Exception
+ {
+ Map includes = new LinkedHashMap();
+
+ addDirectory( includes, "**/**", "**/package.html,**/.svn/**", "", basedir );
+
+ createJar( jarFile, includes );
+ }
+
+ /**
+ * Add all files in the specified directory to the archive.
+ *
+ * @param includes a map This is a utility class used by selectors and DirectoryScanner. The + * functionality more properly belongs just to selectors, but unfortunately + * DirectoryScanner exposed these as protected methods. Thus we have to + * support any subclasses of DirectoryScanner that may access these methods. + *
+ *This is a Singleton.
+ * + * @author Arnout J. Kuiper + * ajkuiper@wxs.nl + * @author Magesh Umasankar + * @author Bruce Atherton + * @since 1.5 + */ +public final class SelectorUtils +{ + + private static SelectorUtils instance = new SelectorUtils(); + + /** + * Private Constructor + */ + private SelectorUtils() + { + } + + /** + * Retrieves the manager of the Singleton. + */ + public static SelectorUtils getInstance() + { + return instance; + } + + /** + * Tests whether or not a given path matches the start of a given + * pattern up to the first "**". + * + * This is not a general purpose test and should only be used if you + * can live with false positives. For example,pattern=**\a
+ * and str=b
will yield true
.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ public static boolean matchPatternStart( String pattern, String str )
+ {
+ return matchPatternStart( pattern, str, true );
+ }
+
+ /**
+ * Tests whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ *
+ * This is not a general purpose test and should only be used if you
+ * can live with false positives. For example, pattern=**\a
+ * and str=b
will yield true
.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ public static boolean matchPatternStart( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ // When str starts with a File.separator, pattern has to start with a
+ // File.separator.
+ // When pattern starts with a File.separator, str has to start with a
+ // File.separator.
+ if ( str.startsWith( File.separator ) !=
+ pattern.startsWith( File.separator ) )
+ {
+ return false;
+ }
+
+ Vector patDirs = tokenizePath( pattern );
+ Vector strDirs = tokenizePath( str );
+
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.size() - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strDirs.size() - 1;
+
+ // up to first '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = (String) patDirs.elementAt( patIdxStart );
+ if ( patDir.equals( "**" ) )
+ {
+ break;
+ }
+ if ( !match( patDir, (String) strDirs.elementAt( strIdxStart ),
+ isCaseSensitive ) )
+ {
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+
+ if ( strIdxStart > strIdxEnd )
+ {
+ // String is exhausted
+ return true;
+ }
+ else if ( patIdxStart > patIdxEnd )
+ {
+ // String not exhausted, but pattern is. Failure.
+ return false;
+ }
+ else
+ {
+ // pattern now holds ** while string is not exhausted
+ // this will generate false positives but we can live with that.
+ return true;
+ }
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @return true
if the pattern matches against the string,
+ * or false
otherwise.
+ */
+ public static boolean matchPath( String pattern, String str )
+ {
+ return matchPath( pattern, str, true );
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null
.
+ * @param str The path to match, as a String. Must not be
+ * null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return true
if the pattern matches against the string,
+ * or false
otherwise.
+ */
+ public static boolean matchPath( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ // When str starts with a File.separator, pattern has to start with a
+ // File.separator.
+ // When pattern starts with a File.separator, str has to start with a
+ // File.separator.
+ if ( str.startsWith( File.separator ) !=
+ pattern.startsWith( File.separator ) )
+ {
+ return false;
+ }
+
+ Vector patDirs = tokenizePath( pattern );
+ Vector strDirs = tokenizePath( str );
+
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.size() - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strDirs.size() - 1;
+
+ // up to first '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = (String) patDirs.elementAt( patIdxStart );
+ if ( patDir.equals( "**" ) )
+ {
+ break;
+ }
+ if ( !match( patDir, (String) strDirs.elementAt( strIdxStart ),
+ isCaseSensitive ) )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // String is exhausted
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !patDirs.elementAt( i ).equals( "**" ) )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+ return true;
+ }
+ else
+ {
+ if ( patIdxStart > patIdxEnd )
+ {
+ // String not exhausted, but pattern is. Failure.
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+
+ // up to last '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = (String) patDirs.elementAt( patIdxEnd );
+ if ( patDir.equals( "**" ) )
+ {
+ break;
+ }
+ if ( !match( patDir, (String) strDirs.elementAt( strIdxEnd ),
+ isCaseSensitive ) )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // String is exhausted
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !patDirs.elementAt( i ).equals( "**" ) )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ int patIdxTmp = -1;
+ for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
+ {
+ if ( patDirs.elementAt( i ).equals( "**" ) )
+ {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if ( patIdxTmp == patIdxStart + 1 )
+ {
+ // '**/**' situation, so skip one
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = ( patIdxTmp - patIdxStart - 1 );
+ int strLength = ( strIdxEnd - strIdxStart + 1 );
+ int foundIdx = -1;
+ strLoop:
+ for ( int i = 0; i <= strLength - patLength; i++ )
+ {
+ for ( int j = 0; j < patLength; j++ )
+ {
+ String subPat = (String) patDirs.elementAt( patIdxStart + j + 1 );
+ String subStr = (String) strDirs.elementAt( strIdxStart + i + j );
+ if ( !match( subPat, subStr, isCaseSensitive ) )
+ {
+ continue strLoop;
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if ( foundIdx == -1 )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !patDirs.elementAt( i ).equals( "**" ) )
+ {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:null
.
+ * @param str The string which must be matched against the pattern.
+ * Must not be null
.
+ * @return true
if the string matches against the pattern,
+ * or false
otherwise.
+ */
+ public static boolean match( String pattern, String str )
+ {
+ return match( pattern, str, true );
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:null
.
+ * @param str The string which must be matched against the pattern.
+ * Must not be null
.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return true
if the string matches against the pattern,
+ * or false
otherwise.
+ */
+ public static boolean match( String pattern, String str,
+ boolean isCaseSensitive )
+ {
+ char[] patArr = pattern.toCharArray();
+ char[] strArr = str.toCharArray();
+ int patIdxStart = 0;
+ int patIdxEnd = patArr.length - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strArr.length - 1;
+ char ch;
+
+ boolean containsStar = false;
+ for ( int i = 0; i < patArr.length; i++ )
+ {
+ if ( patArr[i] == '*' )
+ {
+ containsStar = true;
+ break;
+ }
+ }
+
+ if ( !containsStar )
+ {
+ // No '*'s, so we make a shortcut
+ if ( patIdxEnd != strIdxEnd )
+ {
+ return false; // Pattern and string do not have the same size
+ }
+ for ( int i = 0; i <= patIdxEnd; i++ )
+ {
+ ch = patArr[i];
+ if ( ch != '?' )
+ {
+ if ( isCaseSensitive && ch != strArr[i] )
+ {
+ return false;// Character mismatch
+ }
+ if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
+ Character.toUpperCase( strArr[i] ) )
+ {
+ return false; // Character mismatch
+ }
+ }
+ }
+ return true; // String matches against pattern
+ }
+
+ if ( patIdxEnd == 0 )
+ {
+ return true; // Pattern contains only '*', which matches anything
+ }
+
+ // Process characters before first star
+ while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
+ {
+ if ( ch != '?' )
+ {
+ if ( isCaseSensitive && ch != strArr[strIdxStart] )
+ {
+ return false;// Character mismatch
+ }
+ if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
+ Character.toUpperCase( strArr[strIdxStart] ) )
+ {
+ return false;// Character mismatch
+ }
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] != '*' )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Process characters after last star
+ while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
+ {
+ if ( ch != '?' )
+ {
+ if ( isCaseSensitive && ch != strArr[strIdxEnd] )
+ {
+ return false;// Character mismatch
+ }
+ if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
+ Character.toUpperCase( strArr[strIdxEnd] ) )
+ {
+ return false;// Character mismatch
+ }
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] != '*' )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // process pattern between stars. padIdxStart and patIdxEnd point
+ // always to a '*'.
+ while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ int patIdxTmp = -1;
+ for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] == '*' )
+ {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if ( patIdxTmp == patIdxStart + 1 )
+ {
+ // Two stars next to each other, skip the first one.
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = ( patIdxTmp - patIdxStart - 1 );
+ int strLength = ( strIdxEnd - strIdxStart + 1 );
+ int foundIdx = -1;
+ strLoop:
+ for ( int i = 0; i <= strLength - patLength; i++ )
+ {
+ for ( int j = 0; j < patLength; j++ )
+ {
+ ch = patArr[patIdxStart + j + 1];
+ if ( ch != '?' )
+ {
+ if ( isCaseSensitive && ch != strArr[strIdxStart + i + j] )
+ {
+ continue strLoop;
+ }
+ if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
+ Character.toUpperCase( strArr[strIdxStart + i + j] ) )
+ {
+ continue strLoop;
+ }
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if ( foundIdx == -1 )
+ {
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ // All characters in the string are used. Check if only '*'s are left
+ // in the pattern. If so, we succeeded. Otherwise failure.
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] != '*' )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Breaks a path up into a Vector of path elements, tokenizing on
+ * File.separator
.
+ *
+ * @param path Path to tokenize. Must not be null
.
+ * @return a Vector of path elements from the tokenized path
+ */
+ public static Vector tokenizePath( String path )
+ {
+ Vector ret = new Vector();
+ StringTokenizer st = new StringTokenizer( path, File.separator );
+ while ( st.hasMoreTokens() )
+ {
+ ret.addElement( st.nextToken() );
+ }
+ return ret;
+ }
+
+
+ /**
+ * Returns dependency information on these two files. If src has been
+ * modified later than target, it returns true. If target doesn't exist,
+ * it likewise returns true. Otherwise, target is newer than src and
+ * is not out of date, thus the method returns false. It also returns
+ * false if the src file doesn't even exist, since how could the
+ * target then be out of date.
+ *
+ * @param src the original file
+ * @param target the file being compared against
+ * @param granularity the amount in seconds of slack we will give in
+ * determining out of dateness
+ * @return whether the target is out of date
+ */
+ public static boolean isOutOfDate( File src, File target, int granularity )
+ {
+ if ( !src.exists() )
+ {
+ return false;
+ }
+ if ( !target.exists() )
+ {
+ return true;
+ }
+ if ( ( src.lastModified() - granularity ) > target.lastModified() )
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * "Flattens" a string by removing all whitespace (space, tab, linefeed,
+ * carriage return, and formfeed). This uses StringTokenizer and the
+ * default set of tokens as documented in the single arguement constructor.
+ *
+ * @param input a String to remove all whitespace.
+ * @return a String that has had all whitespace removed.
+ */
+ public static String removeWhitespace( String input )
+ {
+ StringBuffer result = new StringBuffer();
+ if ( input != null )
+ {
+ StringTokenizer st = new StringTokenizer( input );
+ while ( st.hasMoreTokens() )
+ {
+ result.append( st.nextToken() );
+ }
+ }
+ return result.toString();
+ }
+}
diff --git a/bootstrap/src/main/java/org/apache/maven/bootstrap/util/StringUtils.java b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/StringUtils.java
new file mode 100644
index 0000000000..d4ca09752d
--- /dev/null
+++ b/bootstrap/src/main/java/org/apache/maven/bootstrap/util/StringUtils.java
@@ -0,0 +1,136 @@
+package org.apache.maven.bootstrap.util;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+public class StringUtils
+{
+ public static String[] split( String str )
+ {
+ return split( str, null, -1 );
+ }
+
+ public static String[] split( String text, String separator )
+ {
+ return split( text, separator, -1 );
+ }
+
+ public static String[] split( String str, String separator, int max )
+ {
+ StringTokenizer tok = null;
+ if ( separator == null )
+ {
+ // Null separator means we're using StringTokenizer's default
+ // delimiter, which comprises all whitespace characters.
+ tok = new StringTokenizer( str );
+ }
+ else
+ {
+ tok = new StringTokenizer( str, separator );
+ }
+
+ int listSize = tok.countTokens();
+ if ( max > 0 && listSize > max )
+ {
+ listSize = max;
+ }
+
+ String[] list = new String[listSize];
+ int i = 0;
+ int lastTokenBegin = 0;
+ int lastTokenEnd = 0;
+ while ( tok.hasMoreTokens() )
+ {
+ if ( max > 0 && i == listSize - 1 )
+ {
+ // In the situation where we hit the max yet have
+ // tokens left over in our input, the last list
+ // element gets all remaining text.
+ String endToken = tok.nextToken();
+ lastTokenBegin = str.indexOf( endToken, lastTokenEnd );
+ list[i] = str.substring( lastTokenBegin );
+ break;
+ }
+ else
+ {
+ list[i] = tok.nextToken();
+ lastTokenBegin = str.indexOf( list[i], lastTokenEnd );
+ lastTokenEnd = lastTokenBegin + list[i].length();
+ }
+ i++;
+ }
+ return list;
+ }
+
+ public static String replaceOnce( String text, String repl, String with )
+ {
+ return replace( text, repl, with, 1 );
+ }
+
+ public static String replace( String text, String repl, String with )
+ {
+ return replace( text, repl, with, -1 );
+ }
+
+ public static String replace( String text, String repl, String with, int max )
+ {
+ if ( text == null || repl == null || with == null || repl.length() == 0 )
+ {
+ return text;
+ }
+
+ StringBuffer buf = new StringBuffer( text.length() );
+ int start = 0, end = 0;
+ while ( ( end = text.indexOf( repl, start ) ) != -1 )
+ {
+ buf.append( text.substring( start, end ) ).append( with );
+ start = end + repl.length();
+
+ if ( --max == 0 )
+ {
+ break;
+ }
+ }
+ buf.append( text.substring( start ) );
+ return buf.toString();
+ }
+
+ public static String interpolate( String text, Map namespace )
+ {
+ Iterator keys = namespace.keySet().iterator();
+
+ while ( keys.hasNext() )
+ {
+ String key = keys.next().toString();
+
+ Object obj = namespace.get( key );
+
+ String value = obj.toString();
+
+ text = StringUtils.replace( text, "${" + key + "}", value );
+
+ if ( key.indexOf( " " ) == -1 )
+ {
+ text = StringUtils.replace( text, "$" + key, value );
+ }
+ }
+ return text;
+ }
+}
diff --git a/bootstrap/src/main/resources/META-INF/MANIFEST.MF b/bootstrap/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..6af167fa0c
--- /dev/null
+++ b/bootstrap/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: org.apache.maven.bootstrap.Bootstrap