/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.parser;
import gw.config.CommonServices;
import gw.fs.IDirectory;
import gw.fs.IFile;
import gw.fs.IResource;
import gw.lang.reflect.IDefaultTypeLoader;
import gw.lang.reflect.IExtendedTypeLoader;
import gw.lang.reflect.IMetaType;
import gw.lang.reflect.INamespaceType;
import gw.lang.reflect.IType;
import gw.lang.reflect.ITypeLoader;
import gw.lang.reflect.ITypeRef;
import gw.lang.reflect.ITypeRefFactory;
import gw.lang.reflect.IUninitializableTypeLoader;
import gw.lang.reflect.RefreshKind;
import gw.lang.reflect.RefreshRequest;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.GosuClassTypeLoader;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.gs.IGosuClassRepository;
import gw.lang.reflect.gs.IGosuObject;
import gw.lang.reflect.gs.TypeName;
import gw.lang.reflect.java.JavaTypes;
import gw.lang.reflect.module.IClassPath;
import gw.lang.reflect.module.IModule;
import gw.util.GosuClassUtil;
import gw.util.Pair;
import gw.util.Predicate;
import gw.util.cache.FqnCacheNode;
import gw.util.cache.WeakFqnCache;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*/
public class ModuleTypeLoader implements ITypeLoaderStackInternal {
private static final IType CACHE_MISS = ErrorType.getInstance( "cache-miss-type" );
// The corresponding module
private IModule _module;
// Type loader data structures
private List<ITypeLoader> _globalStack;
private DefaultTypeLoader _defaultTypeLoader;
private Map<String, ITypeLoader> _loadersByPrefix;
// Type system caches
private WeakFqnCache<IType> _typesByName;
private Map<String, IType> _namespaceTypesByName; // A case-Sensitive map of names to namespace types
//## todo: remove this pos
private Map<String, IType> _typesByCaseInsensitiveName; // A case-Insensitive map of names to intrinsic types
private ITypeRefFactory _typeRefFactory;
public ModuleTypeLoader( IModule module, List<ITypeLoader> loaderStack )
{
_module = module;
initMaps();
_globalStack.addAll(loaderStack);
_typeRefFactory = module.getModuleTypeLoader().getTypeRefFactory();
for (ITypeLoader typeLoader : loaderStack) {
List<String> handledPrefixes = typeLoader.getHandledPrefixes();
for( int i = 0; i < handledPrefixes.size(); i++ )
{
String handledPrefix = handledPrefixes.get( i );
_loadersByPrefix.put( handledPrefix, typeLoader );
}
}
}
private void initMaps() {
_globalStack = new ArrayList<ITypeLoader>();
_loadersByPrefix = new HashMap<String, ITypeLoader>();
_typesByName = new WeakFqnCache<IType>();
_namespaceTypesByName = new HashMap<String, IType>();
_typesByCaseInsensitiveName = new HashMap<String, IType>();
}
public ModuleTypeLoader( IModule module, DefaultTypeLoader defaultTypeLoader)
{
_module = module;
_defaultTypeLoader = defaultTypeLoader;
_typeRefFactory = new TypeRefFactory();
reset();
}
public void reset()
{
initMaps();
_globalStack.add( _defaultTypeLoader );
_typeRefFactory.clearCaches();
}
@Override
public IModule getModule()
{
return _module;
}
@Override
public List<ITypeLoader> getTypeLoaders()
{
return new ArrayList<ITypeLoader>( _globalStack );
}
public void pushTypeLoader( ITypeLoader typeLoader )
{
TypeSystem.lock();
try
{
clearErrorTypes();
int position = -1;
for( int i = 0; i < _globalStack.size(); i++ )
{
ITypeLoader loaderOnStack = _globalStack.get( i );
if( loaderOnStack.getClass() == typeLoader.getClass() )
{
position = i;
}
}
if( position == -1 )
{
_globalStack.add( 0, typeLoader );
}
else
{
_globalStack.set( position, typeLoader );
}
List<String> handledPrefixes = typeLoader.getHandledPrefixes();
for( int i = 0; i < handledPrefixes.size(); i++ )
{
String handledPrefix = handledPrefixes.get( i );
_loadersByPrefix.put( handledPrefix, typeLoader );
}
CommonServices.getEntityAccess().getLogger().debug("TypeLoader added: " + GosuClassUtil.getShortClassName(typeLoader.getClass()));
}
finally
{
TypeSystem.unlock();
}
CommonServices.getEntityAccess().getLogger().debug( "TypeLoader added: " + GosuClassUtil.getShortClassName( typeLoader.getClass() ) );
}
@Override
public void clearErrorTypes()
{
TypeSystem.lock();
try
{
removeMissesAndErrorsFromMainCache();
removeMissesAndErrors( _typesByCaseInsensitiveName.values() );
removeMissesAndErrors( _namespaceTypesByName.values() );
}
finally
{
TypeSystem.unlock();
}
}
private void removeMissesAndErrorsFromMainCache() {
_typesByName.visitNodeDepthFirst(new Predicate<FqnCacheNode>() {
public boolean evaluate(FqnCacheNode node) {
WeakReference<IType> ref = (WeakReference<IType>) node.getUserData();
if (ref != null) {
IType type = ref.get();
if (type == CACHE_MISS || type instanceof ErrorType) {
node.delete();
}
}
return true;
}
});
}
private void removeMissesAndErrors( Collection<IType> types ) {
for( Iterator<IType> iterator = types.iterator(); iterator.hasNext(); )
{
IType type = iterator.next();
if( type == null ||
type instanceof ErrorType ||
type == CACHE_MISS )
{
iterator.remove();
}
}
}
private void clearCaches()
{
_typesByName.clear();
_namespaceTypesByName.clear();
_typesByCaseInsensitiveName.clear();
}
public void removeTypeLoader( Class<? extends ITypeLoader> loaderType )
{
TypeSystem.lock();
try
{
ITypeLoader typeLoader = getTypeLoader( loaderType );
if( typeLoader != null )
{
_globalStack.remove( typeLoader );
// Removing a type loader must trigger a type system reset
refreshed();
List<String> handledPrefixes = typeLoader.getHandledPrefixes();
for( String handledPrefix : handledPrefixes )
{
_loadersByPrefix.remove( handledPrefix );
}
CommonServices.getEntityAccess().getLogger().debug("TypeLoader removed: " + GosuClassUtil.getShortClassName(typeLoader.getClass()));
}
}
finally
{
TypeSystem.unlock();
}
}
public void clearFromCaches( RefreshRequest request)
{
TypeSystem.lock();
try
{
for (String fullyQualifiedTypeName : request.types) {
clearFromCaches(fullyQualifiedTypeName);
}
clearNamespaces(request); // Clear namespace types that might be affected.
DefaultTypeLoader defaultTypeLoader = getTypeLoader(DefaultTypeLoader.class);
if (defaultTypeLoader != null) {
defaultTypeLoader.clearMisses();
}
// This is vital for runtime where Java types are redefined in via debugger
NewIntrospector.flushCaches();
}
finally
{
TypeSystem.unlock();
}
}
private void clearNamespaces(RefreshRequest request) {
// Type could be added, removed or renamed.
for (String fullyQualifiedTypeName : request.types) {
int pos = fullyQualifiedTypeName.lastIndexOf('.');
if (pos != -1) {
String pkgName = fullyQualifiedTypeName.substring(0, pos);
_namespaceTypesByName.remove(pkgName);
}
}
}
public Set<TypeName> getTypeNames(String namespace) {
Set<TypeName> names = new HashSet<TypeName>();
for (ITypeLoader loader : _globalStack) {
names.addAll(loader.getTypeNames(namespace));
}
return names;
}
@Override
public <T extends ITypeLoader> T getTypeLoader( Class<? extends T> loaderType )
{
TypeSystem.lock();
try
{
for( ITypeLoader loader : _globalStack )
{
// Note, this MUST be equals(). It must be an exact match, not an assignable match.
if( loader.getClass().equals( loaderType ) )
{
//noinspection unchecked
return (T)loader;
}
}
return null;
}
finally
{
TypeSystem.unlock();
}
}
@Override
public INamespaceType getNamespaceType( String strNamespace )
{
// First, look for the type in the map by name
IType foundType = _namespaceTypesByName.get(strNamespace);
if( foundType == null )
{
TypeSystem.lock();
try
{
// Look it up again, to make sure no concurrent write has happened
foundType = _namespaceTypesByName.get( strNamespace );
// If it's not found, then go ahead and try to load it from the type loader stacks
if( foundType == null )
{
foundType = loadNamespaceAndCacheResult(strNamespace);
}
}
finally
{
TypeSystem.unlock();
}
}
if( foundType == CACHE_MISS )
{
return null;
}
else if( foundType instanceof INamespaceType )
{
return (INamespaceType)foundType;
}
else
{
return null;
}
}
@Override
public IType getIntrinsicTypeFromObject( Object object )
{
IType type = null;
int iLoaders = _globalStack.size();
for( int i = 0; i < iLoaders; i++ ) //!! don't use iterator here; concurrency issue; we don't want to lock either
{
ITypeLoader loader;
try
{
loader = _globalStack.get( i );
}
catch( IndexOutOfBoundsException e )
{
break;
}
if( loader instanceof IExtendedTypeLoader )
{
if(loader instanceof IGosuObject) {
if(GosuClassCompilingStack.getCompilingType(((IGosuObject)loader).getIntrinsicType().getName()) != null) {
continue;
}
}
type = ((IExtendedTypeLoader)loader).getIntrinsicTypeFromObject( object );
if( type != null )
{
break;
}
}
}
if( type == null && _defaultTypeLoader != null)
{
type = _defaultTypeLoader.getIntrinsicTypeFromObject( object );
}
if( type instanceof IMetaType && !((IMetaType)type).isLiteral() )
{
// Ensure we return the "literal" version of the metatype; it has both the metatype features and the type's static features.
type = MetaType.getLiteral( ((IMetaType)type).getType() );
}
return type;
}
public IType getTypeByFullNameIfValid( String fullyQualifiedName, boolean skipJava )
{
// strip off all trailing array brackets "[]"
String fqnNoArrays = stripArrayBrackets(fullyQualifiedName);
// First, look for the type in the map by name
IType foundType = findInCache( fqnNoArrays );
if( foundType == null )
{
TypeSystem.lock();
try
{
// Look it up again, to make sure no concurrent write has happened
foundType = findInCache( fqnNoArrays );
// If still null, check in the case-insensitive map and add it to the case-sensitive version if necessary
if( foundType == null )
{
foundType = findInCaseInsenstiveCache( fqnNoArrays );
if( foundType != null )
{
_typesByName.add( fqnNoArrays, foundType );
}
}
// If it's not found, then go ahead and try to load it from the type loader stacks
if( foundType == null )
{
foundType = loadTypeAndCacheResult(fqnNoArrays, skipJava);
}
}
finally
{
TypeSystem.unlock();
}
}
if( foundType == CACHE_MISS )
{
return null;
}
// restore arrays
if( foundType != null )
{
int numArrays = (fullyQualifiedName.length() - fqnNoArrays.length()) / 2;
for( int i = 0; i < numArrays; i++ )
{
foundType = foundType.getArrayType();
}
}
return foundType;
}
private IType findInCache( String fqnNoArrays )
{
IType foundType = _typesByName.get(fqnNoArrays);
if( foundType == null )
{
foundType = _typeRefFactory.get(fqnNoArrays);
}
if( foundType instanceof ITypeRef && ((ITypeRef)foundType)._shouldReload() )
{
// The proxied type is stale, force it to reload
clearFromCaches( fqnNoArrays );
foundType = null;
}
if (TypeSystem.isDeleted(foundType)) {
foundType = null;
}
return foundType;
}
private IType findInCaseInsenstiveCache( String fqnNoArrays )
{
IType foundType = _typesByCaseInsensitiveName.get( fqnNoArrays );
if( foundType instanceof ITypeRef && ((ITypeRef)foundType)._shouldReload() )
{
// The proxied type is stale, force it to reload
foundType = null;
}
return foundType;
}
/**
* \
*
* @param name The name
*
* @return The result of stripping all trailing occurrences of array brackets ("[]")
* from <code>name</code>. Examples:
* <pre>
* entity.Coverage => entity.Coverage
* entity.Coverage[] => entity.Coverage
* entity.Coverage[][][] => entity.Coverage
* </pre>
*/
static String stripArrayBrackets( String name )
{
if( name == null )
{
return "";
}
int checkPos = name.length();
while( checkPos > 2 && name.charAt( checkPos - 2 ) == '[' && name.charAt( checkPos - 1 ) == ']' )
{
checkPos -= 2;
}
assert checkPos <= name.length();
return name.substring( 0, checkPos );
}
private IType loadTypeAndCacheResult(String fullyQualifiedName, boolean skipJava)
{
Pair<IType, ITypeLoader> pair;
TypeSystem.pushModule( getModule() );
try
{
pair = loadType(fullyQualifiedName, skipJava);
}
finally
{
TypeSystem.popModule( getModule() );
}
IType type;
if( pair != null )
{
type = cacheType(fullyQualifiedName, pair);
}
else if( !skipJava )
{
type = cacheType(fullyQualifiedName, new Pair<IType, ITypeLoader>(CACHE_MISS, null));
}
else
{
// Type loading was initiated through class loader, so we skipped Java type loader. Don't cache -- type could
// be valid Java type. Eventually, we will cache it.
type = null;
}
return type;
}
private IType loadNamespaceAndCacheResult(String fullyQualifiedName)
{
IType type = loadNamespaceType(fullyQualifiedName);
if( type != null )
{
type = cacheNamespace(fullyQualifiedName, type);
}
else
{
type = cacheNamespace(fullyQualifiedName, CACHE_MISS);
}
return type;
}
@Override
public void refreshed()
{
TypeSystem.lock();
try
{
clearCaches();
for (ITypeLoader loader : _globalStack) {
loader.refreshed();
}
}
finally
{
TypeSystem.unlock();
}
}
private void clearFromCaches( String fullyQualifiedTypeName ) {
_typesByName.remove(fullyQualifiedTypeName);
_typesByCaseInsensitiveName.remove( fullyQualifiedTypeName );
if(fullyQualifiedTypeName.endsWith(IClassPath.PLACEHOLDER_FOR_PACKAGE)) {
_namespaceTypesByName.remove(fullyQualifiedTypeName.substring(0, fullyQualifiedTypeName.length() - IClassPath.PLACEHOLDER_FOR_PACKAGE.length() - 1));
}
}
private Pair<IType, ITypeLoader> loadType(String fullyQualifiedName, boolean skipJava)
{
IType type = TypeLoaderAccess.instance().getDefaultType(fullyQualifiedName);
if (type != null) {
return new Pair<IType, ITypeLoader>(type, type.getTypeLoader());
}
int dotIdx = fullyQualifiedName.indexOf('.');
if (dotIdx > 0) {
if (fullyQualifiedName.indexOf('.', dotIdx + 1) < 0) {
String prefix = fullyQualifiedName.substring(0, dotIdx);
ITypeLoader typeLoader = _loadersByPrefix.get(prefix);
if (typeLoader != null && typeLoader.isInited()) {
return new Pair<IType, ITypeLoader>(typeLoader.getType(fullyQualifiedName), typeLoader);
}
}
}
for( ITypeLoader loader : _globalStack )
{
if(loader instanceof IGosuObject) {
if(((IGosuObject)loader).getIntrinsicType().getName().equals(fullyQualifiedName) ||
GosuClassCompilingStack.getCompilingType(((IGosuObject)loader).getIntrinsicType().getName()) != null) {
continue;
}
}
if( loader.handlesNonPrefixLoads() && loader.isInited() )
{
// Only look through loaders that can do non-prefix loading
if (loader instanceof IDefaultTypeLoader && skipJava) {
// Forbid going back into classloaders world (for example, we are loading Gosu type through URL handler)
continue;
}
type = loader.getType( fullyQualifiedName );
if( type != null )
{
return new Pair<IType, ITypeLoader>(type, loader);
}
}
}
return null;
}
private IType loadNamespaceType(String namespace)
{
for (int i = 0; i < _globalStack.size(); i++) {
ITypeLoader loader = _globalStack.get( i );
if(loader instanceof IGosuObject) {
if(GosuClassCompilingStack.getCompilingType(((IGosuObject)loader).getIntrinsicType().getName()) != null) {
continue;
}
}
//noinspection SuspiciousMethodCalls
if ( loader.hasNamespace( namespace ) || isProxyType( namespace, loader ) ) {
return new NamespaceType( namespace, getModule() );
}
}
return null;
}
private boolean isProxyType( String fullyQualifiedName, ITypeLoader loader )
{
if( (loader == _defaultTypeLoader ||
loader.getClass() == GosuClassTypeLoader.class) &&
IGosuClass.ProxyUtil.isProxyClass( fullyQualifiedName ) )
{
String minusProxy = fullyQualifiedName.substring( IGosuClass.PROXY_PREFIX.length()+1 );
if( loader.hasNamespace(minusProxy) )
{
return true;
}
}
return false;
}
@Override
public List<ITypeLoader> getTypeLoaderStack()
{
return _globalStack;
}
/**
* Adds the type to the cache.
*/
private IType cacheType(String name, Pair<IType, ITypeLoader> pair)
{
if( pair != null )
{
IType type = pair.getFirst();
// We have to make sure we aren't replacing an existing type so we obey the return from the put.
IType oldType = _typesByName.get(name);
if( oldType != null && oldType != CACHE_MISS )
{
return oldType;
}
_typesByName.add( name, type );
ITypeLoader typeLoader = pair.getSecond();
if( typeLoader != null && !typeLoader.isCaseSensitive() )
{
_typesByCaseInsensitiveName.put( name, type);
}
}
return pair != null ? pair.getFirst() : null;
}
private IType cacheNamespace(String name, IType type)
{
if( type != null )
{
// We have to make sure we aren't replacing an existing type so we obey the return from the put.
IType oldType = _namespaceTypesByName.put( name, type );
if( oldType != null && oldType != CACHE_MISS )
{
_namespaceTypesByName.put( name, oldType );
return oldType;
}
}
return type;
}
@Override
public ITypeRefFactory getTypeRefFactory()
{
return _typeRefFactory;
}
public void uninitializeTypeLoaders() {
for (ITypeLoader typeLoader : _globalStack) {
if (typeLoader instanceof IUninitializableTypeLoader) {
((IUninitializableTypeLoader)typeLoader).uninitialize();
}
}
}
public DefaultTypeLoader getDefaultTypeLoader() {
return _defaultTypeLoader;
}
public String toString() {
return _module.toString();
}
public void shutdown() {
for (ITypeLoader typeLoader : _globalStack) {
typeLoader.shutdown();
}
}
public boolean refresh(IResource file, String typeName, RefreshKind refreshKind) {
TypeSystem.pushModule(getModule());
TypeSystem.lock();
try {
if (file instanceof IFile) {
return refreshFile((IFile) file, typeName, refreshKind);
} else if (file instanceof IDirectory) {
return refreshDirectory((IDirectory) file, refreshKind);
} else {
throw new RuntimeException("Unknown resource: " + file);
}
} finally {
TypeSystem.unlock();
TypeSystem.popModule(getModule());
}
}
private boolean refreshDirectory(IDirectory directory, RefreshKind kind) {
boolean processed = false;
// refresh the directory itself
for (ITypeLoader typeLoader : _globalStack) {
if (typeLoader.handlesDirectory(directory)) {
String namespace = typeLoader.getNamespaceForDirectory(directory);
if (namespace != null) {
refreshNamespaceCaches(namespace, typeLoader, kind);
typeLoader.refreshedNamespace(namespace, directory, kind);
}
processed = true;
}
}
// refresh directory content
for (IFile file : directory.listFiles()) {
processed |= refreshFile(file, null, kind);
}
for (IDirectory dir : directory.listDirs()) {
processed |= refreshDirectory(dir, kind);
}
return processed;
}
private void refreshNamespaceCaches( String namespace, ITypeLoader typeLoader, RefreshKind kind ) {
if (kind == RefreshKind.CREATION) {
_namespaceTypesByName.remove(namespace);
clearErrorTypes();
} else if (kind == RefreshKind.DELETION) {
if( typeLoader.getClass() == GosuClassTypeLoader.class ) {
IGosuClassRepository repository = ((GosuClassTypeLoader)typeLoader).getRepository();
if( repository.hasNamespace( namespace ) == 1 )
_namespaceTypesByName.put( namespace, CACHE_MISS );
}
}
}
private boolean refreshFile(IFile file, String typeName, RefreshKind kind) {
boolean processed = false;
for (ITypeLoader typeLoader : _globalStack) {
if (typeLoader.handlesFile(file)) {
String[] types;
if (typeName != null) {
types = new String[] {typeName};
} else {
types = typeLoader.getTypesForFile(file);
}
kind = typeLoader.refreshedFile(file, types, kind);
if (types != null && types.length != 0) {
RefreshRequest refreshRequest = new RefreshRequest(file, types, typeLoader, kind);
TypeLoaderAccess.instance().refreshTypes(refreshRequest);
}
processed = true;
}
}
return processed;
}
public IType getCachedType(String fqn) {
IType foundType = findInCache( fqn );
if (foundType == null) {
foundType = findInCaseInsenstiveCache(fqn);
}
return foundType;
}
}