blob: 1aca96ec73581694f895a35670b9b1740fbdaf04 [file] [log] [blame]
package org.apache.maven.shared.runtime;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import org.apache.maven.shared.utils.io.IOUtil;
/**
* Provides various methods of applying Maven runtime visitors.
*
* @author <a href="mailto:markh@apache.org">Mark Hobson</a>
* @version $Id$
* @see MavenRuntimeVisitor
*/
final class MavenRuntimeVisitorUtils
{
// constants --------------------------------------------------------------
/**
* The path to Maven's metadata directory.
*/
private static final String MAVEN_PATH = "META-INF/maven";
/**
* The path elements of a Maven project properties file, where {@code null} is a wildcard.
*/
private static final String[] PROPERTIES_PATH_TOKENS =
new String[] { "META-INF", "maven", null, null, "pom.properties" };
/**
* The path elements of a Maven project XML file, where {@code null} is a wildcard.
*/
private static final String[] XML_PATH_TOKENS = new String[] { "META-INF", "maven", null, null, "pom.xml" };
/**
* The path element index of a Maven project properties/XML file that contains the project group id.
*/
private static final int GROUP_ID_TOKEN_INDEX = 2;
/**
* The path element index of a Maven project properties/XML file that contains the project artifact id.
*/
private static final int ARTIFACT_ID_TOKEN_INDEX = 3;
// constructors -----------------------------------------------------------
/**
* {@code MavenRuntimeVisitorUtils} is not intended to be instantiated.
*/
private MavenRuntimeVisitorUtils()
{
throw new AssertionError();
}
// public methods ---------------------------------------------------------
/**
* Invokes the specified visitor on all Maven projects found within the specified class loader.
*
* @param classLoader
* the class loader to introspect
* @param visitor
* the visitor to invoke
* @throws MavenRuntimeException
* if an error occurs visiting the projects
*/
public static void accept( ClassLoader classLoader, MavenRuntimeVisitor visitor )
throws MavenRuntimeException
{
Enumeration<URL> urls;
try
{
urls = classLoader.getResources( MAVEN_PATH );
}
catch ( IOException exception )
{
throw new MavenRuntimeException( "Cannot obtain Maven metadata from class loader: " + classLoader,
exception );
}
Set<String> visitedProjectProperties = new HashSet<String>();
Set<String> visitedProjectXML = new HashSet<String>();
while ( urls.hasMoreElements() )
{
URL url = urls.nextElement();
acceptURL( url, visitor, visitedProjectProperties, visitedProjectXML );
}
}
/**
* Invokes the specified visitor on the specified class's Maven project.
*
* @param klass
* the class to introspect
* @param visitor
* the visitor to invoke
* @throws MavenRuntimeException
* if an error occurs visiting the projects
*/
public static void accept( Class<?> klass, MavenRuntimeVisitor visitor )
throws MavenRuntimeException
{
try
{
URL baseURL = ClassUtils.getBaseURL( klass );
URL url = new URL( baseURL, MAVEN_PATH );
acceptURL( url, visitor, new HashSet<String>(), new HashSet<String>() );
}
catch ( MalformedURLException exception )
{
throw new MavenRuntimeException( "Cannot obtain URL for class: " + klass.getName(), exception );
}
}
/**
* Invokes the specified visitor on the specified URL's Maven project.
*
* @param url
* the URL to introspect
* @param visitor
* the visitor to invoke
* @throws MavenRuntimeException
* if an error occurs visiting the projects
*/
public static void accept( URL url, MavenRuntimeVisitor visitor )
throws MavenRuntimeException
{
try
{
if ( "jar".equals( url.getProtocol() ) )
{
url = getJarFileURL( url );
}
URL baseURL = getJarEntryURL( url, "" );
URL mavenURL = new URL( baseURL, MAVEN_PATH );
acceptURL( mavenURL, visitor, new HashSet<String>(), new HashSet<String>() );
}
catch ( MalformedURLException exception )
{
throw new MavenRuntimeException( "Cannot obtain URL for Jar: " + url, exception );
}
}
// private methods --------------------------------------------------------
/**
* Invokes the specified visitor on all Maven projects found within the specified Maven metadata URL.
*
* @param url
* the URL of the Maven metadata directory to introspect
* @param visitor
* the visitor to invoke
* @param visitedProjectProperties
* the ids of projects' properties that have been visited
* @param visitedProjectXML
* the ids of projects' XML that have been visited
* @throws MavenRuntimeException
* if an error occurs visiting the projects
*/
private static void acceptURL( URL url, MavenRuntimeVisitor visitor, Set<String> visitedProjectProperties,
Set<String> visitedProjectXML ) throws MavenRuntimeException
{
if ( "jar".equals( url.getProtocol() ) )
{
URL jarURL;
try
{
jarURL = getJarFileURL( url );
}
catch ( MalformedURLException exception )
{
throw new MavenRuntimeException( "Cannot obtain Jar file URL for URL: " + url, exception );
}
acceptJar( jarURL, visitor, visitedProjectProperties, visitedProjectXML );
}
}
/**
* Invokes the specified visitor on all Maven projects found within the specified Jar URL.
*
* @param url
* the Jar URL to introspect
* @param visitor
* the visitor to invoke
* @param visitedProjectProperties
* the ids of projects' properties that have been visited
* @param visitedProjectXML
* the ids of projects' XML that have been visited
* @throws MavenRuntimeException
* if an error occurs visiting the projects
*/
private static void acceptJar( URL url, MavenRuntimeVisitor visitor, Set<String> visitedProjectProperties,
Set<String> visitedProjectXML ) throws MavenRuntimeException
{
JarInputStream in = null;
try
{
URLConnection connection = url.openConnection();
connection.setUseCaches( false );
in = new JarInputStream( connection.getInputStream() );
JarEntry entry;
while ( ( entry = in.getNextJarEntry() ) != null )
{
acceptJarEntry( url, entry, visitor, visitedProjectProperties, visitedProjectXML );
}
}
catch ( IOException exception )
{
throw new MavenRuntimeException( "Cannot read jar", exception );
}
finally
{
IOUtil.close( in );
}
}
/**
* Invokes the specified visitor on the specified Jar entry if it corresponds to a Maven project XML or properties
* file.
*
* @param jarURL
* a URL to the Jar file for this entry
* @param entry
* the Jar entry to introspect
* @param visitor
* the visitor to invoke
* @param visitedProjectProperties
* the ids of projects' properties that have been visited
* @param visitedProjectXML
* the ids of projects' XML that have been visited
* @throws MavenRuntimeException
* if an error occurs visiting the projects
*/
private static void acceptJarEntry( URL jarURL, JarEntry entry, MavenRuntimeVisitor visitor,
Set<String> visitedProjectProperties, Set<String> visitedProjectXML )
throws MavenRuntimeException
{
String name = entry.getName();
try
{
URL url = getJarEntryURL( jarURL, entry.getName() );
if ( isProjectPropertiesPath( name ) )
{
String projectId = getProjectId( name );
if ( !visitedProjectProperties.contains( projectId ) )
{
visitor.visitProjectProperties( url );
visitedProjectProperties.add( projectId );
}
}
else if ( isProjectXMLPath( name ) )
{
String projectId = getProjectId( name );
if ( !visitedProjectXML.contains( projectId ) )
{
visitor.visitProjectXML( url );
visitedProjectXML.add( projectId );
}
}
}
catch ( MalformedURLException exception )
{
throw new MavenRuntimeException( "Cannot read jar entry", exception );
}
}
/**
* Gets the underlying Jar file URL for the specified Jar entry URL.
*
* @param url
* the Jar entry URL
* @return the Jar file URL
* @throws MalformedURLException
* if an error occurs deriving the Jar file URL
*/
private static URL getJarFileURL( URL url ) throws MalformedURLException
{
if ( !"jar".equals( url.getProtocol() ) )
{
return url;
}
String path = url.getPath();
int index = path.indexOf( "!/" );
if ( index != -1 )
{
path = path.substring( 0, index );
}
return new URL( path );
}
private static URL getJarEntryURL( URL jarURL, String entryName ) throws MalformedURLException
{
return new URL( "jar:" + jarURL + "!/" + entryName );
}
/**
* Gets a unique project identifier for the specified Maven project properties/XML file.
*
* @param path
* the path to a Maven project properties/XML file
* @return the unique project identifier
*/
private static String getProjectId( String path )
{
String[] tokens = path.split( "/" );
String groupId = tokens[GROUP_ID_TOKEN_INDEX];
String artifactId = tokens[ARTIFACT_ID_TOKEN_INDEX];
return groupId + ":" + artifactId;
}
/**
* Gets whether the specified path represents a Maven project properties file.
*
* @param path
* the path to examine
* @return {@code true} if the specified path represents a Maven project properties file
*/
private static boolean isProjectPropertiesPath( String path )
{
return matches( PROPERTIES_PATH_TOKENS, path.split( "/" ) );
}
/**
* Gets whether the specified path represents a Maven project XML file.
*
* @param path
* the path to examine
* @return {@code true} if the specified path represents a Maven project XML file
*/
private static boolean isProjectXMLPath( String path )
{
return matches( XML_PATH_TOKENS, path.split( "/" ) );
}
/**
* Gets whether the specified string arrays are equal, with wildcard support.
*
* @param matchTokens
* the string tokens to match, where {@code null} represents a wildcard
* @param tokens
* the string tokens to test
* @return {@code true} if the {@code tokens} array equals the {@code matchTokens}, treating any {@code null}
* {@code matchTokens} values as wildcards
*/
private static boolean matches( String[] matchTokens, String[] tokens )
{
if ( tokens.length != matchTokens.length )
{
return false;
}
for ( int i = 0; i < tokens.length; i++ )
{
if ( matchTokens[i] != null && !tokens[i].equals( matchTokens[i] ) )
{
return false;
}
}
return true;
}
}