Fixing a few bugs in the statistics printout.
[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
589       // initiate the roundtrip & bail out
590       pushloadClassFrame(typeName);
591       throw new LoadOnJPFRequired(typeName);
592     }
593   }
594
595   protected void pushloadClassFrame (String typeName) {
596     ThreadInfo ti = VM.getVM().getCurrentThread();
597
598     // obtain the class of this ClassLoader
599     ClassInfo clClass = VM.getVM().getClassInfo(objRef);
600
601     // retrieve the loadClass() method of this ClassLoader class
602     MethodInfo miLoadClass = clClass.getMethod("loadClass(Ljava/lang/String;)Ljava/lang/Class;", true);
603
604     // create a frame representing loadClass() & push it to the stack of the  current thread 
605     DirectCallStackFrame frame = miLoadClass.createDirectCallStackFrame( ti, 0);
606
607     String clsName = typeName.replace('/', '.');
608     int sRef = ti.getEnv().newString( clsName);
609     int argOffset = frame.setReferenceArgument( 0, objRef, null);
610     frame.setReferenceArgument( argOffset, sRef, null);
611
612     frame.setFrameAttr( new LoadClassRequest(typeName));
613     
614     ti.pushFrame(frame);
615   }
616
617   protected ClassInfo getDefinedClassInfo(String typeName){
618     ClassInfo ci = resolvedClasses.get(typeName);
619     if(ci != null && ci.classLoader == this) {
620       return ci;
621     } else {
622       return null;
623     }
624   }
625   
626   public ElementInfo getElementInfo (String typeName) {
627     ClassInfo ci = resolvedClasses.get(typeName);
628     if (ci != null) {
629       ClassLoaderInfo cli = ci.classLoader;
630       Statics st = cli.statics;
631       return st.get(ci.getId());
632       
633     } else {
634       return null; // not resolved
635     }
636   }
637
638   public ElementInfo getModifiableElementInfo (String typeName) {
639     ClassInfo ci = resolvedClasses.get(typeName);
640     if (ci != null) {
641       ClassLoaderInfo cli = ci.classLoader;
642       Statics st = cli.statics;
643       return st.getModifiable(ci.getId());
644       
645     } else {
646       return null; // not resolved
647     }
648   }
649
650   protected ClassFileMatch getMatch(String typeName) {
651     if(ClassInfo.isBuiltinClass(typeName)) {
652       return null;
653     }
654
655     ClassFileMatch match;
656     try {
657       match = cp.findMatch(typeName); 
658     } catch (ClassParseException cfx){
659       throw new JPFException("error reading class " + typeName, cfx);
660     }
661
662     return match;
663   }
664
665   /**
666    * Finds the first Resource in the classpath which has the specified name. 
667    * Returns null if no Resource is found.
668    */
669   public String findResource (String resourceName){
670     for (String cpe : getClassPathElements()) {
671       String URL = getResourceURL(cpe, resourceName);
672       if(URL != null) {
673         return URL;
674       }
675     }
676     return null;
677   }
678
679   /**
680    * Finds all resources in the classpath with the given name. Returns an 
681    * enumeration of the URL objects.
682    */
683   public String[] findResources (String resourceName){
684     ArrayList<String> resources = new ArrayList(0);
685     for (String cpe : getClassPathElements()) {
686       String URL = getResourceURL(cpe, resourceName);
687       if(URL != null) {
688         if(!resources.contains(URL)) {
689           resources.add(URL);
690         }
691       }
692     }
693     return resources.toArray(new String[resources.size()]);
694   }
695   
696   protected String getResourceURL(String path, String resource) {
697     if(resource != null) {
698       try {
699         if (path.endsWith(".jar")){
700           JarFile jar = new JarFile(path);
701           JarEntry e = jar.getJarEntry(resource);
702           if (e != null){
703             File f = new File(path);
704             return "jar:" + f.toURI().toURL().toString() + "!/" + resource;
705           }
706         } else {
707           File f = new File(path, resource);
708           if (f.exists()){
709             return f.toURI().toURL().toString();
710           }
711         }
712       } catch (MalformedURLException mfx){
713         return null;
714       } catch (IOException iox){
715         return null;
716       }
717     }
718
719     return null;
720   }
721
722   public Statics getStatics() {
723     return statics;
724   }
725
726   public ClassPath getClassPath() {
727     return cp;
728   }
729
730   public String[] getClassPathElements() {
731     return cp.getPathNames();
732   }
733
734   protected ClassFileContainer createClassFileContainer (String path){
735     return getCurrentSystemClassLoader().createClassFileContainer(path);
736   }
737   
738   public void addClassPathElement (String path){
739     ClassFileContainer cfc = createClassFileContainer(path);
740     
741     if (cfc != null){
742       cp.addClassFileContainer(cfc);
743     } else {
744       log.warning("unknown classpath element: ", path);
745     }
746   }
747   
748   /**
749    * Comparison for sorting based on index.
750    */
751   @Override
752   public int compareTo (ClassLoaderInfo that) {
753     return this.id - that.id;
754   }
755
756   /**
757    * Returns an iterator over the classes that are defined (directly loaded) by this classloader. 
758    */
759   @Override
760   public Iterator<ClassInfo> iterator () {
761     return resolvedClasses.values().iterator();
762   }
763
764   /**
765    * For now, this always returns true, and it used while the classloader is being
766    * serialized. That is going to be changed if we ever consider unloading the
767    * classes. For now, it is just added in analogy to ThreadInfo
768    */
769   public boolean isAlive () {
770     return true;
771   }
772
773   public Map<String, ClassLoaderInfo> getPackages() {
774     Map<String, ClassLoaderInfo> pkgs = new HashMap<String, ClassLoaderInfo>();
775     for(String cname: resolvedClasses.keySet()) {
776       if(!ClassInfo.isBuiltinClass(cname) && cname.indexOf('.')!=-1) {
777         pkgs.put(cname.substring(0, cname.lastIndexOf('.')), this);
778       }
779     }
780
781     Map<String, ClassLoaderInfo> parentPkgs = null;
782     if(parent!=null) {
783       parentPkgs = parent.getPackages();
784     }
785
786     if (parentPkgs != null) {
787       for (String pName: parentPkgs.keySet()) {
788         if (pkgs.get(pName) == null) {
789           pkgs.put(pName, parentPkgs.get(pName));
790         }
791       }
792     }
793     return pkgs;
794   }
795
796   //-------- assertion management --------
797   
798   // set in the jpf.properties file
799   static StringSetMatcher enabledAssertionPatterns;
800   static StringSetMatcher disabledAssertionPatterns;
801
802   protected Map<String, Boolean> classAssertionStatus = new HashMap<String, Boolean>();
803   protected Map<String, Boolean> packageAssertionStatus = new HashMap<String, Boolean>();
804   protected boolean defaultAssertionStatus = false;
805   protected boolean isDefaultSet = false;
806
807   protected boolean desiredAssertionStatus(String cname) {
808     // class level assertion can override all their assertion settings
809     Boolean result = classAssertionStatus.get(cname);
810     if (result != null) {
811       return result.booleanValue();
812     }
813
814     // package level assertion can override the default assertion settings
815     int dotIndex = cname.lastIndexOf(".");
816     if (dotIndex < 0) { // check for default package
817       result = packageAssertionStatus.get(null);
818       if (result != null) {
819         return result.booleanValue();
820       }
821     }
822
823     if(dotIndex > 0) {
824       String pkgName = cname;
825       while(dotIndex > 0) { // check for the class package and its upper level packages 
826         pkgName = pkgName.substring(0, dotIndex);
827         result = packageAssertionStatus.get(pkgName);
828         if (result != null) {
829           return result.booleanValue();
830         }
831         dotIndex = pkgName.lastIndexOf(".", dotIndex-1);
832       }
833     }
834
835     // class loader default, if it has been set, can override the settings
836     // specified by VM arguments
837     if(isDefaultSet) {
838       return defaultAssertionStatus;
839     } else {
840       return StringSetMatcher.isMatch(cname, enabledAssertionPatterns, disabledAssertionPatterns);
841     }
842   }
843
844   public void setDefaultAssertionStatus(boolean enabled) {
845     isDefaultSet = true;
846     defaultAssertionStatus = enabled;
847   }
848
849   public void setClassAssertionStatus(String cname, boolean enabled) {
850     classAssertionStatus.put(cname, enabled);
851   }
852
853   public void setPackageAssertionStatus(String pname, boolean enabled) {
854     packageAssertionStatus.put(pname, enabled);
855   }
856
857   public void clearAssertionStatus() {
858     classAssertionStatus = new HashMap<String, Boolean>();
859     packageAssertionStatus = new HashMap<String, Boolean>();
860     defaultAssertionStatus = false;
861   }
862 }