/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.parser; import gw.fs.IFile; import gw.internal.gosu.module.DefaultSingleModule; import gw.lang.reflect.java.asm.AsmClass; import gw.lang.reflect.java.asm.AsmClassLoader; import gw.lang.reflect.IInjectableClassLoader; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.TypeName; import gw.lang.reflect.module.IClassPath; import gw.lang.reflect.module.IModule; import gw.util.concurrent.LockingLazyVar; import java.io.IOException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; public class ClassCache { @SuppressWarnings({"unchecked"}) private final Map<String, Class> _classMap = new HashMap<String, Class>(); private Set<CharSequence> _packages = new HashSet<CharSequence>(); private IModule _module; private LockingLazyVar<ClassPath> _classPathCache; private LockingLazyVar<Set<String>> _allTypeNamesCache = new LockingLazyVar<Set<String>>() { @Override protected Set<String> init() { HashSet<String> strings = new HashSet<String>(); Set<String> filteredClassNames = _classPathCache.get().getFilteredClassNames(); for (String className : filteredClassNames) { strings.add(className.replace('$', '.')); } return strings; } }; private boolean ignoreTheCache; public ClassCache(final IModule module) { _module = module; ignoreTheCache = module instanceof DefaultSingleModule; _classPathCache = new LockingLazyVar<ClassPath>() { protected ClassPath init() { return new ClassPath( _module, _module instanceof DefaultSingleModule ? IClassPath.ONLY_API_CLASSES : // FIXME-isd: for performance reasons, only select API classes IClassPath.ALLOW_ALL_WITH_SUN_FILTER); } }; } private Class tryToLoadClass(CharSequence name) { if (_packages.contains(name)) { // A package name, valid or not, is never a Class, so fail immediately return ClassNotFoundMarkerClass.class; } try { Class<?> cls; try { cls = _module.getModuleClassLoader().loadClass(name.toString()); } catch (ClassNotFoundException cnfe) { return ClassNotFoundMarkerClass.class; } if (cls == null) { return ClassNotFoundMarkerClass.class; } if (!cls.isArray() && !cls.isPrimitive()) { addFoundPackages(cls); } return cls; } catch (VerifyError e) { // Probably could not inherit from final class return VerifyError.class; } } private void addFoundPackages(Class<?> aClass) { String packageName = aClass.getName(); if (packageName.contains(".")) { packageName = packageName.substring(0, packageName.lastIndexOf('.')); int dotIndex = 0; while (dotIndex != -1) { int nextDot = packageName.indexOf('.', dotIndex); _packages.add(packageName.subSequence(0, nextDot == -1 ? packageName.length() : nextDot)); dotIndex = nextDot == -1 ? -1 : nextDot + 1; } } } public AsmClass loadAsmClass( String className ) { AsmClass primitiveClazz = AsmClass.findPrimitive( className ); try { IModule jreModule = _module.getExecutionEnvironment().getJreModule(); if( jreModule == _module && primitiveClazz != null ) { return primitiveClazz; } } catch( Exception e ) { // ignore, jreModule isn't available yet } if( _classPathCache.get().isEmpty() ) { return null; } StringBuilder s = new StringBuilder( className ); int i; do { if( ignoreTheCache || _classPathCache.get().contains( className ) ) { IFile file = _classPathCache.get().get( className ); if( file != null ) { try { return AsmClassLoader.loadClass( _module, className, file.openInputStream() ); } catch( IOException e ) { throw new RuntimeException( e ); } } } i = s.lastIndexOf( "." ); if( i >= 0 ) { if( isPackage( s, i ) ) { return null; } s.setCharAt( i, '$' ); className = s.toString(); } } while( i >= 0 ); return null; } public Class loadClass(String className) { Class primitiveClazz = Primitives.get(className); if (_module.getExecutionEnvironment().getJreModule() == _module && primitiveClazz != null) { return primitiveClazz; } StringBuilder s = new StringBuilder(className); int i; do { if (ignoreTheCache || _classPathCache.get().contains(className)) { Class aClass = loadClassImplImpl(className); if (aClass != null) { return aClass; } } i = s.lastIndexOf("."); if (i >= 0) { if( isPackage( s, i ) ) { return null; } s.setCharAt(i, '$'); className = s.toString(); } } while (i >= 0); return null; } /** * Short-circuit the case where com.foo.Fred is not a class name, but * com.foo is a package. Avoid the expensive test for com.foo$Fred as a an * inner class (and then com$foo$Fred). * <p> * Yes, java supports a package and a class having the same name, but in this case * we are checking for an inner class of a class having the same name as a package... * Let's just not support references to those in Gosu in the name of both sanity * and performance. * <p> * Warning this design decision was not vetted through various committees, architect * round tables, community processes, or guys with beards. */ private boolean isPackage( StringBuilder s, int i ) { try { String maybePackage = s.substring( 0, i ); if( getPackageMethod().invoke( _module.getModuleClassLoader(), maybePackage ) != null ) { return true; } } catch( Exception e ) { throw new RuntimeException( e ); } return false; } private static Method _getPackageMethod = null; private Method getPackageMethod() { if( _getPackageMethod == null ) { try { _getPackageMethod = ClassLoader.class.getDeclaredMethod("getPackage", String.class); _getPackageMethod.setAccessible( true ); } catch (NoSuchMethodException e) { throw new RuntimeException( e ); } } return _getPackageMethod; } private Class loadClassImplImpl(String type) { type = normalizeArrayNotation(type); Class clazz = _classMap.get(type); if (clazz == null) { TypeSystem.lock(); try { clazz = _classMap.get(type); if (clazz == null) { clazz = tryToLoadClass(type); _classMap.put(type, clazz); } } finally { TypeSystem.unlock(); } } if (clazz != ClassNotFoundMarkerClass.class) { return clazz; } else { return null; } } /** * Normalizes a class name string to the appropriate java class name. For example: * <p/> * java.lang.String -> java.lang.String * java.lang.String[] -> [Ljava.lang.String; * java.lang.String[][] -> [[Ljava.lang.String; * * @param type Name of type * @return ormalizes a class name string to the appropriate java class name */ private static String normalizeArrayNotation(String type) { int arrayDimensions = 0; while (type.endsWith("[]")) { type = type.substring(0, type.length() - 2); arrayDimensions++; } if (arrayDimensions == 0) { return type; } else { String arraySignature = Primitives.getArraySignature(type); if (arraySignature != null) { type = arraySignature; } else { type = "[L" + type + ";"; } for (int i = 1; i < arrayDimensions; i++) { type = "[" + type; } return type; } } public Set<String> getAllTypeNames() { return _allTypeNamesCache.get(); } public void clearClasspathInfo() { _allTypeNamesCache.clear(); } public void remove(String fullyQualifiedName) { TypeSystem.lock(); try { _classMap.remove(fullyQualifiedName); } finally { TypeSystem.unlock(); } } public void dispose() { _module.disposeLoader(); } /** * Called in Single module mode. If the parent loader of the ModuleClassLoader is the GosuPluginContainer, * we drop the ModuleClassLoader and its parent, the GosuPluginContainer. New ones are created and assigned here. * Note, this is a giant hack among many gianter hacks that keep the old test framework floating. */ public void reassignClassLoader() { ClassLoader loader = _module.getModuleClassLoader(); if( loader.getParent() instanceof IInjectableClassLoader ) { // Dispose the GosuPluginContainer "singleton" and create a new one ((IInjectableClassLoader)loader.getParent()).dispose(); // Dispose the ModuleClassLoader and create new one, its parent will be the new GosuPluginContainer. // Note the ModuleClassLoader in the single module case does absolutely nothing as it has no classpath; it fully delegates to its parent. _module.disposeLoader(); } } public boolean hasNamespace(String namespace) { return _classPathCache.get().hasNamespace(namespace); } public Set<TypeName> getTypeNames(String namespace) { return _classPathCache.get().getTypeNames(namespace); } /** * A non-instantiable marker class to help us store misses * in this typeloader */ private static final class ClassNotFoundMarkerClass { private ClassNotFoundMarkerClass() { } } }