/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.parser; import gw.config.CommonServices; import gw.fs.IDirectory; import gw.internal.gosu.coercer.FunctionToInterfaceClassGenerator; import gw.internal.gosu.compiler.GosuClassLoader; import gw.internal.gosu.parser.java.classinfo.JavaSourceClass; import gw.lang.reflect.IDefaultTypeLoader; import gw.lang.reflect.IErrorType; import gw.lang.reflect.IExtendedTypeLoader; import gw.lang.reflect.IType; import gw.lang.reflect.RefreshKind; import gw.lang.reflect.RefreshRequest; import gw.lang.reflect.SimpleTypeLoader; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.GosuClassPathThing; import gw.lang.reflect.gs.IGosuClassLoader; import gw.lang.reflect.gs.IGosuObject; import gw.lang.reflect.gs.ISourceFileHandle; import gw.lang.reflect.gs.TypeName; import gw.lang.reflect.java.IJavaClassInfo; import gw.lang.reflect.java.IJavaClassType; import gw.lang.reflect.java.IJavaType; import gw.lang.reflect.java.JavaTypes; import gw.lang.reflect.java.asm.AsmClass; import gw.lang.reflect.module.IModule; import java.net.URL; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class DefaultTypeLoader extends SimpleTypeLoader implements IExtendedTypeLoader, IDefaultTypeLoader { private static final boolean USE_ASM_LOADER = true; private ClassCache _classCache; private IGosuClassLoader _gosuClassLoader; //## todo: use a ConcurrentWeakValueHashMap here? private Map<String, IJavaClassInfo> _classInfoCache = new ConcurrentHashMap<String, IJavaClassInfo>(1000); protected Set<String> _namespaces; public static DefaultTypeLoader instance(IModule module) { if (module == null) { module = ExecutionEnvironment.instance().getJreModule(); } return module.getModuleTypeLoader().getTypeLoader(DefaultTypeLoader.class); } public DefaultTypeLoader(final IModule module) { super(module); _classCache = new ClassCache(module); } public static DefaultTypeLoader instance() { IModule module = TypeSystem.getCurrentModule(); return instance(module); } @Override public IType getType(String fullyQualifiedName) { IJavaClassInfo classInfo = getJavaClassInfo( fullyQualifiedName ); if (classInfo != null) { return JavaType.create(classInfo, this); } else { return null; } } public IJavaType getInnerType(String fqn) { IType cachedType = ((ModuleTypeLoader) _module.getModuleTypeLoader()).getCachedType( fqn ); if (cachedType != null && !(cachedType instanceof IErrorType)) { return (IJavaType) cachedType; } else { return (IJavaType) getType(fqn); } } public IJavaClassInfo getJavaClassInfo(String fullyQualifiedName) { if (fullyQualifiedName.startsWith("[")) { throw new IllegalArgumentException("Cannot call getJavaClassInfo with a raw array descriptor"); } if (!TypeSystem.isSingleModuleMode() && _module.equals(TypeSystem.getGlobalModule())) { return null; } // strip off all trailing array brackets "[]" String fqnNoArrays = ModuleTypeLoader.stripArrayBrackets(fullyQualifiedName); IJavaClassInfo result = _classInfoCache.get(fqnNoArrays); if (result == null) { result = resolveJavaClassInfo(fqnNoArrays); if (result == null) { result = IJavaClassInfo.NULL_TYPE; } _classInfoCache.put(fqnNoArrays, result); } if( result != IJavaClassType.NULL_TYPE ) { int numArrays = (fullyQualifiedName.length() - fqnNoArrays.length()) / 2; for( int i = 0; i < numArrays; i++ ) { result = result.getArrayType(); } } return result == IJavaClassInfo.NULL_TYPE ? null : result; } @Override public IJavaClassInfo getJavaClassInfoForClassDirectly(Class clazz, IModule module) { return new ClassJavaClassInfo(clazz, module); } public IJavaClassInfo getJavaClassInfo( Class aClass, IModule gosuModule ) { if( !TypeSystem.isSingleModuleMode() && _module.equals( TypeSystem.getGlobalModule() ) ) { return null; } String fullyQualifiedName = aClass.getName().replace('$', '.'); IJavaClassInfo result = _classInfoCache.get( fullyQualifiedName ); if( result == null ) { result = new ClassJavaClassInfo( aClass, gosuModule ); _classInfoCache.put( fullyQualifiedName, result ); } return result == IJavaClassInfo.NULL_TYPE ? null : result; } public IJavaClassInfo getJavaClassInfo( AsmClass aClass, IModule gosuModule ) { if( !TypeSystem.isSingleModuleMode() && _module.equals( TypeSystem.getGlobalModule() ) ) { return null; } String fullyQualifiedName = aClass.getName().replace('$', '.'); IJavaClassInfo result = _classInfoCache.get( fullyQualifiedName ); if( result == null ) { result = new AsmClassJavaClassInfo( aClass, gosuModule ); _classInfoCache.put( fullyQualifiedName, result ); } return result == IJavaClassInfo.NULL_TYPE ? null : result; } public IJavaClassInfo resolveJavaClassInfo(String fullyQualifiedName) { if (!CommonServices.getPlatformHelper().isInIDE()) { return getByClass(fullyQualifiedName, _module, _module); } ISourceFileHandle fileHandle = getSouceFileHandle( fullyQualifiedName ); if (fileHandle == null) { return getByClass(fullyQualifiedName, _module, _module); } if( fileHandle.getParentType() != null && !fileHandle.getParentType().isEmpty() ) { String parentType = fileHandle.getTypeNamespace(); IJavaClassInfo parentClassInfo = getJavaClassInfo(parentType); if (parentClassInfo == null) { return null; } IJavaClassInfo[] declaredClasses = parentClassInfo.getDeclaredClasses(); IJavaClassInfo inner = null; for (IJavaClassInfo declaredClass : declaredClasses) { String name = declaredClass.getName(); // cache all inner classes now if (!_classInfoCache.containsKey(name)) { _classInfoCache.put(name, declaredClass); } //## todo: these names should be consistent if (fullyQualifiedName.equals(name) || name.replace( '$', '.').equals( fullyQualifiedName ) ) { inner = declaredClass; } } return inner; } return JavaSourceClass.createTopLevel(fileHandle, _module); } @Override public ISourceFileHandle getSouceFileHandle(String qualifiedName) { ISourceFileHandle aClass = _module.getFileRepository().findClass(qualifiedName, EXTENSIONS_ARRAY); if (aClass == null || !aClass.getClassType().isJava()) { return null; } return aClass; } private IJavaClassInfo getByClass( String className, IModule lookupModule, IModule actualModule ) { DefaultTypeLoader loader = (DefaultTypeLoader)lookupModule.getTypeLoaders( IDefaultTypeLoader.class ).get( 0 ); if( USE_ASM_LOADER && CommonServices.getPlatformHelper().isInIDE() ) { AsmClass theClass = loader.loadAsmClass( className ); if( theClass == null ) { return null; } return getJavaClassInfo( theClass, actualModule ); } else { Class theClass = loader.loadClass( className ); if( theClass == null ) { return null; } return getJavaClassInfo( theClass, actualModule ); } } @Override public IType getIntrinsicTypeFromObject(Object object) { if (object == null) { return null; } if (object instanceof IGosuObject) { if (object instanceof AbstractTypeRef) { object = ((AbstractTypeRef) object)._getType(); } return ((IGosuObject) object).getIntrinsicType(); } if (object instanceof IType) { return MetaType.get((IType) object); } return TypeSystem.get(object.getClass()); // Call allllll the way back through the stack if this is a Class; } @Override public Set<String> computeTypeNames() { Set<String> allTypeNames = _classCache.getAllTypeNames(); allTypeNames.addAll(_module.getFileRepository().getAllTypeNames(DOT_JAVA_EXTENSION)); return allTypeNames; } @Override public URL getResource(String name) { return getGosuClassLoader().getActualLoader().getResource(name); } @Override public void refreshedTypesImpl(RefreshRequest request) { for (String fullyQualifiedTypeName : request.types) { _classCache.remove(fullyQualifiedTypeName); _classInfoCache.remove(fullyQualifiedTypeName); } _module.getFileRepository().typesRefreshed( request ); } @Override public boolean isCaseSensitive() { return true; } @Override public List<String> getHandledPrefixes() { return Collections.emptyList(); } @Override public boolean handlesNonPrefixLoads() { return true; } public void refreshedImpl() { JavaType.unloadTypes(); if (TypeSystem.isSingleModuleMode()) { _classCache.clearClasspathInfo(); } else { _classCache.dispose(); _classCache = new ClassCache(getModule()); dumpGosuClassLoader(); } _namespaces = null; _classInfoCache.clear(); _module.getFileRepository().typesRefreshed( null ); JavaTypes.flushCache(); } public void clearMisses() { Iterator<Map.Entry<String,IJavaClassInfo>> iterator = _classInfoCache.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, IJavaClassInfo> entry = iterator.next(); if (entry.getValue() == IJavaClassInfo.NULL_TYPE) { iterator.remove(); } } } public Class loadClass(String className) { return _classCache.loadClass( className ); } public AsmClass loadAsmClass(String className) { return _classCache.loadAsmClass( className ); } public IGosuClassLoader getGosuClassLoader() { if (_gosuClassLoader == null) { _gosuClassLoader = new GosuClassLoader(_module.getModuleClassLoader()); GosuClassPathThing.init(); } return _gosuClassLoader; } void dumpGosuClassLoader() { if (_gosuClassLoader != null) { _gosuClassLoader.dumpAllClasses(); // The FunctionToInterfaceClassGenerator holds onto Class objects that could be from // the GosuClassLoader; if we don't clear its map, we'll end up leaking the old GosuClassLoader FunctionToInterfaceClassGenerator.clearCachedClasses(); // The module classloader should be a PluginContainer, which should be new at this point; the old one should be disposed if( !haveWeRecreatedTheModuleLoader() ) { // Gosu's classloader hasn't been recreated, which implies the _classCache was not recreated, // which means we have to manually dump and recreate the plugin loader and the module loader. _classCache.reassignClassLoader(); } _gosuClassLoader.assignParent( _module.getModuleClassLoader() ); GosuClassPathThing.init(); } } private boolean haveWeRecreatedTheModuleLoader() { ClassLoader gosusLoader = _gosuClassLoader.getActualLoader(); ClassLoader csr = _module.getModuleClassLoader(); while( csr != null ) { if( csr == gosusLoader ) { return false; } csr = csr.getParent(); } return true; } @Override public Set<String> getExtensions() { return EXTENSIONS; } @Override public boolean hasNamespace(String namespace) { return _module.getFileRepository().hasNamespace(namespace) > 0 || _classCache.hasNamespace(namespace); } @Override public Set<String> getAllNamespaces() { if (_namespaces == null) { try { _namespaces = TypeSystem.getNamespacesFromTypeNames(getAllTypeNames(), new HashSet<String>()); } catch (NullPointerException e) { //!! hack to get past dependency issue with tests return Collections.emptySet(); } } return _namespaces; } @Override public void refreshedNamespace(String namespace, IDirectory dir, RefreshKind kind) { if (_namespaces != null) { if (kind == RefreshKind.CREATION) { _namespaces.add(namespace); } else if (kind == RefreshKind.DELETION) { _namespaces.remove(namespace); } } } @Override public Set<TypeName> getTypeNames(String namespace) { Set<TypeName> names = new HashSet<TypeName>(); names.addAll(_module.getFileRepository().getTypeNames(namespace, Collections.singleton(".java"), this)); names.addAll(_classCache.getTypeNames(namespace)); return names; } }