1 package org.codehaus.mojo.exec;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.plugin.logging.Log;
23
24 import java.io.ByteArrayOutputStream;
25 import java.io.File;
26 import java.io.IOException;
27 import java.net.MalformedURLException;
28 import java.net.URL;
29 import java.net.URLClassLoader;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.Enumeration;
35 import java.util.Iterator;
36 import java.util.List;
37
38 import static java.util.Arrays.asList;
39
40
41
42
43
44
45 class URLClassLoaderBuilder
46 {
47 private Log logger;
48 private Collection<Path> paths;
49 private Collection<String> exclusions;
50
51 private URLClassLoaderBuilder()
52 {
53 }
54
55 static URLClassLoaderBuilder builder()
56 {
57 return new URLClassLoaderBuilder();
58 }
59
60 URLClassLoaderBuilder setLogger(Log logger )
61 {
62 this.logger = logger;
63 return this;
64 }
65
66 URLClassLoaderBuilder setExclusions(Collection<String> exclusions )
67 {
68 this.exclusions = exclusions;
69 return this;
70 }
71
72 URLClassLoaderBuilder setPaths(Collection<Path> paths )
73 {
74 this.paths = paths;
75 return this;
76 }
77
78 URLClassLoader build() throws IOException
79 {
80 List<URL> urls = new ArrayList<>( paths.size() );
81
82 for ( Path dependency : paths )
83 {
84 if ( exclusions != null && exclusions.contains( dependency.getFileName().toString() ) )
85 {
86 if ( logger != null )
87 {
88 logger.debug("Excluding as requested '" + dependency + "'" );
89 }
90 continue;
91 }
92 try
93 {
94 urls.add( dependency.toUri().toURL() );
95 }
96 catch ( MalformedURLException e )
97 {
98 throw new IOException( "Error during setting up classpath", e );
99 }
100 }
101
102 return new ExecJavaClassLoader( urls.toArray( new URL[0] ) );
103 }
104
105
106 private static class ExecJavaClassLoader extends URLClassLoader
107 {
108 static
109 {
110 try
111 {
112 registerAsParallelCapable();
113 }
114 catch ( Exception e )
115 {
116
117 }
118 }
119
120 private final String jre;
121
122 public ExecJavaClassLoader(URL[] urls )
123 {
124 super(urls);
125 jre = getJre();
126 }
127
128 @Override
129 public Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
130 if (name == null) {
131 throw new ClassNotFoundException();
132 }
133
134 synchronized ( getClassLoadingLock( name ) )
135 {
136 Class<?> clazz;
137
138
139 if ( isDirectJvmClass( name ) )
140 {
141 try
142 {
143 clazz = getSystemClassLoader().loadClass( name );
144 if ( postLoad( resolve, clazz ) )
145 {
146 return clazz;
147 }
148 }
149 catch ( NoClassDefFoundError | ClassNotFoundException ignored )
150 {
151
152 }
153 }
154
155
156 clazz = findLoadedClass( name );
157 if ( postLoad( resolve, clazz ) )
158 {
159 return clazz;
160 }
161
162
163 try
164 {
165 clazz = super.findClass( name );
166 if (clazz != null)
167 {
168 if ( postLoad( resolve, clazz ) )
169 {
170 return clazz;
171 }
172 return clazz;
173 }
174 }
175 catch ( ClassNotFoundException | NoClassDefFoundError ignored )
176 {
177
178 }
179
180
181 ClassLoader parent = getParent();
182 if (parent == null) {
183 parent = getSystemClassLoader();
184 }
185 try
186 {
187 clazz = Class.forName( name, false, parent );
188 if ( postLoad( resolve, clazz ) )
189 {
190 return clazz;
191 }
192 }
193 catch ( ClassNotFoundException ignored )
194 {
195
196 }
197
198 throw new ClassNotFoundException(name);
199 }
200 }
201
202 @Override
203 public Enumeration<URL> getResources( String name ) throws IOException
204 {
205 final Enumeration<URL> selfResources = findResources(name);
206 final Enumeration<URL> parentResources = getParent().getResources(name);
207 if (!parentResources.hasMoreElements())
208 {
209 return selfResources;
210 }
211 if (!selfResources.hasMoreElements())
212 {
213 return new FilteringUrlEnum(parentResources);
214 }
215 return new ChainedEnumerations(
216 asList( selfResources, new FilteringUrlEnum(parentResources) ).iterator());
217 }
218
219 private boolean isInJvm( URL resource )
220 {
221 final Path path = toPath(resource);
222 if (path == null)
223 {
224 return false;
225 }
226 return path.normalize().toAbsolutePath().toString().startsWith(jre);
227 }
228
229 private String getJre()
230 {
231 final Path home = new File( System.getProperty("java.home", "") ).toPath();
232 if ( "jre".equals( home.getFileName().toString() ) && home.getParent() != null &&
233 Files.exists(home.getParent().resolve( "lib/tools.jar" ) ) )
234 {
235 return home.getParent().toAbsolutePath().toString();
236 }
237 return home.toAbsolutePath().toString();
238 }
239
240 private Path toPath(final URL url)
241 {
242 if ( "jar".equals( url.getProtocol()) )
243 {
244 try
245 {
246 String spec = url.getFile();
247 int separator = spec.indexOf( '!' );
248 if ( separator == -1 ) {
249 return null;
250 }
251 return toPath( new URL( spec.substring( 0, separator + 1 ) ) ) ;
252 }
253 catch ( MalformedURLException e )
254 {
255
256 }
257 }
258 else if ( "file".equals( url.getProtocol() ) )
259 {
260 String path = decode( url.getFile() );
261 if (path.endsWith( "!" ))
262 {
263 path = path.substring( 0, path.length() - 1 );
264 }
265 return new File(path).getAbsoluteFile().toPath();
266 }
267 return null;
268 }
269
270 private String decode( String fileName )
271 {
272 if ( fileName.indexOf( '%' ) == -1 )
273 {
274 return fileName;
275 }
276
277 final StringBuilder result = new StringBuilder( fileName.length() );
278 final ByteArrayOutputStream out = new ByteArrayOutputStream();
279
280 for ( int i = 0; i < fileName.length(); )
281 {
282 final char c = fileName.charAt( i );
283
284 if ( c == '%' )
285 {
286 out.reset();
287 do
288 {
289 if ( i + 2 >= fileName.length() )
290 {
291 throw new IllegalArgumentException( "Incomplete % sequence at: " + i );
292 }
293
294 final int d1 = Character.digit( fileName.charAt( i + 1 ), 16 );
295 final int d2 = Character.digit (fileName.charAt( i + 2 ), 16 );
296
297 if ( d1 == -1 || d2 == -1 )
298 {
299 throw new IllegalArgumentException(
300 "Invalid % sequence (" + fileName.substring(i, i + 3) + ") at: " + i );
301 }
302
303 out.write( (byte) ( ( d1 << 4 ) + d2 ) );
304
305 i += 3;
306
307 }
308 while ( i < fileName.length() && fileName.charAt(i) == '%' );
309
310 result.append( out.toString() );
311
312 continue;
313 }
314 else
315 {
316 result.append( c );
317 }
318
319 i++;
320 }
321 return result.toString();
322 }
323
324 private boolean postLoad( boolean resolve, Class<?> clazz )
325 {
326 if ( clazz != null )
327 {
328 if (resolve)
329 {
330 resolveClass( clazz );
331 }
332 return true;
333 }
334 return false;
335 }
336
337
338 private boolean isDirectJvmClass(final String name) {
339 if (name.startsWith( "java." ) )
340 {
341 return true;
342 }
343 if ( name.startsWith( "sun." ) )
344 {
345 return true;
346 }
347 if ( name.startsWith( "jdk." ) )
348 {
349 return true;
350 }
351 if ( name.startsWith( "oracle." ) )
352 {
353 return true;
354 }
355 if ( name.startsWith( "javafx." ) )
356 {
357 return true;
358 }
359 if ( name.startsWith( "netscape." ) )
360 {
361 return true;
362 }
363 if ( name.startsWith( "org." ) )
364 {
365 final String sub = name.substring( "org.".length() );
366 if ( sub.startsWith( "w3c.dom." ) )
367 {
368 return true;
369 }
370 if ( sub.startsWith( "omg." ) )
371 {
372 return true;
373 }
374 if ( sub.startsWith( "xml.sax." ) )
375 {
376 return true;
377 }
378 if ( sub.startsWith( "ietf.jgss." ) )
379 {
380 return true;
381 }
382 if ( sub.startsWith( "jcp.xml.dsig.internal." ) )
383 {
384 return true;
385 }
386 }
387 if ( name.startsWith( "com." ) )
388 {
389 final String sub = name.substring( "com.".length() );
390 if ( sub.startsWith( "oracle." ) )
391 {
392 return true;
393 }
394 if ( sub.startsWith( "sun." ) )
395 {
396 final String subSun = sub.substring( "sun.".length() );
397 if ( subSun.startsWith( "accessibility." ) )
398 {
399 return true;
400 }
401 if ( subSun.startsWith( "activation." ) )
402 {
403 return true;
404 }
405 if ( subSun.startsWith( "awt." ) )
406 {
407 return true;
408 }
409 if ( subSun.startsWith( "beans." ) )
410 {
411 return true;
412 }
413 if ( subSun.startsWith( "corba.se." ) )
414 {
415 return true;
416 }
417 if ( subSun.startsWith( "demo.jvmti." ) )
418 {
419 return true;
420 }
421 if ( subSun.startsWith( "image.codec.jpeg." ) )
422 {
423 return true;
424 }
425 if ( subSun.startsWith( "imageio." ) )
426 {
427 return true;
428 }
429 if ( subSun.startsWith( "istack.internal." ) )
430 {
431 return true;
432 }
433 if ( subSun.startsWith( "java." ) )
434 {
435 return true;
436 }
437 if ( subSun.startsWith( "java_cup." ) )
438 {
439 return true;
440 }
441 if ( subSun.startsWith( "jmx." ) )
442 {
443 return true;
444 }
445 if ( subSun.startsWith( "jndi." ) )
446 {
447 return true;
448 }
449 if ( subSun.startsWith( "management." ) )
450 {
451 return true;
452 }
453 if ( subSun.startsWith( "media.sound." ) )
454 {
455 return true;
456 }
457 if ( subSun.startsWith( "naming.internal." ) )
458 {
459 return true;
460 }
461 if ( subSun.startsWith( "net." ) )
462 {
463 return true;
464 }
465 if ( subSun.startsWith( "nio." ) )
466 {
467 return true;
468 }
469 if ( subSun.startsWith( "org." ) )
470 {
471 return true;
472 }
473 if ( subSun.startsWith( "rmi.rmid." ) )
474 {
475 return true;
476 }
477 if ( subSun.startsWith( "rowset." ) )
478 {
479 return true;
480 }
481 if ( subSun.startsWith( "security." ) )
482 {
483 return true;
484 }
485 if ( subSun.startsWith( "swing." ) )
486 {
487 return true;
488 }
489 if ( subSun.startsWith( "tracing." ) )
490 {
491 return true;
492 }
493 if ( subSun.startsWith( "xml.internal." ) )
494 {
495 return true;
496 }
497 return false;
498 }
499 }
500 return false;
501 }
502
503 private class FilteringUrlEnum implements Enumeration<URL>
504 {
505 private final Enumeration<URL> delegate;
506 private URL next;
507
508 private FilteringUrlEnum( Enumeration<URL> delegate )
509 {
510 this.delegate = delegate;
511 }
512
513 @Override
514 public boolean hasMoreElements()
515 {
516 while (delegate.hasMoreElements())
517 {
518 next = delegate.nextElement();
519 if (isInJvm(next))
520 {
521 return true;
522 }
523 }
524 return false;
525 }
526
527 @Override
528 public URL nextElement()
529 {
530 return next;
531 }
532 }
533
534 private static class ChainedEnumerations implements Enumeration<URL>
535 {
536 private final Iterator<Enumeration<URL>> enumerations;
537 private Enumeration<URL> current;
538
539 private ChainedEnumerations( Iterator<Enumeration<URL>> enumerations )
540 {
541 this.enumerations = enumerations;
542 }
543
544 @Override
545 public boolean hasMoreElements()
546 {
547 if (current == null || !current.hasMoreElements())
548 {
549 if (!enumerations.hasNext())
550 {
551 return false;
552 }
553 current = enumerations.next();
554 }
555 return current.hasMoreElements();
556 }
557
558 @Override
559 public URL nextElement()
560 {
561 return current.nextElement();
562 }
563 }
564 }
565 }