1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 package groovy.lang;
36
37 import java.io.BufferedInputStream;
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.lang.reflect.Field;
44 import java.net.MalformedURLException;
45 import java.net.URL;
46 import java.security.AccessController;
47 import java.security.CodeSource;
48 import java.security.PrivilegedAction;
49 import java.security.ProtectionDomain;
50 import java.security.SecureClassLoader;
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.Iterator;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Set;
59 import java.util.jar.Attributes;
60 import java.util.jar.JarEntry;
61 import java.util.jar.JarFile;
62 import java.util.jar.Manifest;
63
64 import org.codehaus.groovy.ast.ClassNode;
65 import org.codehaus.groovy.classgen.Verifier;
66 import org.codehaus.groovy.control.CompilationFailedException;
67 import org.codehaus.groovy.control.CompilationUnit;
68 import org.codehaus.groovy.control.CompilerConfiguration;
69 import org.codehaus.groovy.control.Phases;
70 import org.objectweb.asm.ClassVisitor;
71 import org.objectweb.asm.ClassWriter;
72
73 /***
74 * A ClassLoader which can load Groovy classes
75 *
76 * @author <a href="mailto:james@coredevelopers.net">James Strachan </a>
77 * @author Guillaume Laforge
78 * @author Steve Goetze
79 * @author Bing Ran
80 * @author <a href="mailto:scottstirling@rcn.com">Scott Stirling</a>
81 * @version $Revision: 1.52 $
82 */
83 public class GroovyClassLoader extends SecureClassLoader {
84
85 private Map cache = new HashMap();
86 private Collection loadedClasses = null;
87
88 public void removeFromCache(Class aClass) {
89 cache.remove(aClass);
90 }
91
92 public static class PARSING {
93 }
94
95 private class NOT_RESOLVED {
96 }
97
98 private CompilerConfiguration config;
99
100 private String[] searchPaths;
101
102 private Set additionalPaths = new HashSet();
103
104 public GroovyClassLoader() {
105 this(Thread.currentThread().getContextClassLoader());
106 }
107
108 public GroovyClassLoader(ClassLoader loader) {
109 this(loader, null);
110 }
111
112 public GroovyClassLoader(GroovyClassLoader parent) {
113 this(parent, parent.config);
114 }
115
116 public GroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
117 super(loader);
118 if (config==null) config = CompilerConfiguration.DEFAULT;
119 this.config = config;
120 this.loadedClasses = new ArrayList();
121 }
122
123 /***
124 * Loads the given class node returning the implementation Class
125 *
126 * @param classNode
127 * @return
128 */
129 public Class defineClass(ClassNode classNode, String file) {
130 return defineClass(classNode, file, "/groovy/defineClass");
131 }
132
133 /***
134 * Loads the given class node returning the implementation Class
135 *
136 * @param classNode
137 * @return
138 */
139 public Class defineClass(ClassNode classNode, String file, String newCodeBase) {
140 CodeSource codeSource = null;
141 try {
142 codeSource = new CodeSource(new URL("file", "", newCodeBase), (java.security.cert.Certificate[]) null);
143 } catch (MalformedURLException e) {
144
145 }
146
147
148
149
150 CompilationUnit unit = new CompilationUnit(config, codeSource, getParent());
151 try {
152 ClassCollector collector = createCollector(unit);
153
154 unit.addClassNode(classNode);
155 unit.setClassgenCallback(collector);
156 unit.compile(Phases.CLASS_GENERATION);
157
158 return collector.generatedClass;
159 } catch (CompilationFailedException e) {
160 throw new RuntimeException(e);
161 }
162 }
163
164 /***
165 * Parses the given file into a Java class capable of being run
166 *
167 * @param file the file name to parse
168 * @return the main class defined in the given script
169 */
170 public Class parseClass(File file) throws CompilationFailedException, IOException {
171 return parseClass(new GroovyCodeSource(file));
172 }
173
174 /***
175 * Parses the given text into a Java class capable of being run
176 *
177 * @param text the text of the script/class to parse
178 * @param fileName the file name to use as the name of the class
179 * @return the main class defined in the given script
180 */
181 public Class parseClass(String text, String fileName) throws CompilationFailedException {
182 return parseClass(new ByteArrayInputStream(text.getBytes()), fileName);
183 }
184
185 /***
186 * Parses the given text into a Java class capable of being run
187 *
188 * @param text the text of the script/class to parse
189 * @return the main class defined in the given script
190 */
191 public Class parseClass(String text) throws CompilationFailedException {
192 return parseClass(new ByteArrayInputStream(text.getBytes()), "script" + System.currentTimeMillis() + ".groovy");
193 }
194
195 /***
196 * Parses the given character stream into a Java class capable of being run
197 *
198 * @param in an InputStream
199 * @return the main class defined in the given script
200 */
201 public Class parseClass(InputStream in) throws CompilationFailedException {
202 return parseClass(in, "script" + System.currentTimeMillis() + ".groovy");
203 }
204
205 public Class parseClass(final InputStream in, final String fileName) throws CompilationFailedException {
206
207
208
209
210
211 GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() {
212 public Object run() {
213 return new GroovyCodeSource(in, fileName, "/groovy/script");
214 }
215 });
216 return parseClass(gcs);
217 }
218
219
220 public Class parseClass(GroovyCodeSource codeSource) throws CompilationFailedException {
221 return parseClass(codeSource, true);
222 }
223
224 /***
225 * Parses the given code source into a Java class capable of being run
226 *
227 * @return the main class defined in the given script
228 */
229 public Class parseClass(GroovyCodeSource codeSource, boolean shouldCache) throws CompilationFailedException {
230 String name = codeSource.getName();
231 Class answer = null;
232
233
234
235 synchronized (cache) {
236 answer = (Class) cache.get(name);
237 if (answer != null) {
238 return (answer == PARSING.class ? null : answer);
239 } else {
240 cache.put(name, PARSING.class);
241 }
242 }
243
244
245 try {
246 CompilationUnit unit = new CompilationUnit(config, codeSource.getCodeSource(), this);
247
248 ClassCollector collector = createCollector(unit);
249
250 if (codeSource.getFile()==null) {
251 unit.addSource(name, codeSource.getInputStream());
252 } else {
253 unit.addSource(codeSource.getFile());
254 }
255 unit.setClassgenCallback(collector);
256 int goalPhase = Phases.CLASS_GENERATION;
257 if (config != null && config.getTargetDirectory()!=null) goalPhase = Phases.OUTPUT;
258 unit.compile(goalPhase);
259
260 answer = collector.generatedClass;
261
262
263
264
265 synchronized (this.loadedClasses) {
266 this.loadedClasses.addAll(collector.getLoadedClasses());
267 }
268 } finally {
269 synchronized (cache) {
270 if (answer == null || !shouldCache) {
271 cache.remove(name);
272 } else {
273 cache.put(name, answer);
274 }
275 }
276 try {
277 codeSource.getInputStream().close();
278 } catch (IOException e) {
279 throw new GroovyRuntimeException("unable to close stream",e);
280 }
281 }
282 return answer;
283 }
284
285 /***
286 * Using this classloader you can load groovy classes from the system
287 * classpath as though they were already compiled. Note that .groovy classes
288 * found with this mechanism need to conform to the standard java naming
289 * convention - i.e. the public class inside the file must match the
290 * filename and the file must be located in a directory structure that
291 * matches the package structure.
292 */
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394 private byte[] extractBytes(JarFile jarFile, JarEntry entry) {
395 ByteArrayOutputStream baos = new ByteArrayOutputStream();
396 int b;
397 try {
398 BufferedInputStream bis = new BufferedInputStream(jarFile.getInputStream(entry));
399 while ((b = bis.read()) != -1) {
400 baos.write(b);
401 }
402 } catch (IOException ioe) {
403 throw new GroovyRuntimeException("Could not read the jar bytes for " + entry.getName());
404 }
405 return baos.toByteArray();
406 }
407
408 /***
409 * Workaround for Groovy-835
410 *
411 * @return the classpath as an array of strings, uses the classpath in the CompilerConfiguration object if possible,
412 * otherwise defaults to the value of the <tt>java.class.path</tt> system property
413 */
414 protected String[] getClassPath() {
415 if (null == searchPaths) {
416 String classpath;
417 if(null != config && null != config.getClasspath()) {
418
419
420 StringBuffer sb = new StringBuffer();
421 for(Iterator iter = config.getClasspath().iterator(); iter.hasNext(); ) {
422 sb.append(iter.next().toString());
423 sb.append(File.pathSeparatorChar);
424 }
425
426 sb.deleteCharAt(sb.length()-1);
427 classpath = sb.toString();
428 } else {
429 classpath = System.getProperty("java.class.path", ".");
430 }
431 List pathList = new ArrayList(additionalPaths);
432 expandClassPath(pathList, null, classpath, false);
433 searchPaths = new String[pathList.size()];
434 searchPaths = (String[]) pathList.toArray(searchPaths);
435 }
436 return searchPaths;
437 }
438
439 /***
440 * @param pathList an empty list that will contain the elements of the classpath
441 * @param classpath the classpath specified as a single string
442 */
443 protected void expandClassPath(List pathList, String base, String classpath, boolean isManifestClasspath) {
444
445
446
447
448 if (classpath != null) {
449
450
451
452
453
454 String[] paths;
455 if (isManifestClasspath) {
456 paths = classpath.split("[// ,:;]");
457 } else {
458 paths = classpath.split(File.pathSeparator);
459 }
460
461 for (int i = 0; i < paths.length; i++) {
462 if (paths.length > 0) {
463 File path = null;
464
465 if ("".equals(base)) {
466 path = new File(paths[i]);
467 } else {
468 path = new File(base, paths[i]);
469 }
470
471 if (path.exists()) {
472 if (!path.isDirectory()) {
473 try {
474 JarFile jar = new JarFile(path);
475 pathList.add(paths[i]);
476
477 Manifest manifest = jar.getManifest();
478 if (manifest != null) {
479 Attributes classPathAttributes = manifest.getMainAttributes();
480 String manifestClassPath = classPathAttributes.getValue("Class-Path");
481
482 if (manifestClassPath != null)
483 expandClassPath(pathList, paths[i], manifestClassPath, true);
484 }
485 } catch (IOException e) {
486
487 continue;
488 }
489 } else {
490 pathList.add(paths[i]);
491 }
492 }
493 }
494 }
495 }
496 }
497
498 /***
499 * A helper method to allow bytecode to be loaded. spg changed name to
500 * defineClass to make it more consistent with other ClassLoader methods
501 */
502 protected Class defineClass(String name, byte[] bytecode, ProtectionDomain domain) {
503 return defineClass(name, bytecode, 0, bytecode.length, domain);
504 }
505
506 protected ClassCollector createCollector(CompilationUnit unit) {
507 return new ClassCollector(this, unit);
508 }
509
510 public static class ClassCollector extends CompilationUnit.ClassgenCallback {
511 private Class generatedClass;
512
513 private GroovyClassLoader cl;
514
515 private CompilationUnit unit;
516
517 private Collection loadedClasses = null;
518
519 protected ClassCollector(GroovyClassLoader cl, CompilationUnit unit) {
520 this.cl = cl;
521 this.unit = unit;
522 this.loadedClasses = new ArrayList();
523 }
524
525 protected Class onClassNode(ClassWriter classWriter, ClassNode classNode) {
526 byte[] code = classWriter.toByteArray();
527
528 Class theClass = cl.defineClass(classNode.getName(), code, 0, code.length, unit.getAST().getCodeSource());
529 this.loadedClasses.add(theClass);
530
531 if (generatedClass == null) {
532 generatedClass = theClass;
533 }
534
535 return theClass;
536 }
537
538 public void call(ClassVisitor classWriter, ClassNode classNode) {
539 onClassNode((ClassWriter) classWriter, classNode);
540 }
541
542 public Collection getLoadedClasses() {
543 return this.loadedClasses;
544 }
545 }
546
547 /***
548 * open up the super class define that takes raw bytes
549 *
550 */
551 public Class defineClass(String name, byte[] b) {
552 return super.defineClass(name, b, 0, b.length);
553 }
554
555
556
557
558
559
560
561
562
563 protected synchronized Class loadClass(final String name, boolean resolve) throws ClassNotFoundException {
564 synchronized (cache) {
565 Class cls = (Class) cache.get(name);
566 if (cls == NOT_RESOLVED.class) throw new ClassNotFoundException(name);
567 if (cls!=null) return cls;
568 }
569
570 SecurityManager sm = System.getSecurityManager();
571 if (sm != null) {
572 String className = name.replace('/', '.');
573 int i = className.lastIndexOf('.');
574 if (i != -1) {
575 sm.checkPackageAccess(className.substring(0, i));
576 }
577 }
578
579 Class cls = null;
580 ClassNotFoundException last = null;
581 try {
582 cls = super.loadClass(name, resolve);
583
584 boolean recompile = false;
585 if (getTimeStamp(cls) < Long.MAX_VALUE) {
586 Class[] inters = cls.getInterfaces();
587 for (int i = 0; i < inters.length; i++) {
588 if (inters[i].getName().equals(GroovyObject.class.getName())) {
589 recompile=true;
590 break;
591 }
592 }
593 }
594 if (!recompile) return cls;
595 } catch (ClassNotFoundException cnfe) {
596 last = cnfe;
597 }
598
599
600 try {
601 File source = (File) AccessController.doPrivileged(new PrivilegedAction() {
602 public Object run() {
603 return getSourceFile(name);
604 }
605 });
606 if (source != null) {
607 if ((cls!=null && isSourceNewer(source, cls)) || (cls==null)) {
608 synchronized (cache) {
609 cache.put(name,PARSING.class);
610 }
611 cls = parseClass(source);
612 }
613 }
614 } catch (Exception e) {
615 cls = null;
616 last = new ClassNotFoundException("Failed to parse groovy file: " + name, e);
617 }
618 if (cls==null) {
619 if (last==null) throw new AssertionError(true);
620 synchronized (cache) {
621 cache.put(name, NOT_RESOLVED.class);
622 }
623 throw last;
624 }
625 synchronized (cache) {
626 cache.put(name, cls);
627 }
628 return cls;
629 }
630
631 private long getTimeStamp(Class cls) {
632 Field field;
633 Long o;
634 try {
635 field = cls.getField(Verifier.__TIMESTAMP);
636 o = (Long) field.get(null);
637 } catch (Exception e) {
638
639 return Long.MAX_VALUE;
640 }
641 return o.longValue();
642 }
643
644
645
646
647
648
649
650
651
652
653
654
655
656 private File getSourceFile(String name) {
657 File source = null;
658 String filename = name.replace('.', '/') + ".groovy";
659 String[] paths = getClassPath();
660 for (int i = 0; i < paths.length; i++) {
661 String pathName = paths[i];
662 File path = new File(pathName);
663 if (path.exists()) {
664 if (path.isDirectory()) {
665 File file = new File(path, filename);
666 if (file.exists()) {
667
668
669 boolean fileExists = false;
670 int sepp = filename.lastIndexOf('/');
671 String fn = filename;
672 if (sepp >= 0) {
673 fn = filename.substring(++sepp);
674 }
675 File parent = file.getParentFile();
676 String[] files = parent.list();
677 for (int j = 0; j < files.length; j++) {
678 if (files[j].equals(fn)) {
679 fileExists = true;
680 break;
681 }
682 }
683
684 if (fileExists) {
685 source = file;
686 break;
687 }
688 }
689 }
690 }
691 }
692 return source;
693 }
694
695 private boolean isSourceNewer(File source, Class cls) {
696 return source.lastModified() > getTimeStamp(cls);
697 }
698
699 public void addClasspath(String path) {
700 additionalPaths.add(path);
701 searchPaths = null;
702 }
703
704 /***
705 * <p>Returns all Groovy classes loaded by this class loader.
706 *
707 * @return all classes loaded by this class loader
708 */
709 public Class[] getLoadedClasses() {
710 Class[] loadedClasses = null;
711 synchronized (this.loadedClasses) {
712 loadedClasses = (Class[])this.loadedClasses.toArray(new Class[this.loadedClasses.size()]);
713 }
714 return loadedClasses;
715 }
716 }