/* * Copyright 2013 Guidewire Software, Inc. */ package gw.lang.reflect.gs; import gw.fs.IDirectory; import gw.lang.GosuShop; import gw.lang.parser.ISymbolTable; import gw.lang.parser.ITypeUsesMap; import gw.lang.reflect.FragmentCache; import gw.lang.reflect.IType; import gw.lang.reflect.ITypeLoader; import gw.lang.reflect.RefreshRequest; import gw.lang.reflect.RefreshKind; import gw.lang.reflect.SimpleTypeLoader; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.java.IJavaType; import gw.lang.reflect.module.IModule; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; public class GosuClassTypeLoader extends SimpleTypeLoader { public static final String GOSU_CLASS_FILE_EXT = ".gs"; public static final String GOSU_ENHANCEMENT_FILE_EXT = ".gsx"; public static final String GOSU_PROGRAM_FILE_EXT = ".gsp"; public static final String GOSU_TEMPLATE_FILE_EXT = ".gst"; public static final String GOSU_RULE_EXT = ".gr"; public static final String GOSU_RULE_SET_EXT = ".grs"; public static final String[] ALL_EXTS = {GOSU_CLASS_FILE_EXT, GOSU_ENHANCEMENT_FILE_EXT, GOSU_PROGRAM_FILE_EXT, GOSU_TEMPLATE_FILE_EXT, GOSU_RULE_EXT, GOSU_RULE_SET_EXT}; public static final Set<String> ALL_EXTS_SET = new HashSet<String>(Arrays.asList(GOSU_CLASS_FILE_EXT, GOSU_ENHANCEMENT_FILE_EXT, GOSU_PROGRAM_FILE_EXT, GOSU_TEMPLATE_FILE_EXT, GOSU_RULE_EXT, GOSU_RULE_SET_EXT)); public static final Set<String> EXTENSIONS = new HashSet<String>(Arrays.asList("gs", "gsx", "gsp", "gst", "gr", "grs")); // These constants are only here because api can't depend on impl currently; they shouldn't be considered // part of the API proper public static final String BLOCK_PREFIX = "block_"; public static final String INNER_BLOCK_PREFIX = "." + BLOCK_PREFIX; public static final String BLOCK_POSTFIX = "_"; private IGosuClassRepository _repository; private IEnhancementIndex _enhancementIndex; protected Set<String> _namespaces; public static GosuClassTypeLoader getDefaultClassLoader() { return TypeSystem.getTypeLoader( GosuClassTypeLoader.class ); } public static GosuClassTypeLoader getDefaultClassLoader(IModule module) { return TypeSystem.getTypeLoader( GosuClassTypeLoader.class, module ); } public GosuClassTypeLoader( IGosuClassRepository repository ) { super( repository.getModule() ); _repository = repository; _enhancementIndex = GosuShop.createEnhancementIndex( this ); } public GosuClassTypeLoader( IModule module, IGosuClassRepository repository ) { super( module ); _repository = repository; _enhancementIndex = GosuShop.createEnhancementIndex( this ); } public IGosuClassRepository getRepository() { return _repository; } public IEnhancementIndex getEnhancementIndex() { return _enhancementIndex; } @Override public boolean isCaseSensitive() { return true; } @Override public ICompilableType getType( String strFullyQualifiedName ) { int iDollar = strFullyQualifiedName.indexOf( '$' ); if( iDollar > 0 ) { // The name has a $ in it, which is the java inner class separator, but because a gosu class can be // precompiled this name could be coming from a gosu class file, thus we need to find the toplevel // type and see if it's a gosu class (note this scenario is indicative of some external tool use-case) IType topLevelType = TypeSystem.getByFullNameIfValid( strFullyQualifiedName.substring( 0, iDollar ) ); if( topLevelType instanceof IGosuClass ) { // The toplevel type is a gosu class, so we can assume the name is a gosu inner class name strFullyQualifiedName = strFullyQualifiedName.replace( '$', '.' ); } else if( topLevelType != null ) { // Toplevel type is not a gosu class, bail quick return null; } } IGosuClass adapterClass = getAdapterClass( strFullyQualifiedName ); if( adapterClass != null ) { return adapterClass; } if( !isBlock( strFullyQualifiedName ) ) { IGosuFragment fragment = FragmentCache.instance().get( strFullyQualifiedName ); if( fragment != null ) { return fragment; } } _enhancementIndex.maybeLoadEnhancementIndex(); IType type = TypeSystem.getCompilingType( strFullyQualifiedName ); ITypeLoader typeLoader = type != null ? type.getTypeLoader() : null; if( type != null && typeLoader != this ) { return null; } if( type == null ) { return findClass( strFullyQualifiedName ); } return (IGosuClass)type; } private IGosuClass getAdapterClass( String strFullyQualifiedName ) { if( getClass() == GosuClassTypeLoader.class ) { if( strFullyQualifiedName.length() > IGosuClass.PROXY_PREFIX.length() && strFullyQualifiedName.startsWith( IGosuClass.PROXY_PREFIX ) ) { IType javaType = TypeSystem.getByFullNameIfValid( IGosuClass.ProxyUtil.getNameSansProxy( strFullyQualifiedName ) ); if( javaType instanceof IJavaType ) { IGosuClass adapterClass = ((IJavaType)javaType).getAdapterClass(); if( adapterClass == null ) { adapterClass = ((IJavaType)javaType).createAdapterClass(); } return adapterClass; } } } return null; } @Override public Set<String> getAllNamespaces() { if( _namespaces == null ) { try { _namespaces = TypeSystem.getNamespacesFromTypeNames( getAllTypeNames(), new HashSet<String>() ); _namespaces.add( "Libraries" ); } 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); } } _repository.namespaceRefreshed(namespace, dir, kind); } @Override public URL getResource(String name) { return _repository.findResource(name); } @Override public Set<String> computeTypeNames() { return _repository.getAllTypeNames(getAllExtensions()); } @Override public void refreshedImpl() { _namespaces = null; _repository.typesRefreshed(null); } @Override public List<String> getHandledPrefixes() { return Collections.emptyList(); } @Override public boolean handlesNonPrefixLoads() { return true; } public IGosuClass makeNewClass( ISourceFileHandle sourceFile ) { return makeNewClass( sourceFile, null ); } public IGosuClass makeNewClass( ISourceFileHandle sourceFile, ISymbolTable programSymTable ) { switch( sourceFile.getClassType() ) { case Class: case Interface: case Structure: case Enum: return GosuShop.createClass( sourceFile.getTypeNamespace(), sourceFile.getRelativeName(), this, sourceFile, getTypeUsesMap() ); case Enhancement: return GosuShop.createEnhancement( sourceFile.getTypeNamespace(), sourceFile.getRelativeName(), this, sourceFile, getTypeUsesMap() ); case Program: return GosuShop.createProgram( sourceFile.getTypeNamespace(), sourceFile.getRelativeName(), this, sourceFile, getTypeUsesMap(), programSymTable ); case Template: return GosuShop.createTemplate( sourceFile.getTypeNamespace(), sourceFile.getRelativeName(), this, sourceFile, getTypeUsesMap(), programSymTable ); case Eval: return GosuShop.createProgramForEval( sourceFile.getTypeNamespace(), sourceFile.getRelativeName(), this, sourceFile, getTypeUsesMap(), programSymTable ); default: throw new IllegalStateException( "Unhandled class type: " + sourceFile.getClassType() ); } } private IGosuClass getBlockType( String strName ) { IGosuClass classInternal = null; if( isBlock( strName ) ) { try { String strippedName = strName.substring( 0, strName.length() - BLOCK_POSTFIX.length() ); int iStr = strippedName.lastIndexOf( INNER_BLOCK_PREFIX ); String indexStr = strippedName.substring( iStr + INNER_BLOCK_PREFIX.length(), strippedName.length() ); int i = Integer.parseInt( indexStr ); String enclosingClassStr = strippedName.substring( 0, iStr ); IType type = getBlockType( enclosingClassStr ); if( type == null ) { type = TypeSystem.getByFullNameIfValid( enclosingClassStr.replace( '$', '.' ), getModule() ); } // this module check is to make sure that the block class is in the same module as the parent class // otherwise the Rule loader loads block classes! if( type instanceof ICompilableType && type.getTypeLoader().getModule() == _module ) { classInternal = ((ICompilableType)type).getBlock( i ); } } catch( NumberFormatException e ) { //ignore } catch( IndexOutOfBoundsException e ) { //ignore } } return classInternal; } private boolean isBlock( String strName ) { return strName.endsWith( BLOCK_POSTFIX ) && strName.contains( INNER_BLOCK_PREFIX ); } private IGosuClass findClass( String strQualifiedClassName ) { IGosuClass blockType = getBlockType( strQualifiedClassName ); if( blockType != null ) { return blockType; } ISourceFileHandle sourceFile = _repository.findClass( strQualifiedClassName, getAllExtensions()); if( sourceFile == null || !isValidSourceFileHandle( sourceFile ) || !sourceFile.isValid() || !strQualifiedClassName.endsWith( sourceFile.getRelativeName() ) ) { return null; } IGosuClass gsClass=null; if( sourceFile.getParentType() != null ) { // It's an inner class final IType type = TypeSystem.getByFullNameIfValid(sourceFile.getParentType()); //we have to check instance of type as it can return JavaType //this happens when you have Gosu class which has the same name as any java package //in this case you shadow entire java package with gosu class if (type instanceof IGosuClass) { IGosuClass enclosingType = (IGosuClass) type; gsClass = enclosingType.getInnerClass(sourceFile.getRelativeName()); } } else { // It's a top-level class gsClass = makeNewClass( sourceFile ); } return gsClass; } protected boolean isValidSourceFileHandle( ISourceFileHandle sourceFile ) { return sourceFile.getClassType().isGosu(); } protected String[] getAllExtensions() { return ALL_EXTS; } protected ITypeUsesMap getTypeUsesMap() { return null; } @Override public void refreshedTypesImpl(RefreshRequest request) { _repository.typesRefreshed(request); _enhancementIndex.refreshedTypes(request); } public boolean shouldKeepDebugInfo( IGosuClass gsClass ) { return gsClass.shouldKeepDebugInfo(); } @Override public Set<String> getExtensions() { return EXTENSIONS; } @Override public Set<TypeName> getTypeNames(String namespace) { return _repository.getTypeNames(namespace, ALL_EXTS_SET, this); } @Override public boolean hasNamespace(String namespace) { return _repository.hasNamespace(namespace) > 0; } }