/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.parser; import gw.internal.ext.org.antlr.runtime.ANTLRInputStream; import gw.internal.ext.org.antlr.runtime.CharStream; import gw.internal.ext.org.antlr.runtime.Token; import gw.internal.gosu.parser.java.JavaLexer; import gw.lang.parser.CICS; import gw.lang.parser.Keyword; import gw.lang.parser.TypeVarToTypeMap; import gw.lang.reflect.IErrorType; import gw.lang.reflect.IMethodInfo; import gw.lang.reflect.IParameterInfo; import gw.lang.reflect.IPropertyInfo; import gw.lang.reflect.IType; import gw.lang.reflect.ITypeRef; import gw.lang.reflect.RefreshKind; import gw.lang.reflect.RefreshRequest; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.ClassType; import gw.lang.reflect.gs.GosuClassTypeLoader; import gw.lang.reflect.gs.IEnhancementIndex; import gw.lang.reflect.gs.IGenericTypeVariable; import gw.lang.reflect.gs.IGosuClassRepository; import gw.lang.reflect.gs.IGosuEnhancement; import gw.lang.reflect.gs.ISourceFileHandle; import gw.util.GosuObjectUtil; import gw.util.StreamUtil; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** */ public class EnhancementIndex implements IEnhancementIndex { // Controls some local debugging messages that can be used to troubleshoot this here index private static final boolean LOCAL_DEBUG = false; private GosuClassTypeLoader _loader; private Map<String, ArrayList<String>> _typeToEnhancementsMap; private ArrayList<String> _arrayEnhancements; private boolean _loadingIndex; private String _currentEnhName; private Set<IType> _currentlyEnhancing; EnhancementIndex( GosuClassTypeLoader loader ) { _currentlyEnhancing = new HashSet<IType>(); _loader = loader; } public void addEnhancementMethods( IType typeToEnhance, Collection<IMethodInfo> methodsToAddTo ) { maybeLoadEnhancementIndex(); checkNotIndexing(typeToEnhance); if( !(typeToEnhance instanceof IGosuEnhancementInternal) ) { TypeSystem.lock(); try { checkAndPushEnhancing( typeToEnhance ); try { EnhancementManager enhancementManager = new EnhancementManager(methodsToAddTo ); enhancementManager.addAllEnhancementMethodsForType(typeToEnhance); IType[] interfaces = typeToEnhance.getInterfaces(); for( IType interfaceType : interfaces ) { enhancementManager.addAllEnhancementMethodsForType(interfaceType); } } finally { popEnhancing( typeToEnhance ); } } finally { TypeSystem.unlock(); } } } public void addEnhancementProperties(IType typeToEnhance, Map<CharSequence, IPropertyInfo> propertyInfosToAddTo, boolean caseSensitive) { maybeLoadEnhancementIndex(); checkNotIndexing(typeToEnhance); if( !(typeToEnhance instanceof IGosuEnhancementInternal) ) { TypeSystem.lock(); try { checkAndPushEnhancing( typeToEnhance ); try { EnhancementManager enhancementManager = new EnhancementManager(propertyInfosToAddTo ); enhancementManager.addAllEnhancementPropsForType(typeToEnhance, caseSensitive); IType[] interfaces = typeToEnhance.getInterfaces(); for( IType interfaceType : interfaces ) { enhancementManager.addAllEnhancementPropsForType(interfaceType, caseSensitive); } } finally { popEnhancing( typeToEnhance ); } } finally { TypeSystem.unlock(); } } } private void popEnhancing( IType typeToEnhance ) { _currentlyEnhancing.remove(typeToEnhance); } private void checkAndPushEnhancing( IType typeToEnhance ) { if( _currentlyEnhancing.contains( typeToEnhance ) ) { throw new IllegalStateException( "A recursive loop was found in enhancement definition. The type " + typeToEnhance + " attempted to load its enhancements again while loading its enhancments initially. " + "This usually indicates a logical error in the Gosu runtime." ); } else { _currentlyEnhancing.add( typeToEnhance ); } } private void checkNotIndexing( IType typeToEnhance ) { if( _loadingIndex ) { throw new IllegalStateException( "There is a circular type reference. When loading the enhancement defined in " + _currentEnhName + ", enhancements for " + typeToEnhance.getName() + " were requested. This" + " usual occurs when the typeloader of the type enhanced in " + _currentEnhName + " compiles " + " code. We do not support enhancements on types that have this behavior." ); } } public void maybeLoadEnhancementIndex() { if( _typeToEnhancementsMap != null ) { return; } try { _loadingIndex = true; _typeToEnhancementsMap = new HashMap<String, ArrayList<String>>(); _arrayEnhancements = new ArrayList<String>(); Set<String> allEnhancements = _loader.getRepository().getAllTypeNames(new String[]{GosuClassTypeLoader.GOSU_ENHANCEMENT_FILE_EXT}); indexEnhancements(allEnhancements.toArray(new String[allEnhancements.size()])); } finally { _loadingIndex = false; _currentEnhName = null; } } private Set<String> indexEnhancements(String[] enhancementNames) { IGosuClassRepository repository = _loader.getRepository(); Set<String> enhancedTypes = new HashSet<String>(); for( String enhancementName : enhancementNames ) { _currentEnhName = enhancementName; ISourceFileHandle sfh = repository.findClass(enhancementName, GosuClassTypeLoader.ALL_EXTS); if (sfh != null && sfh.getClassType() == ClassType.Enhancement && sfh.getParentType() == null) { String enhancedTypeName = parseEnhancedTypeName(sfh); if( enhancedTypeName != null && !IErrorType.NAME.equals( enhancedTypeName ) ) { ArrayList<String> enhancements = getEnhancementIndexForType( enhancedTypeName ); if (!enhancements.contains(enhancementName)) { enhancements.add(enhancementName); enhancedTypes.add(enhancedTypeName); } } } else { // remove the enhancement } } return enhancedTypes; } private Set<String> indexEnhancements(RefreshRequest request) { Set<String> enhancedTypes = new HashSet<String>(); if (request.file != null && request.file.getExtension().equals("gsx") && // request type can be an inner class or block, avoid processing those as enhancements request.file.getBaseName().equals( getSimpleName( request.types[0] ) ) ) { String enhancementName = request.types[0]; _currentEnhName = enhancementName; String enhancedTypeName = parseEnhancedTypeName(request); if (enhancedTypeName != null && !IErrorType.NAME.equals(enhancedTypeName)) { ArrayList<String> enhancements = getEnhancementIndexForType(enhancedTypeName); if (!enhancements.contains(enhancementName)) { enhancements.add(enhancementName); enhancedTypes.add(enhancedTypeName); } } } return enhancedTypes; } private String getSimpleName(String type) { int iDot = type.lastIndexOf( '.' ); if( iDot >= 0 ) { return type.substring( iDot + 1 ); } return type; } private String parseEnhancedTypeName(RefreshRequest request) { try { return parseEnhancedTypeName(request.file.openInputStream(), request.file.getBaseName()); } catch (IOException e) { return IErrorType.NAME; } } public static String parseEnhancedTypeName(ISourceFileHandle sfh) { return parseEnhancedTypeName(new ByteArrayInputStream(StreamUtil.toBytes(sfh.getSource().getSource())), sfh.getRelativeName()); } public static String parseEnhancedTypeName(InputStream stream, String baseFileName) { BufferedInputStream input = null; try { input = new BufferedInputStream(stream, 1024); CharStream cs = new ANTLRInputStream(input); JavaLexer lexer = new JavaLexer(cs); Token token = lexer.nextToken(); while (not(token, Keyword.KW_enhancement.getName())) { token = lexer.nextToken(); } while (not(token, baseFileName)) { token = lexer.nextToken(); } while (not(token, ":")) { token = lexer.nextToken(); } StringBuilder name = new StringBuilder(); token = lexer.nextToken(); do { name.append(token.getText()); token = lexer.nextToken(); } while (not(token, "{") && not(token, "<")); return clean(name); } catch (IOException e) { return IErrorType.NAME; } finally { if (input != null) try { input.close(); } catch (IOException e) { } } } private static String clean(StringBuilder name) { for (int i = 0; i < name.length(); ) { if (Character.isWhitespace(name.charAt(i))) { name.delete(i, i + 1); } else { i++; } } return name.toString(); } private static boolean not(Token token, String text) throws IOException { if (token.getType() == JavaLexer.EOF) { throw new IOException("EOF"); } return !token.getText().equals(text); } private ArrayList<String> getEnhancementIndexForType( String strEnhancedTypeName ) { maybeLoadEnhancementIndex(); if( strEnhancedTypeName.contains( "[" ) ) { return _arrayEnhancements; } else { ArrayList<String> enhancements = _typeToEnhancementsMap.get( strEnhancedTypeName ); if( enhancements == null ) { enhancements = new ArrayList<String>(); _typeToEnhancementsMap.put( strEnhancedTypeName, enhancements ); } return enhancements; } } public List<IGosuEnhancementInternal> getEnhancementsForType( IType typeToEnhance ) { maybeLoadEnhancementIndex(); ArrayList<IGosuEnhancementInternal> enhancements = new ArrayList<IGosuEnhancementInternal>(); IType genericEnhancedType = TypeLord.getPureGenericType(typeToEnhance); if (genericEnhancedType == null) { return enhancements; } Set<String> possibleEnhancementNames = getPossibleEnhancementsForTypeFromIndex( genericEnhancedType ); for( String possibleEnhancementTypeName : possibleEnhancementNames ) { try { IType enhancementType = TypeLoaderAccess.instance().getIntrinsicTypeByFullName( possibleEnhancementTypeName ); if( enhancementType instanceof IGosuEnhancementInternal ) { IGosuEnhancementInternal possibleEnhancement = (IGosuEnhancementInternal)enhancementType; if (!possibleEnhancement.isCompilingHeader()) { IType typeEnhanced = possibleEnhancement.getEnhancedType(); TypeVarToTypeMap actualParamByVarName = TypeLord.mapTypeByVarName( possibleEnhancement, possibleEnhancement ); typeEnhanced = typeEnhanced == null ? null : TypeLord.getActualType( typeEnhanced, actualParamByVarName, false ); if( enhancementApplies( typeEnhanced, typeToEnhance, true) ) { enhancements.add( possibleEnhancement ); } } else { throw new RuntimeException("Theoretically, should not be able to get here....so much for theory "); } } } catch( ClassNotFoundException e ) { // CommonServices.getEntityAccess().getLogger().warn( "Unable to add enhancement " + possibleEnhancementTypeName + " because the corresponding class was not found." ); } } return enhancements; } @Override public void refreshedTypes(RefreshRequest request) { if (request.kind == RefreshKind.CREATION) { Set<String> enhancedTypes = indexEnhancements(request.types); for (String enhancedType : enhancedTypes) { IType type = TypeSystem.getByFullNameIfValid(enhancedType, _loader.getModule()); if (type != null) { TypeSystem.refresh((ITypeRef)type); } } } else if (request.kind == RefreshKind.DELETION) { for (String enhancementName : request.types) { removeEnhancement(enhancementName); } } else if (request.kind == RefreshKind.MODIFICATION) { indexEnhancements(request); } } public String getOrphanedEnhancement(String typeName) { String name = typeName.substring(typeName.lastIndexOf('.') + 1); ArrayList<String> enhancementNames = getEnhancementIndexForType(name); for (String enhancement : enhancementNames) { return enhancement; } return null; } private boolean enhancementApplies( IType typeEnhanced, IType typeToEnhance, boolean exact ) { if( typeEnhanced == null ) { return false; } else if( typeEnhanced instanceof IGosuEnhancementInternal ) { //Do not enhance enhancements return false; } else if( hasErrorTypeComponent( typeEnhanced ) ) { //Do not enhance if there is an error component to the enhancement type return false; } else if( typeEnhanced.isArray() && typeToEnhance.isArray() ) { IType enhancementComponent = typeEnhanced.getComponentType(); IType enhancedComponent = typeToEnhance.getComponentType(); return enhancedComponent.isPrimitive() == enhancementComponent.isPrimitive() && enhancementApplies( enhancementComponent, enhancedComponent, exact ); } else { if (exact) { return typeEnhanced.isAssignableFrom( typeToEnhance ); } else { return TypeLord.getPureGenericType(typeEnhanced).isAssignableFrom( TypeLord.getPureGenericType(typeToEnhance) ); } } } private boolean hasErrorTypeComponent(IType enhancedType) { if (enhancedType instanceof IErrorType) { return true; } if (enhancedType.isParameterizedType()) { IType[] typeParam = enhancedType.getTypeParameters(); for (IType parameter : typeParam) { if (hasErrorTypeComponent(parameter)) { return true; } } } if( enhancedType.isArray() ) { return hasErrorTypeComponent( enhancedType.getComponentType() ); } return false; } Set<String> getPossibleEnhancementsForTypeFromIndex( IType typeToGetEnhancementsFor ) { String typeToEnhanceName = typeToGetEnhancementsFor.getName(); StringBuilder possibleName = new StringBuilder(); Set<String> enhancements = new TreeSet<String>(); int currPos = typeToEnhanceName.length(); do { int nextDot = typeToEnhanceName.lastIndexOf('.', currPos-1); String string = typeToEnhanceName.substring(nextDot == -1 ? 0 : nextDot+1, currPos); currPos = nextDot; possibleName.insert( 0, string ); ArrayList<String> enhancementTypes; if( typeToGetEnhancementsFor.isArray() ) { enhancementTypes = _arrayEnhancements; } else { enhancementTypes = _typeToEnhancementsMap.get( possibleName.toString() ); } if( enhancementTypes != null ) { enhancements.addAll( enhancementTypes ); } possibleName.insert( 0, "." ); } while (currPos != -1); return enhancements; } public void removeEntry( IGosuEnhancement enhancement ) { removeEnhancement(enhancement.getName()); } public void removeEnhancement(String enhancementName) { if (_typeToEnhancementsMap != null) { for( Map.Entry<String, ArrayList<String>> entry : _typeToEnhancementsMap.entrySet()) { ArrayList<String> value = entry.getValue(); if( value.remove( enhancementName ) ) { break; } } } } public void addEntry( IType enhancedType, IGosuEnhancement enhancement ) { ArrayList<String> enhancementIndexForType = getEnhancementIndexForType( enhancedType.getName() ); if( !enhancementIndexForType.contains( enhancement.getName() ) ) { enhancementIndexForType.add( enhancement.getName() ); } } /** * A helper class that holds some data structures that we build up to manage method addition/replacement */ private class EnhancementManager { private Collection<IMethodInfo> _methodsToAddTo; private Map<CharSequence, IPropertyInfo> _propertyInfosToAddTo; private Map<String, List<IMethodInfo>> _methodNamesToMethods; public EnhancementManager(Collection<IMethodInfo> methodsToAddTo) { _methodsToAddTo = methodsToAddTo; } public EnhancementManager(Map<CharSequence, IPropertyInfo> propertyInfosToAddTo) { _propertyInfosToAddTo = propertyInfosToAddTo; } public void addAllEnhancementMethodsForType( IType typeToGetEnhancementsFor ) { List<IGosuEnhancementInternal> enhancements = getEnhancementsForType( typeToGetEnhancementsFor ); for( IGosuEnhancementInternal type : enhancements ) { GosuClassTypeInfo typeInfo = getTypeInfoForType( typeToGetEnhancementsFor, type ); List extensionMethods = typeInfo.getDeclaredMethods(); for( Object extensionMethod : extensionMethods ) { GosuMethodInfo extensionMethodInfo = (GosuMethodInfo)extensionMethod; if( shouldAddMethod( extensionMethodInfo, type, typeToGetEnhancementsFor ) ) { _methodsToAddTo.add( extensionMethodInfo ); } } } } public void addAllEnhancementPropsForType(IType typeToGetEnhancementsFor, boolean caseSensitive) { List<IGosuEnhancementInternal> enhancements = getEnhancementsForType( typeToGetEnhancementsFor ); for( IGosuEnhancementInternal type : enhancements ) { GosuClassTypeInfo typeInfo = getTypeInfoForType( typeToGetEnhancementsFor, type ); List<? extends IPropertyInfo> props = typeInfo.getDeclaredProperties(); for( IPropertyInfo enhancementPropertyInfo : props ) { if( typeToGetEnhancementsFor.isArray() || (!enhancementPropertyInfo.isPrivate() && (!enhancementPropertyInfo.isInternal() || type.getNamespace().equals( typeToGetEnhancementsFor.getNamespace() ))) ) { IPropertyInfo existingPropInfo = _propertyInfosToAddTo.get(convertCharSequenceToCorrectSensitivity(enhancementPropertyInfo.getName(), caseSensitive) ); //if a property exists that is not associated with this if( existingPropInfo != null && !GosuObjectUtil.equals( existingPropInfo.getContainer(), enhancementPropertyInfo.getContainer() ) && notAnInternalOrPrivateField( existingPropInfo ) ) { } else { _propertyInfosToAddTo.put( convertCharSequenceToCorrectSensitivity(enhancementPropertyInfo.getName(), caseSensitive), enhancementPropertyInfo ); } } } } } private boolean notAnInternalOrPrivateField( IPropertyInfo existingPropInfo ) { // Allow shadowing of protected or private fields on Java classes return !(existingPropInfo instanceof JavaFieldPropertyInfo) || (existingPropInfo.isPublic() || existingPropInfo.isProtected()); } private GosuClassTypeInfo getTypeInfoForType( IType typeToGetEnhancementsFor, IGosuEnhancementInternal enhancementType ) { if( enhancementType.isGenericType() && typeToGetEnhancementsFor.isParameterizedType() || typeToGetEnhancementsFor.isArray() ) { enhancementType = parameterizeEnhancement( enhancementType, typeToGetEnhancementsFor ); } else if (enhancementType.isGenericType() && typeToGetEnhancementsFor.isArray() ) { //## todo: I think we can delete the else-if because it is subsumed by the previous if enhancementType = (IGosuEnhancementInternal) enhancementType.getParameterizedType( typeToGetEnhancementsFor.getComponentType() ); } return (GosuClassTypeInfo) enhancementType.getTypeInfo(); } private IGosuEnhancementInternal parameterizeEnhancement( IGosuEnhancementInternal enhancementType, IType typeToGetEnhancementsFor ) { if( typeToGetEnhancementsFor.isArray() ) { typeToGetEnhancementsFor = typeToGetEnhancementsFor.getComponentType().isPrimitive() ? TypeLord.getBoxedTypeFromPrimitiveType( typeToGetEnhancementsFor.getComponentType() ).getArrayType() : typeToGetEnhancementsFor; IType genericEnhancedType = TypeLord.getPureGenericType( enhancementType.getEnhancedType() ); TypeVarToTypeMap typeVars = new TypeVarToTypeMap(); TypeLord.inferTypeVariableTypesFromGenParamTypeAndConcreteType( genericEnhancedType, typeToGetEnhancementsFor, typeVars ); if( typeVars.size() > 0 ) { return (IGosuEnhancementInternal)enhancementType.getParameterizedType( (IType[])typeVars.values().toArray( new IType[typeVars.values().size()] ) ); } else { return enhancementType; } } else { IType genericEnhancedType = TypeLord.getPureGenericType( enhancementType.getEnhancedType() ); IType parameterizedEnhancedType = TypeLord.findParameterizedType( typeToGetEnhancementsFor, genericEnhancedType ); TypeVarToTypeMap map = new TypeVarToTypeMap(); for( IGenericTypeVariable gtv : enhancementType.getGenericTypeVariables() ) { map.put( gtv.getTypeVariableDefinition().getType(), null ); } IGenericTypeVariable[] gtvs = enhancementType.getEnhancedType().getGenericTypeVariables(); if( gtvs != null ) { for( int i = 0; i < gtvs.length; i++ ) { IGenericTypeVariable gtv = gtvs[i]; TypeLord.inferTypeVariableTypesFromGenParamTypeAndConcreteType( gtv.getTypeVariableDefinition().getType(), parameterizedEnhancedType.getTypeParameters()[i], map ); } } // Bound any types that cannot be inferred via the relationship with the enhanced type for( Object key : new ArrayList<Object>( map.keySet() ) ) { if( map.getRaw( key ) == null ) { for( IGenericTypeVariable gtv : enhancementType.getGenericTypeVariables() ) { if( TypeVarToTypeMap.looseEquals( gtv.getTypeVariableDefinition().getType(), key ) ) { map.put( gtv.getTypeVariableDefinition().getType(), gtv.getBoundingType() ); break; } } } } if( map.size() > 0 ) { List<IType> typeParamList = makeOrderedTypeParams( map, enhancementType ); return (IGosuEnhancementInternal)enhancementType.getParameterizedType( typeParamList.toArray( new IType[typeParamList.size()] ) ); } return enhancementType; } } private List<IType> makeOrderedTypeParams( TypeVarToTypeMap map, IGosuEnhancementInternal enhancementType ) { List<IType> typeParams = new ArrayList<IType>(); for( IGenericTypeVariable gtv : enhancementType.getGenericTypeVariables() ) { typeParams.add( map.getRaw( gtv.getTypeVariableDefinition().getType() ) ); } return typeParams; } //builds up a map of method names to method info collections private Map<String, List<IMethodInfo>> createMethodMap( Collection<IMethodInfo> methodsToAddTo ) { Map<String, List<IMethodInfo>> returnMap = new HashMap<String, List<IMethodInfo>>(); for( IMethodInfo methodInfo : methodsToAddTo ) { List<IMethodInfo> list = returnMap.get( methodInfo.getDisplayName() ); if( list == null ) { list = new ArrayList<IMethodInfo>(); returnMap.put( methodInfo.getDisplayName(), list ); } list.add( methodInfo ); } return returnMap; } private boolean shouldAddMethod(IMethodInfo enhancementMethodInfo, IGosuEnhancement enhancementType, IType typeToGetEnhancementsFor ) { if (_methodNamesToMethods == null) { _methodNamesToMethods = createMethodMap( _methodsToAddTo ); } IParameterInfo[] extensionParams = enhancementMethodInfo.getParameters(); List<IMethodInfo> matchingMethods = _methodNamesToMethods.get( enhancementMethodInfo.getDisplayName() ); if( !typeToGetEnhancementsFor.isArray() && (enhancementMethodInfo.isPrivate() || (enhancementMethodInfo.isInternal() && !enhancementType.getNamespace().equals( typeToGetEnhancementsFor.getNamespace() ))) ) { return false; } if( matchingMethods != null ) { //for each method with a matching name for( IMethodInfo methodInfo : matchingMethods ) { IParameterInfo[] methodParams = methodInfo.getParameters(); if( methodParams.length == extensionParams.length ) { if( paramTypesEqual( methodParams, extensionParams ) ) { //do not add the method because either it is a bad extension that conflicts with an existing method or //it has already been added by a superclass return false; } } } } //Good to go, add the method return true; } private boolean paramTypesEqual( IParameterInfo[] methodParams, IParameterInfo[] extensionParams ) { for( int i = 0; i < methodParams.length; i++ ) { if( !GosuObjectUtil.equals( methodParams[i].getFeatureType(), extensionParams[i].getFeatureType() ) ) { return false; } } return true; } } private static CharSequence convertCharSequenceToCorrectSensitivity(CharSequence cs, boolean caseSensitive) { return caseSensitive ? cs.toString() : CICS.get( cs ); } @Override public String toString() { return "Enhancements Index for " + _loader.toString(); } }