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.ast.ModuleNode;
66 import org.codehaus.groovy.classgen.Verifier;
67 import org.codehaus.groovy.control.CompilationFailedException;
68 import org.codehaus.groovy.control.CompilationUnit;
69 import org.codehaus.groovy.control.CompilerConfiguration;
70 import org.codehaus.groovy.control.Phases;
71 import org.codehaus.groovy.control.SourceUnit;
72 import org.objectweb.asm.ClassVisitor;
73 import org.objectweb.asm.ClassWriter;
74
75 /***
76 * A ClassLoader which can load Groovy classes
77 *
78 * @author <a href="mailto:james@coredevelopers.net">James Strachan </a>
79 * @author Guillaume Laforge
80 * @author Steve Goetze
81 * @author Bing Ran
82 * @author <a href="mailto:scottstirling@rcn.com">Scott Stirling</a>
83 * @version $Revision: 1.60 $
84 */
85 public class GroovyClassLoader extends SecureClassLoader {
86
87 private Map cache = new HashMap();
88
89 private GroovyResourceLoader resourceLoader = new GroovyResourceLoader() {
90 public URL loadGroovySource(String filename) throws MalformedURLException {
91 File file = getSourceFile(filename);
92 return file == null ? null : file.toURL();
93 }
94 };
95
96 public void removeFromCache(Class aClass) {
97 cache.remove(aClass);
98 }
99
100 public static class PARSING {
101 }
102
103 private class NOT_RESOLVED {
104 }
105
106 private CompilerConfiguration config;
107 private String[] searchPaths;
108 private Set additionalPaths = new HashSet();
109
110 /***
111 * creates a GroovyClassLoader using the current Thread's context
112 * Class loader as parent.
113 */
114 public GroovyClassLoader() {
115 this(Thread.currentThread().getContextClassLoader());
116 }
117
118 /***
119 * creates a GroovyClassLoader using the given ClassLoader as parent
120 */
121 public GroovyClassLoader(ClassLoader loader) {
122 this(loader, null);
123 }
124
125 /***
126 * creates a GroovyClassLoader using the given GroovyClassLoader as parent.
127 * This loader will get the parent's CompilerConfiguration
128 */
129 public GroovyClassLoader(GroovyClassLoader parent) {
130 this(parent, parent.config);
131 }
132
133 /***
134 * creates a GroovyClassLoader using the given ClassLoader as parent.
135 */
136 public GroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
137 super(loader);
138 if (config==null) config = CompilerConfiguration.DEFAULT;
139 this.config = config;
140 }
141
142 public void setResourceLoader(GroovyResourceLoader resourceLoader) {
143 if (resourceLoader == null) {
144 throw new IllegalArgumentException("Resource loader must not be null!");
145 }
146 this.resourceLoader = resourceLoader;
147 }
148
149 public GroovyResourceLoader getResourceLoader() {
150 return resourceLoader;
151 }
152
153 /***
154 * Loads the given class node returning the implementation Class
155 *
156 * @param classNode
157 * @return a class
158 */
159 public Class defineClass(ClassNode classNode, String file) {
160 return defineClass(classNode, file, "/groovy/defineClass");
161 }
162
163 /***
164 * Loads the given class node returning the implementation Class
165 *
166 * @param classNode
167 * @return a class
168 */
169 public Class defineClass(ClassNode classNode, String file, String newCodeBase) {
170 CodeSource codeSource = null;
171 try {
172 codeSource = new CodeSource(new URL("file", "", newCodeBase), (java.security.cert.Certificate[]) null);
173 } catch (MalformedURLException e) {
174
175 }
176
177 CompilationUnit unit = new CompilationUnit(config, codeSource, this);
178 try {
179 ClassCollector collector = createCollector(unit,classNode.getModule().getContext());
180
181 unit.addClassNode(classNode);
182 unit.setClassgenCallback(collector);
183 unit.compile(Phases.CLASS_GENERATION);
184
185 return collector.generatedClass;
186 } catch (CompilationFailedException e) {
187 throw new RuntimeException(e);
188 }
189 }
190
191 /***
192 * Parses the given file into a Java class capable of being run
193 *
194 * @param file the file name to parse
195 * @return the main class defined in the given script
196 */
197 public Class parseClass(File file) throws CompilationFailedException, IOException {
198 return parseClass(new GroovyCodeSource(file));
199 }
200
201 /***
202 * Parses the given text into a Java class capable of being run
203 *
204 * @param text the text of the script/class to parse
205 * @param fileName the file name to use as the name of the class
206 * @return the main class defined in the given script
207 */
208 public Class parseClass(String text, String fileName) throws CompilationFailedException {
209 return parseClass(new ByteArrayInputStream(text.getBytes()), fileName);
210 }
211
212 /***
213 * Parses the given text into a Java class capable of being run
214 *
215 * @param text the text of the script/class to parse
216 * @return the main class defined in the given script
217 */
218 public Class parseClass(String text) throws CompilationFailedException {
219 return parseClass(new ByteArrayInputStream(text.getBytes()), "script" + System.currentTimeMillis() + ".groovy");
220 }
221
222 /***
223 * Parses the given character stream into a Java class capable of being run
224 *
225 * @param in an InputStream
226 * @return the main class defined in the given script
227 */
228 public Class parseClass(InputStream in) throws CompilationFailedException {
229 return parseClass(in, "script" + System.currentTimeMillis() + ".groovy");
230 }
231
232 public Class parseClass(final InputStream in, final String fileName) throws CompilationFailedException {
233
234
235
236
237
238 GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() {
239 public Object run() {
240 return new GroovyCodeSource(in, fileName, "/groovy/script");
241 }
242 });
243 return parseClass(gcs);
244 }
245
246
247 public Class parseClass(GroovyCodeSource codeSource) throws CompilationFailedException {
248 return parseClass(codeSource, true);
249 }
250
251 /***
252 * Parses the given code source into a Java class capable of being run
253 *
254 * @return the main class defined in the given script
255 */
256 public Class parseClass(GroovyCodeSource codeSource, boolean shouldCache) throws CompilationFailedException {
257 String name = codeSource.getName();
258 Class answer = null;
259
260
261
262 synchronized (cache) {
263 answer = (Class) cache.get(name);
264 if (answer != null) {
265 return (answer == PARSING.class ? null : answer);
266 } else {
267 cache.put(name, PARSING.class);
268 }
269 }
270
271
272 try {
273 CompilationUnit unit = new CompilationUnit(config, codeSource.getCodeSource(), this);
274
275 SourceUnit su = null;
276 if (codeSource.getFile()==null) {
277 su = unit.addSource(name, codeSource.getInputStream());
278 } else {
279 su = unit.addSource(codeSource.getFile());
280 }
281
282 ClassCollector collector = createCollector(unit,su);
283 unit.setClassgenCallback(collector);
284 int goalPhase = Phases.CLASS_GENERATION;
285 if (config != null && config.getTargetDirectory()!=null) goalPhase = Phases.OUTPUT;
286 unit.compile(goalPhase);
287
288 answer = collector.generatedClass;
289 if (shouldCache) {
290 for (Iterator iter = collector.getLoadedClasses().iterator(); iter.hasNext();) {
291 Class clazz = (Class) iter.next();
292 cache.put(clazz.getName(),clazz);
293 }
294 }
295 } finally {
296 synchronized (cache) {
297 cache.remove(name);
298 if (shouldCache) {
299 cache.put(name, answer);
300 }
301 }
302 try {
303 codeSource.getInputStream().close();
304 } catch (IOException e) {
305 throw new GroovyRuntimeException("unable to close stream",e);
306 }
307 }
308 return answer;
309 }
310
311 /***
312 * Using this classloader you can load groovy classes from the system
313 * classpath as though they were already compiled. Note that .groovy classes
314 * found with this mechanism need to conform to the standard java naming
315 * convention - i.e. the public class inside the file must match the
316 * filename and the file must be located in a directory structure that
317 * matches the package structure.
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
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420 private byte[] extractBytes(JarFile jarFile, JarEntry entry) {
421 ByteArrayOutputStream baos = new ByteArrayOutputStream();
422 int b;
423 try {
424 BufferedInputStream bis = new BufferedInputStream(jarFile.getInputStream(entry));
425 while ((b = bis.read()) != -1) {
426 baos.write(b);
427 }
428 } catch (IOException ioe) {
429 throw new GroovyRuntimeException("Could not read the jar bytes for " + entry.getName());
430 }
431 return baos.toByteArray();
432 }
433
434 /***
435 * Workaround for Groovy-835
436 *
437 * @return the classpath as an array of strings, uses the classpath in the CompilerConfiguration object if possible,
438 * otherwise defaults to the value of the <tt>java.class.path</tt> system property
439 */
440 protected String[] getClassPath() {
441 if (null == searchPaths) {
442 String classpath;
443 if(null != config && null != config.getClasspath()) {
444
445
446 StringBuffer sb = new StringBuffer();
447 for(Iterator iter = config.getClasspath().iterator(); iter.hasNext(); ) {
448 sb.append(iter.next().toString());
449 sb.append(File.pathSeparatorChar);
450 }
451
452 sb.deleteCharAt(sb.length()-1);
453 classpath = sb.toString();
454 } else {
455 classpath = System.getProperty("java.class.path", ".");
456 }
457 List pathList = new ArrayList(additionalPaths);
458 expandClassPath(pathList, null, classpath, false);
459 searchPaths = new String[pathList.size()];
460 searchPaths = (String[]) pathList.toArray(searchPaths);
461 }
462 return searchPaths;
463 }
464
465 /***
466 * @param pathList an empty list that will contain the elements of the classpath
467 * @param classpath the classpath specified as a single string
468 */
469 protected void expandClassPath(List pathList, String base, String classpath, boolean isManifestClasspath) {
470
471
472
473
474 if (classpath != null) {
475
476
477
478
479
480 String[] paths;
481 if (isManifestClasspath) {
482 paths = classpath.split("[// ,:;]");
483 } else {
484 paths = classpath.split(File.pathSeparator);
485 }
486
487 for (int i = 0; i < paths.length; i++) {
488 if (paths.length > 0) {
489 File path = null;
490
491 if ("".equals(base)) {
492 path = new File(paths[i]);
493 } else {
494 path = new File(base, paths[i]);
495 }
496
497 if (path.exists()) {
498 if (!path.isDirectory()) {
499 try {
500 JarFile jar = new JarFile(path);
501 pathList.add(paths[i]);
502
503 Manifest manifest = jar.getManifest();
504 if (manifest != null) {
505 Attributes classPathAttributes = manifest.getMainAttributes();
506 String manifestClassPath = classPathAttributes.getValue("Class-Path");
507
508 if (manifestClassPath != null)
509 expandClassPath(pathList, paths[i], manifestClassPath, true);
510 }
511 } catch (IOException e) {
512
513 continue;
514 }
515 } else {
516 pathList.add(paths[i]);
517 }
518 }
519 }
520 }
521 }
522 }
523
524 /***
525 * A helper method to allow bytecode to be loaded. spg changed name to
526 * defineClass to make it more consistent with other ClassLoader methods
527 */
528 protected Class defineClass(String name, byte[] bytecode, ProtectionDomain domain) {
529 return defineClass(name, bytecode, 0, bytecode.length, domain);
530 }
531
532 protected ClassCollector createCollector(CompilationUnit unit,SourceUnit su) {
533 return new ClassCollector(this, unit, su);
534 }
535
536 public static class ClassCollector extends CompilationUnit.ClassgenCallback {
537 private Class generatedClass;
538 private GroovyClassLoader cl;
539 private SourceUnit su;
540 private CompilationUnit unit;
541 private Collection loadedClasses = null;
542
543 protected ClassCollector(GroovyClassLoader cl, CompilationUnit unit, SourceUnit su) {
544 this.cl = cl;
545 this.unit = unit;
546 this.loadedClasses = new ArrayList();
547 this.su = su;
548 }
549
550 protected Class onClassNode(ClassWriter classWriter, ClassNode classNode) {
551 byte[] code = classWriter.toByteArray();
552
553 Class theClass = cl.defineClass(classNode.getName(), code, 0, code.length, unit.getAST().getCodeSource());
554 this.loadedClasses.add(theClass);
555
556 if (generatedClass == null) {
557 ModuleNode mn = classNode.getModule();
558 SourceUnit msu = null;
559 if (mn!=null) msu = mn.getContext();
560 ClassNode main = null;
561 if (mn!=null) main = (ClassNode) mn.getClasses().get(0);
562 if (msu==su && main==classNode) generatedClass = theClass;
563 }
564
565 return theClass;
566 }
567
568 public void call(ClassVisitor classWriter, ClassNode classNode) {
569 onClassNode((ClassWriter) classWriter, classNode);
570 }
571
572 public Collection getLoadedClasses() {
573 return this.loadedClasses;
574 }
575 }
576
577 /***
578 * open up the super class define that takes raw bytes
579 *
580 */
581 public Class defineClass(String name, byte[] b) {
582 Class c = super.defineClass(name, b, 0, b.length);
583 synchronized (cache) {
584 cache.put(name, c);
585 }
586 return c;
587 }
588
589 /***
590 * loads a class from a file or a parent classloader.
591 * This method does call @see #loadClass(String, boolean, boolean, boolean)
592 * with the last parameter set to false.
593 */
594 public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript)
595 throws ClassNotFoundException
596 {
597 return loadClass(name,lookupScriptFiles,preferClassOverScript,false);
598 }
599
600 /***
601 * loads a class from a file or a parent classloader.
602 *
603 * @param name of the class to be loaded
604 * @param lookupScriptFiles if false no lookup at files is done at all
605 * @param preferClassOverScript if true the file lookup is only done if there is no class
606 * @param resolve @see ClassLoader#loadClass(java.lang.String, boolean)
607 * @return the class found or the class created from a file lookup
608 * @throws ClassNotFoundException
609 */
610 public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve)
611 throws ClassNotFoundException
612 {
613
614 synchronized (cache) {
615 Class cls = (Class) cache.get(name);
616 if (cls == NOT_RESOLVED.class) throw new ClassNotFoundException(name);
617 if (cls!=null) return cls;
618 }
619
620
621 SecurityManager sm = System.getSecurityManager();
622 if (sm != null) {
623 String className = name.replace('/', '.');
624 int i = className.lastIndexOf('.');
625 if (i != -1) {
626 sm.checkPackageAccess(className.substring(0, i));
627 }
628 }
629
630
631 Class cls = null;
632 ClassNotFoundException last = null;
633 try {
634 cls = super.loadClass(name, resolve);
635 } catch (ClassNotFoundException cnfe) {
636 last = cnfe;
637 }
638
639 if (cls!=null) {
640 boolean recompile = false;
641 if (getTimeStamp(cls) < Long.MAX_VALUE) {
642 Class[] inters = cls.getInterfaces();
643 for (int i = 0; i < inters.length; i++) {
644 if (inters[i].getName().equals(GroovyObject.class.getName())) {
645 recompile=true;
646 break;
647 }
648 }
649 }
650
651 preferClassOverScript |= cls.getClassLoader()==this;
652 preferClassOverScript |= !recompile;
653 if(preferClassOverScript) return cls;
654 }
655
656 if (lookupScriptFiles) {
657
658 try {
659 URL source = (URL) AccessController.doPrivileged(new PrivilegedAction() {
660 public Object run() {
661 try {
662 return resourceLoader.loadGroovySource(name);
663 } catch (MalformedURLException e) {
664 return null;
665 }
666 }
667 });
668 if (source != null) {
669
670 if ((cls!=null && isSourceNewer(source, cls)) || (cls==null)) {
671 synchronized (cache) {
672 cache.put(name,PARSING.class);
673 }
674 cls = parseClass(source.openStream());
675 }
676 }
677 } catch (Exception e) {
678 cls = null;
679 last = new ClassNotFoundException("Failed to parse groovy file: " + name, e);
680 }
681 }
682
683 if (cls==null) {
684
685 if (last==null) throw new AssertionError(true);
686 synchronized (cache) {
687 cache.put(name, NOT_RESOLVED.class);
688 }
689 throw last;
690 }
691
692
693 synchronized (cache) {
694 cache.put(name, cls);
695 }
696 return cls;
697 }
698
699 /***
700 * Implemented here to check package access prior to returning an
701 * already loaded class.
702 * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
703 */
704 protected synchronized Class loadClass(final String name, boolean resolve) throws ClassNotFoundException {
705 return loadClass(name,true,false,resolve);
706 }
707
708 private long getTimeStamp(Class cls) {
709 Field field;
710 Long o;
711 try {
712 field = cls.getField(Verifier.__TIMESTAMP);
713 o = (Long) field.get(null);
714 } catch (Exception e) {
715 return Long.MAX_VALUE;
716 }
717 return o.longValue();
718 }
719
720 private File getSourceFile(String name) {
721 File source = null;
722 String filename = name.replace('.', '/') + ".groovy";
723 String[] paths = getClassPath();
724 for (int i = 0; i < paths.length; i++) {
725 String pathName = paths[i];
726 File path = new File(pathName);
727 if (path.exists()) {
728 if (path.isDirectory()) {
729 File file = new File(path, filename);
730 if (file.exists()) {
731
732
733 boolean fileExists = false;
734 int sepp = filename.lastIndexOf('/');
735 String fn = filename;
736 if (sepp >= 0) {
737 fn = filename.substring(++sepp);
738 }
739 File parent = file.getParentFile();
740 String[] files = parent.list();
741 for (int j = 0; j < files.length; j++) {
742 if (files[j].equals(fn)) {
743 fileExists = true;
744 break;
745 }
746 }
747
748 if (fileExists) {
749 source = file;
750 break;
751 }
752 }
753 }
754 }
755 }
756 return source;
757 }
758
759 private boolean isSourceNewer(URL source, Class cls) throws IOException {
760 long lastMod;
761
762
763
764 if (source.getProtocol().equals("file")) {
765
766 String path = source.getPath().replace('/', File.separatorChar).replace('|', ':');
767 File file = new File(path);
768 lastMod = file.lastModified();
769 }
770 else {
771 lastMod = source.openConnection().getLastModified();
772 }
773 return lastMod > getTimeStamp(cls);
774 }
775
776 public void addClasspath(String path) {
777 additionalPaths.add(path);
778 searchPaths = null;
779 }
780
781 /***
782 * <p>Returns all Groovy classes loaded by this class loader.
783 *
784 * @return all classes loaded by this class loader
785 */
786 public Class[] getLoadedClasses() {
787 Class[] loadedClasses = null;
788 HashSet set = new HashSet(cache.size());
789 synchronized (cache) {
790 for (Iterator iter = cache.values().iterator(); iter.hasNext();) {
791 Class element = (Class) iter.next();
792 if (element==NOT_RESOLVED.class) continue;
793 set.add(element);
794 }
795 loadedClasses = (Class[])set.toArray(new Class[0]);
796 }
797 return loadedClasses;
798 }
799 }