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