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