// // Copyright (C) 2012 United States Government as represented by the // Administrator of the National Aeronautics and Space Administration // (NASA). All Rights Reserved. // // This software is distributed under the NASA Open Source Agreement // (NOSA), version 1.3. The NOSA has been approved by the Open Source // Initiative. See the file NOSA-1.3-JPF at the top of the distribution // directory tree for the complete NOSA document. // // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. // package gov.nasa.jpf.vm; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import cmu.conditional.One; import de.fosd.typechef.featureexpr.FeatureExpr; import de.fosd.typechef.featureexpr.FeatureExprFactory; import gov.nasa.jpf.Config; import gov.nasa.jpf.JPF; import gov.nasa.jpf.JPFException; import gov.nasa.jpf.SystemAttribute; import gov.nasa.jpf.util.JPFLogger; import gov.nasa.jpf.util.SparseIntVector; import gov.nasa.jpf.util.StringSetMatcher; /** * @author Nastaran Shafiei <nastaran.shafiei@gmail.com> * * Represents the classloader construct in VM which is responsible for loading * classes. */ public class ClassLoaderInfo implements Iterable<ClassInfo>, Comparable<ClassLoaderInfo>, Cloneable, Restorable<ClassLoaderInfo> { static JPFLogger log = JPF.getLogger("class"); // the model class field name where we store our id protected static final String ID_FIELD = "nativeId"; protected static Config config; // this is where we keep the global list of classloader ids protected static SparseIntVector globalCLids; /** * Map from class file URLs to first ClassInfo that was read from it. This search * global map is used to make sure we only read class files once */ protected static Map<String,ClassInfo> loadedClasses; /** * map from annotation class file URLs to AnnotationInfos, which have a separate JPF internal * representation. Again, using a global map ensures we only read the related class files once */ protected static Map<String,AnnotationInfo> loadedAnnotations; // Map that keeps the classes defined (directly loaded) by this loader and the // ones that are resolved from these defined classes protected Map<String,ClassInfo> resolvedClasses; // annotations directly loaded by this classloader protected Map<String,AnnotationInfo> resolvedAnnotations; // Represents the locations where this classloader can load classes form - has to be populated subclasses protected ClassPath cp; // The type of the corresponding class loader object protected ClassInfo classInfo; // The area containing static fields and classes protected Statics statics; protected boolean roundTripRequired = false; // Search global id, which is the basis for canonical order of classloaders protected int id; // The java.lang.ClassLoader object reference protected int objRef; protected ClassLoaderInfo parent; static class ClMemento implements Memento<ClassLoaderInfo> { // note that we don't have to store the invariants (gid, parent, isSystemClassLoader) ClassLoaderInfo cl; Memento<Statics> staticsMemento; Memento<ClassPath> cpMemento; Map<String, Boolean> classAssertionStatus; Map<String, Boolean> packageAssertionStatus; boolean defaultAssertionStatus; boolean isDefaultSet; ClMemento (ClassLoaderInfo cl){ this.cl = cl; staticsMemento = cl.statics.getMemento(); cpMemento = cl.cp.getMemento(); classAssertionStatus = new HashMap<String, Boolean>(cl.classAssertionStatus); packageAssertionStatus = new HashMap<String, Boolean>(cl.packageAssertionStatus); defaultAssertionStatus = cl.defaultAssertionStatus; isDefaultSet = cl.isDefaultSet; } public ClassLoaderInfo restore(ClassLoaderInfo ignored) { staticsMemento.restore(cl.statics); cpMemento.restore(null); cl.classAssertionStatus = this.classAssertionStatus; cl.packageAssertionStatus = this.packageAssertionStatus; cl.defaultAssertionStatus = this.defaultAssertionStatus; cl.isDefaultSet = this.isDefaultSet; return cl; } } /** * This is invoked by VM.initSubsystems() */ static void init (Config config) { ClassLoaderInfo.config = config; globalCLids = new SparseIntVector(); loadedClasses = new HashMap<String,ClassInfo>(); // not sure we actually want this for multiple runs (unless we check file stamps) loadedAnnotations = new HashMap<String,AnnotationInfo>(); enabledAssertionPatterns = StringSetMatcher.getNonEmpty(config.getStringArray("vm.enable_assertions")); disabledAssertionPatterns = StringSetMatcher.getNonEmpty(config.getStringArray("vm.disable_assertions")); } public static int getNumberOfLoadedClasses (){ return loadedClasses.size(); } public static ClassInfo getCurrentResolvedClassInfo (String clsName){ ClassLoaderInfo cl = getCurrentClassLoader(); return cl.getResolvedClassInfo(null, clsName); } public static ClassInfo getSystemResolvedClassInfo (String clsName){ ClassLoaderInfo cl = getCurrentSystemClassLoader(); return cl.getResolvedClassInfo(null, clsName); } /** * for use from SystemClassLoaderInfo ctor, which doesn't have a ClassLoader object * yet and has to set cp and id itself */ protected ClassLoaderInfo (VM vm){ resolvedClasses = new HashMap<String,ClassInfo>(); resolvedAnnotations = new HashMap<String,AnnotationInfo>(); this.statics = createStatics(vm); cp = new ClassPath(); // registration has to happen from SystemClassLoaderInfo ctor since we are // only partially initialized at this point } /** * for all other classloaders, which require an already instantiated ClassLoader object */ protected ClassLoaderInfo (FeatureExpr ctx, VM vm, int objRef, ClassPath cp, ClassLoaderInfo parent) { resolvedClasses = new HashMap<String,ClassInfo>(); resolvedAnnotations = new HashMap<String,AnnotationInfo>(); this.parent = parent; this.objRef = objRef; this.cp = cp; this.statics = createStatics(vm); this.id = computeId(objRef); ElementInfo ei = vm.getModifiableElementInfo(objRef); ei.setIntField(ctx, ID_FIELD, new One<>(id)); if (parent != null) { ei.setReferenceField(ctx, "parent", new One<>(parent.objRef)); } classInfo = ei.getClassInfo(); roundTripRequired = isRoundTripRequired(); vm.registerClassLoader(this); } public Memento<ClassLoaderInfo> getMemento (MementoFactory factory) { return factory.getMemento(this); } public Memento<ClassLoaderInfo> getMemento(){ return new ClMemento(this); } protected Statics createStatics (VM vm){ Class<?>[] argTypes = { Config.class, KernelState.class }; Object[] args = { config, vm.getKernelState() }; return config.getEssentialInstance("vm.statics.class", Statics.class, argTypes, args); } /** * this is our internal, search global id that is used for the * canonical root set */ public int getId() { return id; } /** * Returns the type of the corresponding class loader object */ public ClassInfo getClassInfo () { return classInfo; } /** * Returns the object reference. */ public int getClassLoaderObjectRef () { return objRef; } protected int computeId (int objRef) { int id = globalCLids.get(objRef); if (id == 0) { id = globalCLids.size() + 1; // the first systemClassLoader is not in globalCLids and always has id '0' globalCLids.set(objRef, id); } return id; } /** * For optimizing the class loading mechanism, if the class loader class and the * classes of the whole parents hierarchy are descendant of URLClassLoader and * do not override the ClassLoader.loadClass() & URLClassLoader.findClass, resolving * the class is done natively within JPF */ protected boolean isRoundTripRequired() { return (parent!=null? parent.roundTripRequired: true) || !hasOriginalLoadingImp(); } private boolean hasOriginalLoadingImp() { String signature = "(Ljava/lang/String;)Ljava/lang/Class;"; MethodInfo loadClass = classInfo.getMethod("loadClass" + signature, true); MethodInfo findClass = classInfo.getMethod("findClass" + signature, true); return (loadClass.getClassName().equals("java.lang.ClassLoader") && findClass.getClassName().equals("java.net.URLClassLoader")); } public boolean isSystemClassLoader() { return false; } public static ClassLoaderInfo getCurrentClassLoader() { return getCurrentClassLoader( ThreadInfo.getCurrentThread()); } public static ClassLoaderInfo getCurrentClassLoader (ThreadInfo ti) { for (StackFrame frame = ti.getTopFrame(); frame != null; frame = frame.getPrevious()){ MethodInfo miFrame = frame.getMethodInfo(); ClassInfo ciFrame = miFrame.getClassInfo(); if (ciFrame != null){ return ciFrame.getClassLoaderInfo(); } } return ti.getSystemClassLoaderInfo(); } public static SystemClassLoaderInfo getCurrentSystemClassLoader() { ThreadInfo ti = ThreadInfo.getCurrentThread(); if (ti != null){ return ti.getSystemClassLoaderInfo(); } else { // this is kind of a hack - we just use the latest SystemClassLoaderInfo instance // this might happen if the SystemClassLoader preloads classes before we have a main thread return SystemClassLoaderInfo.lastInstance; } } public SystemClassLoaderInfo getSystemClassLoader() { return getCurrentSystemClassLoader(); } protected ClassInfo loadSystemClass (String clsName){ return getCurrentSystemClassLoader().loadSystemClass(clsName); } protected ClassInfo createClassInfo (String typeName, ClassFileMatch match, ClassLoaderInfo definingLoader) throws ClassParseException { return getCurrentSystemClassLoader().createClassInfo( typeName, match, definingLoader); } protected ClassInfo createClassInfo (String typeName, String url, byte[] data, ClassLoaderInfo definingLoader) throws ClassParseException { return getCurrentSystemClassLoader().createClassInfo( typeName, url, data, definingLoader); } /** * obtain ClassInfo object for given class name * * if the requested class or any of its superclasses and interfaces * is not found this method will throw a ClassInfoException. Loading * of respective superclasses and interfaces happens recursively from here. * * Returned ClassInfo objects are not registered yet, i.e. still have to * be added to the ClassLoaderInfo's statics, and don't have associated java.lang.Class * objects until registerClass(ti) is called. * * Before any field or method access, the class also has to be initialized, * which can include overlayed execution of <clinit> declaredMethods, which is done * by calling initializeClass(ti,insn) * * this is for loading classes from the file system */ public ClassInfo getResolvedClassInfo (FeatureExpr ctx, String className) throws ClassInfoException { if (className.isEmpty()) { throw new ClassInfoException("class not found: " + className, this, "java.lang.ClassNotFoundException", className); } String typeName = Types.getClassNameFromTypeName( className); ClassInfo ci = resolvedClasses.get( typeName); if (ci == null) { if (ClassInfo.isBuiltinClass( typeName)){ ci = loadSystemClass( typeName); } else { ClassFileMatch match = getMatch( typeName); if (match != null){ String url = match.getClassURL(); ci = loadedClasses.get( url); // have we loaded the class from this source before if (ci != null){ if (ci.getClassLoaderInfo() != this){ // might have been loaded by another classloader ci = ci.cloneFor(ctx, this); } } else { try { log.info("loading class ", typeName, " from ", url); ci = match.createClassInfo(ctx, this); } catch (ClassParseException cpx){ throw new ClassInfoException( "error parsing class", this, "java.lang.NoClassDefFoundError", typeName, cpx); } loadedClasses.put( url, ci); } } else { // no match found throw new ClassInfoException("class not found: " + typeName, this, "java.lang.ClassNotFoundException", typeName); } } resolvedClasses.put(typeName, ci); } return ci; } /** * this is for user defined ClassLoaders that explicitly provide the class file data */ public ClassInfo getResolvedClassInfo (String className, byte[] data, int offset, int length) throws ClassInfoException { String typeName = Types.getClassNameFromTypeName( className); ClassInfo ci = resolvedClasses.get( typeName); if (ci == null) { try { // it can't be a builtin class since we have classfile contents String url = typeName; // three isn't really a URL for it, just choose somehting SystemClassLoaderInfo sysCl = getCurrentSystemClassLoader(); ci = sysCl.createClassInfo(typeName, url, data, this); // no use to store it in loadedClasses since the data might be dynamically generated } catch (ClassParseException cpx) { throw new ClassInfoException("error parsing class", this, "java.lang.NoClassDefFoundError", typeName, cpx); } resolvedClasses.put( typeName, ci); } return ci; } public AnnotationInfo getResolvedAnnotationInfo (String typeName) throws ClassInfoException { AnnotationInfo ai = resolvedAnnotations.get(typeName); if (ai == null){ ClassFileMatch match = getMatch( typeName); if (match != null){ String url = match.getClassURL(); ai = loadedAnnotations.get(url); // have we loaded the class from this source before if (ai != null) { if (ai.getClassLoaderInfo() != this) { // might have been loaded by another classloader ai = ai.cloneFor(this); } } else { try { ai = match.createAnnotationInfo(this); } catch (ClassParseException cpx) { throw new ClassInfoException("error parsing class", this, "java.lang.NoClassDefFoundError", typeName, cpx); } loadedAnnotations.put( url, ai); } } else { // no match found throw new ClassInfoException("class not found: " + typeName, this, "java.lang.ClassNotFoundException", typeName); } resolvedAnnotations.put( typeName, ai); } return ai; } public ClassInfo getResolvedAnnotationProxy (ClassInfo ciAnnotation){ String typeName = ciAnnotation.getName() + "$Proxy"; ClassInfo ci = resolvedClasses.get( typeName); if (ci == null) { ci = ciAnnotation.createAnnotationProxy(typeName); resolvedClasses.put( typeName, ci); } return ci; } /** * This method returns a type which implements the given functional interface * and contains a method that captures the behavior of the lambda expression. */ public ClassInfo getResolvedFuncObjType (int bsIdx, ClassInfo fiClassInfo, String samUniqueName, BootstrapMethodInfo bmi, String[] freeVariableTypeNames) { String typeName = bmi.enclosingClass.getName() + "$$Lambda$" + bsIdx; ClassInfo funcObjType = resolvedClasses.get( typeName); if (funcObjType == null) { funcObjType = fiClassInfo.createFuncObjClassInfo(bmi, typeName, samUniqueName, freeVariableTypeNames); resolvedClasses.put( typeName, funcObjType); } return funcObjType; } protected ClassInfo getAlreadyResolvedClassInfo(String cname) { return resolvedClasses.get(cname); } protected void addResolvedClass(ClassInfo ci) { resolvedClasses.put(ci.getName(), ci); } protected boolean hasResolved(String cname) { return (resolvedClasses.get(cname)!=null); } /** * this one is for clients that need to synchronously get an initialized classinfo. * NOTE: we don't handle clinits here. If there is one, this will throw * an exception. NO STATIC BLOCKS / FIELDS ALLOWED */ public ClassInfo getInitializedClassInfo (FeatureExpr ctx, String clsName, ThreadInfo ti){ ClassInfo ci = getResolvedClassInfo(ctx, clsName); ci.registerClass(FeatureExprFactory.True(), ti); // this is safe to call on already loaded classes if (!ci.isInitialized()) { if (ci.initializeClass(ctx, ti)) { throw new ClinitRequired(ci); } } return ci; } /** * obtain ClassInfo from context that does not care about resolution, i.e. * does not check for NoClassInfoExceptions * * @param className fully qualified classname to get a ClassInfo for * @return null if class was not found */ public ClassInfo tryGetResolvedClassInfo (String className){ try { return getResolvedClassInfo(null, className); } catch (ClassInfoException cx){ return null; } } public ClassInfo getClassInfo (int id) { ElementInfo ei = statics.get(id); if (ei != null) { return ei.getClassInfo(); } else { return null; } } // it acquires the resolvedClassInfo by executing the class loader loadClass() method public ClassInfo loadClass(FeatureExpr ctx, String cname) { ClassInfo ci = null; if(roundTripRequired) { // loadClass bytecode needs to be executed by the JPF vm ci = loadClassOnJPF(ctx, cname); } else { // This class loader and the whole parent hierarchy use the standard class loading // mechanism, therefore the class is loaded natively ci = loadClassOnJVM(cname); } return ci; } protected ClassInfo loadClassOnJVM(String cname) { String className = Types.getClassNameFromTypeName(cname); // Check if the given class is already resolved by this loader ClassInfo ci = getAlreadyResolvedClassInfo(className); if (ci == null) { try { if(parent != null) { ci = parent.loadClassOnJVM(cname); } else { ClassLoaderInfo systemClassLoader = getCurrentSystemClassLoader(); ci = systemClassLoader.getResolvedClassInfo(null, cname); } } catch(ClassInfoException cie) { if(cie.getExceptionClass().equals("java.lang.ClassNotFoundException")) { ci = getResolvedClassInfo(null, cname); } else { throw cie; } } } return ci; } // we need a system attribute to class LoadClassRequest implements SystemAttribute { String typeName; LoadClassRequest (String typeName){ this.typeName = typeName; } boolean isRequestFor( String typeName){ return this.typeName.equals( typeName); } } protected ClassInfo loadClassOnJPF (FeatureExpr ctx, String typeName) { String className = Types.getClassNameFromTypeName(typeName); // Check if the given class is already resolved by this loader ClassInfo ci = getAlreadyResolvedClassInfo(className); if(ci != null) { // class already resolved return ci; } else { // class is not yet resolved, do a roundtrip for the respective loadClass() method ThreadInfo ti = VM.getVM().getCurrentThread(); StackFrame frame = ti.getReturnedDirectCall(); if (frame != null){ // there was a roundtrip, but make sure it wasn't a recursive one LoadClassRequest a = frame.getFrameAttr(LoadClassRequest.class); if (a != null && a.isRequestFor(typeName)){ // the roundtrip is completed int clsObjRef = frame.pop(ctx).getValue(); if (clsObjRef == MJIEnv.NULL) { throw new ClassInfoException("class not found: " + typeName, this, "java.lang.NoClassDefFoundError", typeName); } else { return ti.getEnv().getReferredClassInfo(ctx, clsObjRef); } } } // initiate the roundtrip & bail out pushloadClassFrame(ctx, typeName); throw new LoadOnJPFRequired(typeName); } } protected void pushloadClassFrame (FeatureExpr ctx, String typeName) { ThreadInfo ti = VM.getVM().getCurrentThread(); // obtain the class of this ClassLoader ClassInfo clClass = VM.getVM().getClassInfo(objRef); // retrieve the loadClass() method of this ClassLoader class MethodInfo miLoadClass = clClass.getMethod("loadClass(Ljava/lang/String;)Ljava/lang/Class;", true); // create a frame representing loadClass() & push it to the stack of the current thread DirectCallStackFrame frame = miLoadClass.createDirectCallStackFrame( ctx, ti, 0); String clsName = typeName.replace('/', '.'); int sRef = ti.getEnv().newString(FeatureExprFactory.True(), clsName); int argOffset = frame.setReferenceArgument( ctx, 0, objRef, null); frame.setReferenceArgument( ctx, argOffset, sRef, null); frame.setFrameAttr( new LoadClassRequest(typeName)); ti.pushFrame(frame); } protected ClassInfo getDefinedClassInfo(String typeName){ ClassInfo ci = resolvedClasses.get(typeName); if(ci != null && ci.classLoader == this) { return ci; } else { return null; } } public ElementInfo getElementInfo (String typeName) { ClassInfo ci = resolvedClasses.get(typeName); if (ci != null) { ClassLoaderInfo cli = ci.classLoader; Statics st = cli.statics; return st.get(ci.getId()); } else { return null; // not resolved } } public ElementInfo getModifiableElementInfo (String typeName) { ClassInfo ci = resolvedClasses.get(typeName); if (ci != null) { ClassLoaderInfo cli = ci.classLoader; Statics st = cli.statics; return st.getModifiable(ci.getId()); } else { return null; // not resolved } } protected ClassFileMatch getMatch(String typeName) { if(ClassInfo.isBuiltinClass(typeName)) { return null; } ClassFileMatch match; try { match = cp.findMatch(typeName); } catch (ClassParseException cfx){ throw new JPFException("error reading class " + typeName, cfx); } return match; } /** * Finds the first Resource in the classpath which has the specified name. * Returns null if no Resource is found. */ public String findResource (String resourceName){ for (String cpe : getClassPathElements()) { String URL = getResourceURL(cpe, resourceName); if(URL != null) { return URL; } } return null; } /** * Finds all resources in the classpath with the given name. Returns an * enumeration of the URL objects. */ public String[] findResources (String resourceName){ ArrayList<String> resources = new ArrayList<>(0); for (String cpe : getClassPathElements()) { String URL = getResourceURL(cpe, resourceName); if(URL != null) { if(!resources.contains(URL)) { resources.add(URL); } } } return resources.toArray(new String[resources.size()]); } protected String getResourceURL(String path, String resource) { if(resource != null) { try { if (path.endsWith(".jar")){ try(JarFile jar = new JarFile(path)) { JarEntry e = jar.getJarEntry(resource); if (e != null){ File f = new File(path); return "jar:" + f.toURI().toURL().toString() + "!/" + resource; } } } else { File f = new File(path, resource); if (f.exists()){ return f.toURI().toURL().toString(); } } } catch (MalformedURLException mfx){ return null; } catch (IOException iox){ return null; } } return null; } public Statics getStatics() { return statics; } public ClassPath getClassPath() { return cp; } public String[] getClassPathElements() { return cp.getPathNames(); } protected ClassFileContainer createClassFileContainer (String path){ return getCurrentSystemClassLoader().createClassFileContainer(path); } public void addClassPathElement (String path) { for (String existingPath : cp.getPathNames()) { if (existingPath.equals(path)) { return; } } ClassFileContainer cfc = createClassFileContainer(path); if (cfc != null) { cp.addClassFileContainer(cfc); } else { log.warning("unknown classpath element: ", path); } } /** * Comparison for sorting based on index. */ public int compareTo (ClassLoaderInfo that) { return this.id - that.id; } /** * Returns an iterator over the classes that are defined (directly loaded) by this classloader. */ public Iterator<ClassInfo> iterator () { return resolvedClasses.values().iterator(); } /** * For now, this always returns true, and it used while the classloader is being * serialized. That is going to be changed if we ever consider unloading the * classes. For now, it is just added in analogy to ThreadInfo */ public boolean isAlive () { return true; } public Map<String, ClassLoaderInfo> getPackages() { Map<String, ClassLoaderInfo> pkgs = new HashMap<String, ClassLoaderInfo>(); for(String cname: resolvedClasses.keySet()) { if(!ClassInfo.isBuiltinClass(cname) && cname.indexOf('.')!=-1) { pkgs.put(cname.substring(0, cname.lastIndexOf('.')), this); } } Map<String, ClassLoaderInfo> parentPkgs = null; if(parent!=null) { parentPkgs = parent.getPackages(); } if (parentPkgs != null) { for (String pName: parentPkgs.keySet()) { if (pkgs.get(pName) == null) { pkgs.put(pName, parentPkgs.get(pName)); } } } return pkgs; } //-------- assertion management -------- // set in the jpf.properties file static StringSetMatcher enabledAssertionPatterns; static StringSetMatcher disabledAssertionPatterns; protected Map<String, Boolean> classAssertionStatus = new HashMap<String, Boolean>(); protected Map<String, Boolean> packageAssertionStatus = new HashMap<String, Boolean>(); protected boolean defaultAssertionStatus = false; protected boolean isDefaultSet = false; protected boolean desiredAssertionStatus(String cname) { // class level assertion can override all their assertion settings Boolean result = classAssertionStatus.get(cname); if (result != null) { return result.booleanValue(); } // package level assertion can override the default assertion settings int dotIndex = cname.lastIndexOf("."); if (dotIndex < 0) { // check for default package result = packageAssertionStatus.get(null); if (result != null) { return result.booleanValue(); } } if(dotIndex > 0) { String pkgName = cname; while(dotIndex > 0) { // check for the class package and its upper level packages pkgName = pkgName.substring(0, dotIndex); result = packageAssertionStatus.get(pkgName); if (result != null) { return result.booleanValue(); } dotIndex = pkgName.lastIndexOf(".", dotIndex-1); } } // class loader default, if it has been set, can override the settings // specified by VM arguments if(isDefaultSet) { return defaultAssertionStatus; } else { return StringSetMatcher.isMatch(cname, enabledAssertionPatterns, disabledAssertionPatterns); } } public void setDefaultAssertionStatus(boolean enabled) { isDefaultSet = true; defaultAssertionStatus = enabled; } public void setClassAssertionStatus(String cname, boolean enabled) { classAssertionStatus.put(cname, enabled); } public void setPackageAssertionStatus(String pname, boolean enabled) { packageAssertionStatus.put(pname, enabled); } public void clearAssertionStatus() { classAssertionStatus = new HashMap<String, Boolean>(); packageAssertionStatus = new HashMap<String, Boolean>(); defaultAssertionStatus = false; } }