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