Fixing the ClassLoader.defineClass() method issue that could not find the necessary...
[jpf-core.git] / src / main / gov / nasa / jpf / vm / ClassLoaderInfo.java
1 /*
2  * Copyright (C) 2014, United States Government, as represented by the
3  * Administrator of the National Aeronautics and Space Administration.
4  * All rights reserved.
5  *
6  * The Java Pathfinder core (jpf-core) platform is licensed under the
7  * Apache License, Version 2.0 (the "License"); you may not use this file except
8  * in compliance with the License. You may obtain a copy of the License at
9  * 
10  *        http://www.apache.org/licenses/LICENSE-2.0. 
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and 
16  * limitations under the License.
17  */
18 package gov.nasa.jpf.vm;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.net.MalformedURLException;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.Map;
27 import java.util.jar.JarEntry;
28 import java.util.jar.JarFile;
29
30 import gov.nasa.jpf.Config;
31 import gov.nasa.jpf.JPF;
32 import gov.nasa.jpf.JPFException;
33 import gov.nasa.jpf.SystemAttribute;
34 import gov.nasa.jpf.util.JPFLogger;
35 import gov.nasa.jpf.util.SparseIntVector;
36 import gov.nasa.jpf.util.StringSetMatcher;
37
38 /**
39  * @author Nastaran Shafiei <nastaran.shafiei@gmail.com>
40  *  
41  * Represents the classloader construct in VM which is responsible for loading
42  * classes.
43  */
44 public class ClassLoaderInfo 
45      implements Iterable<ClassInfo>, Comparable<ClassLoaderInfo>, Cloneable, Restorable<ClassLoaderInfo> {
46
47   static JPFLogger log = JPF.getLogger("class");
48
49   
50   // the model class field name where we store our id 
51   protected static final String ID_FIELD = "nativeId";
52
53   protected static Config config;
54
55   // this is where we keep the global list of classloader ids
56   protected static SparseIntVector globalCLids;
57   
58   /**
59    * Map from class file URLs to first ClassInfo that was read from it. This search
60    * global map is used to make sure we only read class files once
61    */
62   protected static Map<String,ClassInfo> loadedClasses;
63   
64   /**
65    * map from annotation class file URLs to AnnotationInfos, which have a separate JPF internal
66    * representation. Again, using a global map ensures we only read the related class files once
67    */
68   protected static Map<String,AnnotationInfo> loadedAnnotations;
69   
70   // Map that keeps the classes defined (directly loaded) by this loader and the
71   // ones that are resolved from these defined classes
72   protected Map<String,ClassInfo> resolvedClasses;
73
74   // annotations directly loaded by this classloader
75   protected Map<String,AnnotationInfo> resolvedAnnotations;
76   
77   // Represents the locations where this classloader can load classes form - has to be populated subclasses 
78   protected ClassPath cp;
79
80   // The type of the corresponding class loader object
81   protected ClassInfo classInfo;
82
83   // The area containing static fields and  classes
84   protected Statics statics;
85
86   protected boolean roundTripRequired = false;
87
88   // Search global id, which is the basis for canonical order of classloaders
89   protected int id;
90
91   // The java.lang.ClassLoader object reference
92   protected int objRef;
93
94   protected ClassLoaderInfo parent;
95
96   
97   static class ClMemento implements Memento<ClassLoaderInfo> {
98     // note that we don't have to store the invariants (gid, parent, isSystemClassLoader)
99     ClassLoaderInfo cl;
100     Memento<Statics> staticsMemento;
101     Memento<ClassPath> cpMemento;
102     Map<String, Boolean> classAssertionStatus;
103     Map<String, Boolean> packageAssertionStatus;
104     boolean defaultAssertionStatus;
105     boolean isDefaultSet;
106
107     ClMemento (ClassLoaderInfo cl){
108       this.cl = cl;
109       staticsMemento = cl.statics.getMemento();
110       cpMemento = cl.cp.getMemento();
111       classAssertionStatus = new HashMap<String, Boolean>(cl.classAssertionStatus);
112       packageAssertionStatus = new HashMap<String, Boolean>(cl.packageAssertionStatus);
113       defaultAssertionStatus = cl.defaultAssertionStatus;
114       isDefaultSet = cl.isDefaultSet;
115     }
116
117     @Override
118         public ClassLoaderInfo restore(ClassLoaderInfo ignored) {
119       staticsMemento.restore(cl.statics);
120       cpMemento.restore(null);
121       cl.classAssertionStatus = this.classAssertionStatus;
122       cl.packageAssertionStatus = this.packageAssertionStatus;
123       cl.defaultAssertionStatus = this.defaultAssertionStatus;
124       cl.isDefaultSet = this.isDefaultSet;
125       return cl;
126     }
127   }
128
129   /**
130    * This is invoked by VM.initSubsystems()
131    */
132   static void init (Config config) {
133     ClassLoaderInfo.config = config;
134
135     globalCLids = new SparseIntVector();
136     loadedClasses = new HashMap<String,ClassInfo>(); // not sure we actually want this for multiple runs (unless we check file stamps)
137     loadedAnnotations = new HashMap<String,AnnotationInfo>();
138     
139     enabledAssertionPatterns = StringSetMatcher.getNonEmpty(config.getStringArray("vm.enable_assertions"));
140     disabledAssertionPatterns = StringSetMatcher.getNonEmpty(config.getStringArray("vm.disable_assertions"));
141   }
142     
143   public static int getNumberOfLoadedClasses (){
144     return loadedClasses.size();
145   }
146   
147   public static ClassInfo getCurrentResolvedClassInfo (String clsName){
148     ClassLoaderInfo cl = getCurrentClassLoader();
149     return cl.getResolvedClassInfo(clsName);
150   }
151
152   public static ClassInfo getSystemResolvedClassInfo (String clsName){
153     ClassLoaderInfo cl = getCurrentSystemClassLoader();
154     return cl.getResolvedClassInfo(clsName);
155   }
156    
157   /**
158    * for use from SystemClassLoaderInfo ctor, which doesn't have a ClassLoader object
159    * yet and has to set cp and id itself
160    */
161   protected ClassLoaderInfo (VM vm){
162     resolvedClasses = new HashMap<String,ClassInfo>();
163     resolvedAnnotations = new HashMap<String,AnnotationInfo>();
164     
165     this.statics = createStatics(vm);
166
167     cp = new ClassPath();
168     
169     // registration has to happen from SystemClassLoaderInfo ctor since we are
170     // only partially initialized at this point
171   }
172   
173   /**
174    * for all other classloaders, which require an already instantiated ClassLoader object 
175    */
176   protected ClassLoaderInfo (VM vm, int objRef, ClassPath cp, ClassLoaderInfo parent) {
177     resolvedClasses = new HashMap<String,ClassInfo>();
178     resolvedAnnotations = new HashMap<String,AnnotationInfo>();
179
180     this.parent = parent;
181     this.objRef = objRef;
182     this.cp = cp;
183     this.statics = createStatics(vm);
184
185     this.id = computeId(objRef);
186     ElementInfo ei = vm.getModifiableElementInfo(objRef);
187
188     ei.setIntField(ID_FIELD, id);
189     if (parent != null) {
190       ei.setReferenceField("parent", parent.objRef);
191     }
192     classInfo = ei.getClassInfo();
193     roundTripRequired = isRoundTripRequired();
194
195     vm.registerClassLoader(this);
196   }
197   
198   @Override
199   public Memento<ClassLoaderInfo> getMemento (MementoFactory factory) {
200     return factory.getMemento(this);
201   }
202
203   public Memento<ClassLoaderInfo> getMemento(){
204     return new ClMemento(this);
205   }
206
207   protected Statics createStatics (VM vm){
208     Class<?>[] argTypes = { Config.class, KernelState.class };
209     Object[] args = { config, vm.getKernelState() };
210     
211     return config.getEssentialInstance("vm.statics.class", Statics.class, argTypes, args);
212   }
213   
214   /**
215    * this is our internal, search global id that is used for the
216    * canonical root set
217    */
218   public int getId() {
219     return id;
220   }
221
222   /**
223    * Returns the type of the corresponding class loader object
224    */
225   public ClassInfo getClassInfo () {
226     return classInfo;
227   }
228
229   /**
230    * Returns the object reference.
231    */
232   public int getClassLoaderObjectRef () {
233     return objRef;
234   }
235
236   protected int computeId (int objRef) {
237     int id = globalCLids.get(objRef);
238     if (id == 0) {
239       id = globalCLids.size() + 1; // the first systemClassLoader is not in globalCLids and always has id '0'
240       globalCLids.set(objRef, id);
241     }
242     return id;
243   }
244
245   /**
246    * For optimizing the class loading mechanism, if the class loader class and the 
247    * classes of the whole parents hierarchy are descendant of URLClassLoader and 
248    * do not override the ClassLoader.loadClass() & URLClassLoader.findClass, resolving 
249    * the class is done natively within JPF
250    */
251   protected boolean isRoundTripRequired() {
252     return (parent!=null? parent.roundTripRequired: true)  || !hasOriginalLoadingImp();
253   }
254
255   private boolean hasOriginalLoadingImp() {
256     String signature = "(Ljava/lang/String;)Ljava/lang/Class;";
257     MethodInfo loadClass = classInfo.getMethod("loadClass" + signature, true);
258     MethodInfo findClass = classInfo.getMethod("findClass" + signature, true);
259   
260     return (loadClass.getClassName().equals("java.lang.ClassLoader") &&
261         findClass.getClassName().equals("java.net.URLClassLoader"));
262   }
263
264   public boolean isSystemClassLoader() {
265     return false;
266   }
267   
268   public static ClassLoaderInfo getCurrentClassLoader() {
269     return getCurrentClassLoader( ThreadInfo.getCurrentThread());
270   }
271
272   public static ClassLoaderInfo getCurrentClassLoader (ThreadInfo ti) {
273     for (StackFrame frame = ti.getTopFrame(); frame != null; frame = frame.getPrevious()){
274       MethodInfo miFrame = frame.getMethodInfo();
275       ClassInfo ciFrame =  miFrame.getClassInfo();
276       if (ciFrame != null){
277         return ciFrame.getClassLoaderInfo();
278       }
279     }
280
281     return ti.getSystemClassLoaderInfo();
282   }
283   
284   public static SystemClassLoaderInfo getCurrentSystemClassLoader() {
285     ThreadInfo ti = ThreadInfo.getCurrentThread();
286     if (ti != null){
287       return ti.getSystemClassLoaderInfo();
288     } else {
289       // this is kind of a hack - we just use the latest SystemClassLoaderInfo instance
290       // this might happen if the SystemClassLoader preloads classes before we have a main thread
291       return SystemClassLoaderInfo.lastInstance;
292     }
293   }
294
295   public SystemClassLoaderInfo getSystemClassLoader() {
296     return getCurrentSystemClassLoader();
297   }
298   
299   protected ClassInfo loadSystemClass (String clsName){
300     return getCurrentSystemClassLoader().loadSystemClass(clsName);
301   }
302
303   protected ClassInfo createClassInfo (String typeName, ClassFileMatch match, ClassLoaderInfo definingLoader) throws ClassParseException {
304     return getCurrentSystemClassLoader().createClassInfo( typeName, match, definingLoader);
305   }
306   
307   protected ClassInfo createClassInfo (String typeName, String url, byte[] data, ClassLoaderInfo definingLoader) throws ClassParseException {
308     return getCurrentSystemClassLoader().createClassInfo( typeName, url, data, definingLoader);
309   }
310
311   protected void setAttributes (ClassInfo ci){
312     getCurrentSystemClassLoader().setAttributes(ci);
313   }
314   
315   
316   /**
317    * obtain ClassInfo object for given class name
318    *
319    * if the requested class or any of its superclasses and interfaces
320    * is not found this method will throw a ClassInfoException. Loading
321    * of respective superclasses and interfaces happens recursively from here.
322    *
323    * Returned ClassInfo objects are not registered yet, i.e. still have to
324    * be added to the ClassLoaderInfo's statics, and don't have associated java.lang.Class
325    * objects until registerClass(ti) is called.
326    *
327    * Before any field or method access, the class also has to be initialized,
328    * which can include overlayed execution of &lt;clinit&gt; declaredMethods, which is done
329    * by calling initializeClass(ti,insn)
330    *
331    * this is for loading classes from the file system 
332    */
333   public ClassInfo getResolvedClassInfo (String className) throws ClassInfoException {
334     String typeName = Types.getClassNameFromTypeName( className);
335     
336     ClassInfo ci = resolvedClasses.get( typeName);
337     if (ci == null) {
338       if (ClassInfo.isBuiltinClass( typeName)){
339         ci = loadSystemClass( typeName);
340
341       } else {
342         ClassFileMatch match = getMatch( typeName);
343         if (match != null){
344           String url = match.getClassURL();
345           ci = loadedClasses.get( url); // have we loaded the class from this source before
346           if (ci != null){
347             if (ci.getClassLoaderInfo() != this){ // might have been loaded by another classloader
348               ci = ci.cloneFor(this);
349             }
350           } else {
351             try {
352               log.info("loading class ", typeName, " from ",  url);
353               ci = match.createClassInfo(this);
354               
355             } catch (ClassParseException cpx){
356               throw new ClassInfoException( "error parsing class", this, "java.lang.NoClassDefFoundError", typeName, cpx);
357             }
358             
359             loadedClasses.put( url, ci);
360           }
361           
362         } else { // no match found
363           throw new ClassInfoException("class not found: " + typeName, this, "java.lang.ClassNotFoundException", typeName);
364         }
365       }
366       
367       setAttributes(ci);
368       resolvedClasses.put(typeName, ci);
369     }
370     
371     return ci;
372   }
373   
374   /**
375    * this is for user defined ClassLoaders that explicitly provide the class file data
376    */
377   public ClassInfo getResolvedClassInfo (String className, byte[] data, int offset, int length) throws ClassInfoException {
378     String typeName = Types.getClassNameFromTypeName( className);
379     ClassInfo ci = resolvedClasses.get( typeName);    
380     
381     if (ci == null) {        
382       try {
383         // it can't be a builtin class since we have classfile contents
384         String url = typeName; // three isn't really a URL for it, just choose somehting
385         SystemClassLoaderInfo sysCl = getCurrentSystemClassLoader();
386         ci = sysCl.createClassInfo(typeName, url, data, this);
387
388         // no use to store it in loadedClasses since the data might be dynamically generated
389
390       } catch (ClassParseException cpx) {
391         throw new ClassInfoException("error parsing class", this, "java.lang.NoClassDefFoundError", typeName, cpx);
392       }
393
394       setAttributes(ci);
395       resolvedClasses.put( typeName, ci);
396     }
397     
398     return ci;
399   }
400     
401   public AnnotationInfo getResolvedAnnotationInfo (String typeName) throws ClassInfoException {
402     AnnotationInfo ai = resolvedAnnotations.get(typeName);
403     
404     if (ai == null){
405       ClassFileMatch match = getMatch( typeName);
406       if (match != null){
407         String url = match.getClassURL();
408         ai = loadedAnnotations.get(url); // have we loaded the class from this source before
409         if (ai != null) {
410           if (ai.getClassLoaderInfo() != this) { // might have been loaded by another classloader
411             ai = ai.cloneFor(this);
412           }
413           
414         } else {
415           try {
416             ai = match.createAnnotationInfo(this);
417             
418           } catch (ClassParseException cpx) {
419             throw new ClassInfoException("error parsing class", this, "java.lang.NoClassDefFoundError", typeName, cpx);
420           }
421             
422           loadedAnnotations.put( url, ai);
423         } 
424         
425       } else { // no match found
426         throw new ClassInfoException("class not found: " + typeName, this, "java.lang.ClassNotFoundException", typeName);
427       }
428       
429       resolvedAnnotations.put( typeName, ai);
430     }
431     
432     return ai;
433   }
434   
435   public ClassInfo getResolvedAnnotationProxy (ClassInfo ciAnnotation){
436     String typeName = ciAnnotation.getName() + "$Proxy";
437     
438     ClassInfo ci = resolvedClasses.get( typeName);
439     if (ci == null) {
440       ci = ciAnnotation.createAnnotationProxy(typeName);      
441       resolvedClasses.put( typeName, ci);
442     }
443
444     return ci;
445   }
446
447   /**
448    * This method returns a type which implements the given functional interface 
449    * and contains a method that captures the behavior of the lambda expression.
450    */
451   public ClassInfo getResolvedFuncObjType (int bsIdx, ClassInfo fiClassInfo, String samUniqueName, BootstrapMethodInfo bmi, String[] freeVariableTypeNames) {
452     String typeName = bmi.enclosingClass.getName() + "$$Lambda$" + bsIdx;
453     
454     ClassInfo funcObjType = resolvedClasses.get( typeName);
455     
456     if (funcObjType == null) {
457       funcObjType = fiClassInfo.createFuncObjClassInfo(bmi, typeName, samUniqueName, freeVariableTypeNames);
458       resolvedClasses.put( typeName, funcObjType);
459     }
460     
461     return funcObjType;
462   }
463   
464   protected ClassInfo getAlreadyResolvedClassInfo(String cname) {
465     return resolvedClasses.get(cname);
466   }
467
468   protected void addResolvedClass(ClassInfo ci) {
469     resolvedClasses.put(ci.getName(), ci);
470   }
471
472   protected boolean hasResolved(String cname) {
473     return (resolvedClasses.get(cname)!=null);
474   }
475
476   /**
477    * this one is for clients that need to synchronously get an initialized classinfo.
478    * NOTE: we don't handle clinits here. If there is one, this will throw
479    * an exception. NO STATIC BLOCKS / FIELDS ALLOWED
480    */
481   public ClassInfo getInitializedClassInfo (String clsName, ThreadInfo ti){
482     ClassInfo ci = getResolvedClassInfo(clsName);
483     ci.initializeClassAtomic(ti);
484     return ci;
485   }
486
487   /**
488    * obtain ClassInfo from context that does not care about resolution, i.e.
489    * does not check for NoClassInfoExceptions
490    *
491    * @param className fully qualified classname to get a ClassInfo for
492    * @return null if class was not found
493    */
494   public ClassInfo tryGetResolvedClassInfo (String className){
495     try {
496       return getResolvedClassInfo(className);
497     } catch (ClassInfoException cx){
498       return null;
499     }
500   }
501
502   public ClassInfo getClassInfo (int id) {
503     ElementInfo ei = statics.get(id);
504     if (ei != null) {
505       return ei.getClassInfo();
506     } else {
507       return null;
508     }
509   }
510
511   // it acquires the resolvedClassInfo by executing the class loader loadClass() method
512   public ClassInfo loadClass(String cname) {
513     ClassInfo ci = null;
514     if(roundTripRequired) {
515       // loadClass bytecode needs to be executed by the JPF vm
516       ci = loadClassOnJPF(cname);
517     } else {
518       // This class loader and the whole parent hierarchy use the standard class loading
519       // mechanism, therefore the class is loaded natively
520       ci = loadClassOnJVM(cname);
521     }
522
523     return ci;
524   }
525
526   protected ClassInfo loadClassOnJVM(String cname) {
527     String className = Types.getClassNameFromTypeName(cname);
528     // Check if the given class is already resolved by this loader
529     ClassInfo ci = getAlreadyResolvedClassInfo(className);
530
531     if (ci == null) {
532       try {
533         if(parent != null) {
534           ci = parent.loadClassOnJVM(cname);
535         } else {
536           ClassLoaderInfo systemClassLoader = getCurrentSystemClassLoader();
537           ci = systemClassLoader.getResolvedClassInfo(cname);
538         }
539       } catch(ClassInfoException cie) {
540         if(cie.getExceptionClass().equals("java.lang.ClassNotFoundException")) {
541           ci = getResolvedClassInfo(cname);
542         } else {
543           throw cie;
544         }
545       }
546     }
547
548     return ci;
549   }
550
551   // we need a system attribute to 
552   class LoadClassRequest implements SystemAttribute {
553     String typeName;
554     
555     LoadClassRequest (String typeName){
556       this.typeName = typeName;
557     }
558     
559     boolean isRequestFor( String typeName){
560       return this.typeName.equals( typeName);
561     }
562   }
563   
564   protected ClassInfo loadClassOnJPF (String typeName) {
565     String className = Types.getClassNameFromTypeName(typeName);
566     // Check if the given class is already resolved by this loader
567     ClassInfo ci = getAlreadyResolvedClassInfo(className);
568
569     if(ci != null) { // class already resolved
570       return ci;
571       
572     } else {   // class is not yet resolved, do a roundtrip for the respective loadClass() method
573       ThreadInfo ti = VM.getVM().getCurrentThread();  
574       StackFrame frame = ti.getReturnedDirectCall();
575       
576       if (frame != null){ // there was a roundtrip, but make sure it wasn't a recursive one
577         LoadClassRequest a = frame.getFrameAttr(LoadClassRequest.class);
578         if (a != null && a.isRequestFor(typeName)){ // the roundtrip is completed
579           int clsObjRef = frame.pop();
580
581           if (clsObjRef == MJIEnv.NULL) {
582             throw new ClassInfoException("class not found: " + typeName, this, "java.lang.NoClassDefFoundError", typeName);
583           } else {
584             return ti.getEnv().getReferredClassInfo(clsObjRef);
585           }          
586         }
587       }
588       // TODO: Fix for Groovy's model-checking
589       // TODO: May need to do a recursive lookup
590       if (parent != null) {
591         return parent.loadClass(typeName);
592       }
593
594       // initiate the roundtrip & bail out
595       pushloadClassFrame(typeName);
596       throw new LoadOnJPFRequired(typeName);
597     }
598   }
599
600   protected void pushloadClassFrame (String typeName) {
601     ThreadInfo ti = VM.getVM().getCurrentThread();
602
603     // obtain the class of this ClassLoader
604     ClassInfo clClass = VM.getVM().getClassInfo(objRef);
605
606     // retrieve the loadClass() method of this ClassLoader class
607     MethodInfo miLoadClass = clClass.getMethod("loadClass(Ljava/lang/String;)Ljava/lang/Class;", true);
608
609     // create a frame representing loadClass() & push it to the stack of the  current thread 
610     DirectCallStackFrame frame = miLoadClass.createDirectCallStackFrame( ti, 0);
611
612     String clsName = typeName.replace('/', '.');
613     int sRef = ti.getEnv().newString( clsName);
614     int argOffset = frame.setReferenceArgument( 0, objRef, null);
615     frame.setReferenceArgument( argOffset, sRef, null);
616
617     frame.setFrameAttr( new LoadClassRequest(typeName));
618     
619     ti.pushFrame(frame);
620   }
621
622   protected ClassInfo getDefinedClassInfo(String typeName){
623     ClassInfo ci = resolvedClasses.get(typeName);
624     if(ci != null && ci.classLoader == this) {
625       return ci;
626     } else {
627       return null;
628     }
629   }
630   
631   public ElementInfo getElementInfo (String typeName) {
632     ClassInfo ci = resolvedClasses.get(typeName);
633     if (ci != null) {
634       ClassLoaderInfo cli = ci.classLoader;
635       Statics st = cli.statics;
636       return st.get(ci.getId());
637       
638     } else {
639       return null; // not resolved
640     }
641   }
642
643   public ElementInfo getModifiableElementInfo (String typeName) {
644     ClassInfo ci = resolvedClasses.get(typeName);
645     if (ci != null) {
646       ClassLoaderInfo cli = ci.classLoader;
647       Statics st = cli.statics;
648       return st.getModifiable(ci.getId());
649       
650     } else {
651       return null; // not resolved
652     }
653   }
654
655   protected ClassFileMatch getMatch(String typeName) {
656     if(ClassInfo.isBuiltinClass(typeName)) {
657       return null;
658     }
659
660     ClassFileMatch match;
661     try {
662       match = cp.findMatch(typeName); 
663     } catch (ClassParseException cfx){
664       throw new JPFException("error reading class " + typeName, cfx);
665     }
666
667     return match;
668   }
669
670   /**
671    * Finds the first Resource in the classpath which has the specified name. 
672    * Returns null if no Resource is found.
673    */
674   public String findResource (String resourceName){
675     for (String cpe : getClassPathElements()) {
676       String URL = getResourceURL(cpe, resourceName);
677       if(URL != null) {
678         return URL;
679       }
680     }
681     return null;
682   }
683
684   /**
685    * Finds all resources in the classpath with the given name. Returns an 
686    * enumeration of the URL objects.
687    */
688   public String[] findResources (String resourceName){
689     ArrayList<String> resources = new ArrayList(0);
690     for (String cpe : getClassPathElements()) {
691       String URL = getResourceURL(cpe, resourceName);
692       if(URL != null) {
693         if(!resources.contains(URL)) {
694           resources.add(URL);
695         }
696       }
697     }
698     return resources.toArray(new String[resources.size()]);
699   }
700   
701   protected String getResourceURL(String path, String resource) {
702     if(resource != null) {
703       try {
704         if (path.endsWith(".jar")){
705           JarFile jar = new JarFile(path);
706           JarEntry e = jar.getJarEntry(resource);
707           if (e != null){
708             File f = new File(path);
709             return "jar:" + f.toURI().toURL().toString() + "!/" + resource;
710           }
711         } else {
712           File f = new File(path, resource);
713           if (f.exists()){
714             return f.toURI().toURL().toString();
715           }
716         }
717       } catch (MalformedURLException mfx){
718         return null;
719       } catch (IOException iox){
720         return null;
721       }
722     }
723
724     return null;
725   }
726
727   public Statics getStatics() {
728     return statics;
729   }
730
731   public ClassPath getClassPath() {
732     return cp;
733   }
734
735   public String[] getClassPathElements() {
736     return cp.getPathNames();
737   }
738
739   protected ClassFileContainer createClassFileContainer (String path){
740     return getCurrentSystemClassLoader().createClassFileContainer(path);
741   }
742   
743   public void addClassPathElement (String path){
744     ClassFileContainer cfc = createClassFileContainer(path);
745     
746     if (cfc != null){
747       cp.addClassFileContainer(cfc);
748     } else {
749       log.warning("unknown classpath element: ", path);
750     }
751   }
752   
753   /**
754    * Comparison for sorting based on index.
755    */
756   @Override
757   public int compareTo (ClassLoaderInfo that) {
758     return this.id - that.id;
759   }
760
761   /**
762    * Returns an iterator over the classes that are defined (directly loaded) by this classloader. 
763    */
764   @Override
765   public Iterator<ClassInfo> iterator () {
766     return resolvedClasses.values().iterator();
767   }
768
769   /**
770    * For now, this always returns true, and it used while the classloader is being
771    * serialized. That is going to be changed if we ever consider unloading the
772    * classes. For now, it is just added in analogy to ThreadInfo
773    */
774   public boolean isAlive () {
775     return true;
776   }
777
778   public Map<String, ClassLoaderInfo> getPackages() {
779     Map<String, ClassLoaderInfo> pkgs = new HashMap<String, ClassLoaderInfo>();
780     for(String cname: resolvedClasses.keySet()) {
781       if(!ClassInfo.isBuiltinClass(cname) && cname.indexOf('.')!=-1) {
782         pkgs.put(cname.substring(0, cname.lastIndexOf('.')), this);
783       }
784     }
785
786     Map<String, ClassLoaderInfo> parentPkgs = null;
787     if(parent!=null) {
788       parentPkgs = parent.getPackages();
789     }
790
791     if (parentPkgs != null) {
792       for (String pName: parentPkgs.keySet()) {
793         if (pkgs.get(pName) == null) {
794           pkgs.put(pName, parentPkgs.get(pName));
795         }
796       }
797     }
798     return pkgs;
799   }
800
801   //-------- assertion management --------
802   
803   // set in the jpf.properties file
804   static StringSetMatcher enabledAssertionPatterns;
805   static StringSetMatcher disabledAssertionPatterns;
806
807   protected Map<String, Boolean> classAssertionStatus = new HashMap<String, Boolean>();
808   protected Map<String, Boolean> packageAssertionStatus = new HashMap<String, Boolean>();
809   protected boolean defaultAssertionStatus = false;
810   protected boolean isDefaultSet = false;
811
812   protected boolean desiredAssertionStatus(String cname) {
813     // class level assertion can override all their assertion settings
814     Boolean result = classAssertionStatus.get(cname);
815     if (result != null) {
816       return result.booleanValue();
817     }
818
819     // package level assertion can override the default assertion settings
820     int dotIndex = cname.lastIndexOf(".");
821     if (dotIndex < 0) { // check for default package
822       result = packageAssertionStatus.get(null);
823       if (result != null) {
824         return result.booleanValue();
825       }
826     }
827
828     if(dotIndex > 0) {
829       String pkgName = cname;
830       while(dotIndex > 0) { // check for the class package and its upper level packages 
831         pkgName = pkgName.substring(0, dotIndex);
832         result = packageAssertionStatus.get(pkgName);
833         if (result != null) {
834           return result.booleanValue();
835         }
836         dotIndex = pkgName.lastIndexOf(".", dotIndex-1);
837       }
838     }
839
840     // class loader default, if it has been set, can override the settings
841     // specified by VM arguments
842     if(isDefaultSet) {
843       return defaultAssertionStatus;
844     } else {
845       return StringSetMatcher.isMatch(cname, enabledAssertionPatterns, disabledAssertionPatterns);
846     }
847   }
848
849   public void setDefaultAssertionStatus(boolean enabled) {
850     isDefaultSet = true;
851     defaultAssertionStatus = enabled;
852   }
853
854   public void setClassAssertionStatus(String cname, boolean enabled) {
855     classAssertionStatus.put(cname, enabled);
856   }
857
858   public void setPackageAssertionStatus(String pname, boolean enabled) {
859     packageAssertionStatus.put(pname, enabled);
860   }
861
862   public void clearAssertionStatus() {
863     classAssertionStatus = new HashMap<String, Boolean>();
864     packageAssertionStatus = new HashMap<String, Boolean>();
865     defaultAssertionStatus = false;
866   }
867 }