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