/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.ir.transform.util;
import gw.internal.gosu.parser.ICompilableTypeInternal;
import gw.internal.gosu.parser.IGosuClassInternal;
import gw.internal.gosu.parser.IGosuEnhancementInternal;
import gw.internal.gosu.parser.IGosuProgramInternal;
import gw.internal.gosu.parser.TypeLord;
import gw.lang.reflect.IRelativeTypeInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.BytecodeOptions;
import gw.lang.reflect.gs.IGosuProgram;
import gw.lang.reflect.java.IJavaType;
public class RequiresReflectionDeterminer
{
public static boolean shouldUseReflection( IType declaringClass, ICompilableTypeInternal compilingClass, IRelativeTypeInfo.Accessibility accessibility )
{
return isEnhancementAccessRequiringReflection( declaringClass, compilingClass, accessibility ) ||
isEvalProgramBetweenCallingClassAndDeclaringClass( compilingClass, declaringClass, accessibility ) ||
isDeclaringClassInAncestryOfEnclosingClassesOfEvalProgram( compilingClass, declaringClass, accessibility ) ||
isCallingClassEnclosedInDifferentPackageFromDeclaringSuperclass( compilingClass, declaringClass, accessibility ) ||
isGosuClassAccessingProtectedOrInternalMethodOfClassInDifferentClassloader( compilingClass, declaringClass, accessibility ) ||
isGosuClassAccessingProtectedMemberOfClassNotInHierarchy( compilingClass, declaringClass, accessibility ) ||
isProgramCompilingDuringDebuggerSuspension( compilingClass, accessibility ) ||
(isProgramNotEval( compilingClass, declaringClass ) && accessibility != IRelativeTypeInfo.Accessibility.PUBLIC); // for studio debugger expressions
}
private static boolean isProgramCompilingDuringDebuggerSuspension( IType compilingClass, IRelativeTypeInfo.Accessibility accessibility )
{
if( accessibility == IRelativeTypeInfo.Accessibility.PUBLIC )
{
return false;
}
return TypeLord.getOuterMostEnclosingClass( compilingClass ) instanceof IGosuProgram &&
TypeSystem.isIncludeAll();
}
private static boolean isEnhancementAccessRequiringReflection( IType declaringClass, ICompilableTypeInternal callingClass, IRelativeTypeInfo.Accessibility accessibility )
{
if( accessibility == IRelativeTypeInfo.Accessibility.PUBLIC || callingClass == null )
{
return false;
}
else if( callingClass instanceof IGosuEnhancementInternal )
{
IGosuEnhancementInternal enhancement = (IGosuEnhancementInternal)callingClass;
IType pureEnhancedType = TypeLord.getPureGenericType( enhancement.getEnhancedType() );
IType pureTargetType = TypeLord.getPureGenericType( declaringClass );
return pureTargetType.isAssignableFrom( pureEnhancedType );
}
else
{
return isEnhancementAccessRequiringReflection( declaringClass, callingClass.getEnclosingType(), accessibility );
}
}
private static boolean isEvalProgramBetweenCallingClassAndDeclaringClass( ICompilableTypeInternal callingClass, IType declaringClass, IRelativeTypeInfo.Accessibility accessibility )
{
if( accessibility == IRelativeTypeInfo.Accessibility.PUBLIC )
{
return false;
}
if( TypeLord.encloses( declaringClass, callingClass ) )
{
while( true )
{
if( isInSeparateClassLoader( callingClass, declaringClass ) )
{
return true;
}
else if( callingClass == declaringClass )
{
return false;
}
callingClass = callingClass.getEnclosingType();
}
}
else if( declaringClass instanceof IGosuClassInternal && TypeLord.encloses( callingClass, declaringClass ) )
{
if( TypeLord.encloses( declaringClass, callingClass ) )
{
while( true )
{
if( isInSeparateClassLoader( (IGosuClassInternal)declaringClass, null ) )
{
return true;
}
else if( callingClass == declaringClass )
{
return false;
}
declaringClass = declaringClass.getEnclosingType();
}
}
}
return false;
}
private static boolean isDeclaringClassInAncestryOfEnclosingClassesOfEvalProgram( ICompilableTypeInternal callingClass, IType declaringClass, IRelativeTypeInfo.Accessibility accessibility )
{
if( accessibility == IRelativeTypeInfo.Accessibility.PUBLIC || callingClass == null )
{
return false;
}
if( isInSeparateClassLoader( callingClass, declaringClass ) && isDeclaringClassInAncestryOfEnclosingClasses( callingClass, declaringClass ) )
{
return true;
}
else
{
return isDeclaringClassInAncestryOfEnclosingClassesOfEvalProgram( callingClass.getEnclosingType(), declaringClass, accessibility );
}
}
private static boolean isCallingClassEnclosedInDifferentPackageFromDeclaringSuperclass( ICompilableTypeInternal callingClass, IType declaringClass, IRelativeTypeInfo.Accessibility accessibility )
{
return accessibility == IRelativeTypeInfo.Accessibility.PROTECTED &&
isEnclosedInSubtypeOfClass( callingClass, declaringClass ) &&
!getTopLevelNamespace( callingClass ).equals( getTopLevelNamespace( declaringClass ) );
}
// If we're calling a protected or internal method on a class in a different classloader
// and the package names are the same, then we assume the access is being allowed by Gosu in virtue of the package matching.
// Java will blow up if the package-level access is relied upon across class loaders, though, so we make the call reflectively.
private static boolean isGosuClassAccessingProtectedOrInternalMethodOfClassInDifferentClassloader( ICompilableTypeInternal callingClass, IType declaringClass, IRelativeTypeInfo.Accessibility accessibility )
{
return (accessibility == IRelativeTypeInfo.Accessibility.PROTECTED ||
accessibility == IRelativeTypeInfo.Accessibility.INTERNAL ||
AccessibilityUtil.forType( declaringClass ) == IRelativeTypeInfo.Accessibility.INTERNAL)
&& (declaringClass instanceof IJavaType || isInSeparateClassLoader( callingClass, declaringClass ))
&& getTopLevelNamespace( callingClass ).equals( getTopLevelNamespace( declaringClass ) );
}
private static boolean isGosuClassAccessingProtectedMemberOfClassNotInHierarchy( ICompilableTypeInternal callingClass, IType declaringClass, IRelativeTypeInfo.Accessibility accessibility )
{
// This is legal in Gosu if the member is accessed indirectly through a subclass that lives in same package as calling class
return accessibility == IRelativeTypeInfo.Accessibility.PROTECTED && !declaringClass.isAssignableFrom( callingClass );
}
private static String getTopLevelNamespace( IType type )
{
if( type.getEnclosingType() == null )
{
return type.getNamespace();
}
else
{
return getTopLevelNamespace( type.getEnclosingType() );
}
}
private static boolean isEnclosedInSubtypeOfClass( ICompilableTypeInternal potentiallyEnclosedClass, IType potentialSuperType )
{
if( potentiallyEnclosedClass == null )
{
return false;
}
else if( TypeLord.isSubtype( potentiallyEnclosedClass, potentialSuperType ) )
{
return true;
}
else
{
return isEnclosedInSubtypeOfClass( potentiallyEnclosedClass.getEnclosingType(), potentialSuperType );
}
}
private static boolean isInSeparateClassLoader( ICompilableTypeInternal callingClass, IType declaringClass )
{
return callingClass != declaringClass &&
(isInEvalProgram( callingClass ) ||
isInEvalProgram( declaringClass ) ||
isThrowawayProgram( callingClass ) ||
isThrowawayProgram( declaringClass ) ||
BytecodeOptions.isSingleServingLoader());
}
private static boolean isDeclaringClassInAncestryOfEnclosingClasses( ICompilableTypeInternal callingClass, IType declaringClass )
{
if( callingClass == null )
{
return false;
}
else if( TypeLord.isSubtype( callingClass, declaringClass ) )
{
return true;
}
else
{
return isDeclaringClassInAncestryOfEnclosingClasses( callingClass.getEnclosingType(), declaringClass );
}
}
private static boolean isInEvalProgram( IType gsClass )
{
if( gsClass == null ) {
return false;
}
return (gsClass instanceof IGosuProgram && ((IGosuProgram)gsClass).isAnonymous()) ||
isInEvalProgram( gsClass.getEnclosingType() );
}
private static boolean isThrowawayProgram( IType gsClass )
{
return gsClass instanceof IGosuProgram &&
((IGosuProgramInternal)gsClass).isThrowaway();
}
private static boolean isProgramNotEval( IType callingClass, IType declaringClass )
{
return callingClass != declaringClass &&
callingClass instanceof IGosuProgram &&
!((IGosuProgram)callingClass).isAnonymous();
}
}