View Javadoc

1   package org.apache.maven.shared.runtime;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.net.MalformedURLException;
24  import java.net.URL;
25  import java.net.URLConnection;
26  import java.util.Enumeration;
27  import java.util.HashSet;
28  import java.util.Set;
29  import java.util.jar.JarEntry;
30  import java.util.jar.JarInputStream;
31  
32  import org.codehaus.plexus.util.IOUtil;
33  
34  /**
35   * Provides various methods of applying Maven runtime visitors.
36   * 
37   * @author <a href="mailto:markh@apache.org">Mark Hobson</a>
38   * @version $Id: MavenRuntimeVisitorUtils.java 1341433 2012-05-22 12:13:18Z markh $
39   * @see MavenRuntimeVisitor
40   */
41  final class MavenRuntimeVisitorUtils
42  {
43      // constants --------------------------------------------------------------
44  
45      /**
46       * The path to Maven's metadata directory.
47       */
48      private static final String MAVEN_PATH = "META-INF/maven";
49  
50      /**
51       * The path elements of a Maven project properties file, where {@code null} is a wildcard.
52       */
53      private static final String[] PROPERTIES_PATH_TOKENS =
54          new String[] { "META-INF", "maven", null, null, "pom.properties" };
55  
56      /**
57       * The path elements of a Maven project XML file, where {@code null} is a wildcard.
58       */
59      private static final String[] XML_PATH_TOKENS = new String[] { "META-INF", "maven", null, null, "pom.xml" };
60  
61      /**
62       * The path element index of a Maven project properties/XML file that contains the project group id.
63       */
64      private static final int GROUP_ID_TOKEN_INDEX = 2;
65  
66      /**
67       * The path element index of a Maven project properties/XML file that contains the project artifact id.
68       */
69      private static final int ARTIFACT_ID_TOKEN_INDEX = 3;
70  
71      // constructors -----------------------------------------------------------
72  
73      /**
74       * {@code MavenRuntimeVisitorUtils} is not intended to be instantiated.
75       */
76      private MavenRuntimeVisitorUtils()
77      {
78          throw new AssertionError();
79      }
80  
81      // public methods ---------------------------------------------------------
82  
83      /**
84       * Invokes the specified visitor on all Maven projects found within the specified class loader.
85       * 
86       * @param classLoader
87       *            the class loader to introspect
88       * @param visitor
89       *            the visitor to invoke
90       * @throws MavenRuntimeException
91       *             if an error occurs visiting the projects
92       */
93      public static void accept( ClassLoader classLoader, MavenRuntimeVisitor visitor )
94          throws MavenRuntimeException
95      {
96          Enumeration<URL> urls;
97  
98          try
99          {
100             urls = classLoader.getResources( MAVEN_PATH );
101         }
102         catch ( IOException exception )
103         {
104             throw new MavenRuntimeException( "Cannot obtain Maven metadata from class loader: " + classLoader,
105                                              exception );
106         }
107 
108         Set<String> visitedProjectProperties = new HashSet<String>();
109         Set<String> visitedProjectXML = new HashSet<String>();
110 
111         while ( urls.hasMoreElements() )
112         {
113             URL url = urls.nextElement();
114 
115             acceptURL( url, visitor, visitedProjectProperties, visitedProjectXML );
116         }
117     }
118 
119     /**
120      * Invokes the specified visitor on the specified class's Maven project.
121      * 
122      * @param klass
123      *            the class to introspect
124      * @param visitor
125      *            the visitor to invoke
126      * @throws MavenRuntimeException
127      *             if an error occurs visiting the projects
128      */
129     public static void accept( Class<?> klass, MavenRuntimeVisitor visitor )
130         throws MavenRuntimeException
131     {
132         try
133         {
134             URL baseURL = ClassUtils.getBaseURL( klass );
135             URL url = new URL( baseURL, MAVEN_PATH );
136 
137             acceptURL( url, visitor, new HashSet<String>(), new HashSet<String>() );
138         }
139         catch ( MalformedURLException exception )
140         {
141             throw new MavenRuntimeException( "Cannot obtain URL for class: " + klass.getName(), exception );
142         }
143     }
144 
145     /**
146      * Invokes the specified visitor on the specified URL's Maven project.
147      * 
148      * @param url
149      *            the URL to introspect
150      * @param visitor
151      *            the visitor to invoke
152      * @throws MavenRuntimeException
153      *             if an error occurs visiting the projects
154      */
155     public static void accept( URL url, MavenRuntimeVisitor visitor )
156         throws MavenRuntimeException
157     {
158         try
159         {
160             if ( "jar".equals( url.getProtocol() ) )
161             {
162                 url = getJarFileURL( url );
163             }
164             
165             URL baseURL = getJarEntryURL( url, "" );
166             URL mavenURL = new URL( baseURL, MAVEN_PATH );
167 
168             acceptURL( mavenURL, visitor, new HashSet<String>(), new HashSet<String>() );
169         }
170         catch ( MalformedURLException exception )
171         {
172             throw new MavenRuntimeException( "Cannot obtain URL for Jar: " + url, exception );
173         }
174     }
175 
176     // private methods --------------------------------------------------------
177 
178     /**
179      * Invokes the specified visitor on all Maven projects found within the specified Maven metadata URL.
180      * 
181      * @param url
182      *            the URL of the Maven metadata directory to introspect
183      * @param visitor
184      *            the visitor to invoke
185      * @param visitedProjectProperties
186      *            the ids of projects' properties that have been visited
187      * @param visitedProjectXML
188      *            the ids of projects' XML that have been visited
189      * @throws MavenRuntimeException
190      *             if an error occurs visiting the projects
191      */
192     private static void acceptURL( URL url, MavenRuntimeVisitor visitor, Set<String> visitedProjectProperties,
193                                    Set<String> visitedProjectXML ) throws MavenRuntimeException
194     {
195         if ( "jar".equals( url.getProtocol() ) )
196         {
197             URL jarURL;
198 
199             try
200             {
201                 jarURL = getJarFileURL( url );
202             }
203             catch ( MalformedURLException exception )
204             {
205                 throw new MavenRuntimeException( "Cannot obtain Jar file URL for URL: " + url, exception );
206             }
207 
208             acceptJar( jarURL, visitor, visitedProjectProperties, visitedProjectXML );
209         }
210     }
211 
212     /**
213      * Invokes the specified visitor on all Maven projects found within the specified Jar URL.
214      * 
215      * @param url
216      *            the Jar URL to introspect
217      * @param visitor
218      *            the visitor to invoke
219      * @param visitedProjectProperties
220      *            the ids of projects' properties that have been visited
221      * @param visitedProjectXML
222      *            the ids of projects' XML that have been visited
223      * @throws MavenRuntimeException
224      *             if an error occurs visiting the projects
225      */
226     private static void acceptJar( URL url, MavenRuntimeVisitor visitor, Set<String> visitedProjectProperties,
227                                    Set<String> visitedProjectXML ) throws MavenRuntimeException
228     {
229         JarInputStream in = null;
230 
231         try
232         {
233             URLConnection connection = url.openConnection();
234             connection.setUseCaches( false );
235 
236             in = new JarInputStream( connection.getInputStream() );
237 
238             JarEntry entry;
239 
240             while ( ( entry = in.getNextJarEntry() ) != null )
241             {
242                 acceptJarEntry( url, entry, visitor, visitedProjectProperties, visitedProjectXML );
243             }
244         }
245         catch ( IOException exception )
246         {
247             throw new MavenRuntimeException( "Cannot read jar", exception );
248         }
249         finally
250         {
251             IOUtil.close( in );
252         }
253     }
254 
255     /**
256      * Invokes the specified visitor on the specified Jar entry if it corresponds to a Maven project XML or properties
257      * file.
258      * 
259      * @param jarURL
260      *            a URL to the Jar file for this entry
261      * @param entry
262      *            the Jar entry to introspect
263      * @param visitor
264      *            the visitor to invoke
265      * @param visitedProjectProperties
266      *            the ids of projects' properties that have been visited
267      * @param visitedProjectXML
268      *            the ids of projects' XML that have been visited
269      * @throws MavenRuntimeException
270      *             if an error occurs visiting the projects
271      */
272     private static void acceptJarEntry( URL jarURL, JarEntry entry, MavenRuntimeVisitor visitor,
273                                         Set<String> visitedProjectProperties, Set<String> visitedProjectXML )
274         throws MavenRuntimeException
275     {
276         String name = entry.getName();
277 
278         try
279         {
280             URL url = getJarEntryURL( jarURL, entry.getName() );
281 
282             if ( isProjectPropertiesPath( name ) )
283             {
284                 String projectId = getProjectId( name );
285 
286                 if ( !visitedProjectProperties.contains( projectId ) )
287                 {
288                     visitor.visitProjectProperties( url );
289 
290                     visitedProjectProperties.add( projectId );
291                 }
292             }
293             else if ( isProjectXMLPath( name ) )
294             {
295                 String projectId = getProjectId( name );
296 
297                 if ( !visitedProjectXML.contains( projectId ) )
298                 {
299                     visitor.visitProjectXML( url );
300 
301                     visitedProjectXML.add( projectId );
302                 }
303             }
304         }
305         catch ( MalformedURLException exception )
306         {
307             throw new MavenRuntimeException( "Cannot read jar entry", exception );
308         }
309     }
310 
311     /**
312      * Gets the underlying Jar file URL for the specified Jar entry URL.
313      * 
314      * @param url
315      *            the Jar entry URL
316      * @return the Jar file URL
317      * @throws MalformedURLException
318      *             if an error occurs deriving the Jar file URL
319      */
320     private static URL getJarFileURL( URL url ) throws MalformedURLException
321     {
322         if ( !"jar".equals( url.getProtocol() ) )
323         {
324             return url;
325         }
326 
327         String path = url.getPath();
328 
329         int index = path.indexOf( "!/" );
330 
331         if ( index != -1 )
332         {
333             path = path.substring( 0, index );
334         }
335 
336         return new URL( path );
337     }
338     
339     private static URL getJarEntryURL( URL jarURL, String entryName ) throws MalformedURLException
340     {
341         return new URL( "jar:" + jarURL + "!/" + entryName );
342     }
343 
344     /**
345      * Gets a unique project identifier for the specified Maven project properties/XML file.
346      * 
347      * @param path
348      *            the path to a Maven project properties/XML file
349      * @return the unique project identifier
350      */
351     private static String getProjectId( String path )
352     {
353         String[] tokens = path.split( "/" );
354 
355         String groupId = tokens[GROUP_ID_TOKEN_INDEX];
356         String artifactId = tokens[ARTIFACT_ID_TOKEN_INDEX];
357 
358         return groupId + ":" + artifactId;
359     }
360 
361     /**
362      * Gets whether the specified path represents a Maven project properties file.
363      * 
364      * @param path
365      *            the path to examine
366      * @return {@code true} if the specified path represents a Maven project properties file
367      */
368     private static boolean isProjectPropertiesPath( String path )
369     {
370         return matches( PROPERTIES_PATH_TOKENS, path.split( "/" ) );
371     }
372 
373     /**
374      * Gets whether the specified path represents a Maven project XML file.
375      * 
376      * @param path
377      *            the path to examine
378      * @return {@code true} if the specified path represents a Maven project XML file
379      */
380     private static boolean isProjectXMLPath( String path )
381     {
382         return matches( XML_PATH_TOKENS, path.split( "/" ) );
383     }
384 
385     /**
386      * Gets whether the specified string arrays are equal, with wildcard support.
387      * 
388      * @param matchTokens
389      *            the string tokens to match, where {@code null} represents a wildcard
390      * @param tokens
391      *            the string tokens to test
392      * @return {@code true} if the {@code tokens} array equals the {@code matchTokens}, treating any {@code null}
393      *         {@code matchTokens} values as wildcards
394      */
395     private static boolean matches( String[] matchTokens, String[] tokens )
396     {
397         if ( tokens.length != matchTokens.length )
398         {
399             return false;
400         }
401 
402         for ( int i = 0; i < tokens.length; i++ )
403         {
404             if ( matchTokens[i] != null && !tokens[i].equals( matchTokens[i] ) )
405             {
406                 return false;
407             }
408         }
409 
410         return true;
411     }
412 }