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; } }