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 }