/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.ir.nodes;
import gw.config.CommonServices;
import gw.internal.gosu.ir.transform.util.IRTypeResolver;
import gw.internal.gosu.parser.ClassJavaClassInfo;
import gw.internal.gosu.parser.IGosuClassInternal;
import gw.lang.ir.IJavaClassIRType;
import gw.lang.ir.IRType;
import gw.lang.ir.SyntheticIRType;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.TypeSystemShutdownListener;
import gw.lang.reflect.java.IJavaClassInfo;
import gw.lang.reflect.java.IJavaType;
import gw.util.GosuClassUtil;
import java.lang.reflect.Array;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class JavaClassIRType implements IJavaClassIRType {
private IJavaClassInfo _class;
private boolean _isArray;
private boolean _isPrimitive;
private String _slashName;
private String _descriptor;
// These objects don't have to be singletons; it's just cheaper to keep them around instead of re-creating them
// every single time, since we want to cache information on them
private static final ConcurrentHashMap<IJavaClassInfo, JavaClassIRType> IR_TYPES_BY_CLASS_INFO = new ConcurrentHashMap<IJavaClassInfo, JavaClassIRType>();
static {
TypeSystem.addShutdownListener(new TypeSystemShutdownListener() {
public void shutdown() {
IR_TYPES_BY_CLASS_INFO.clear();
}
});
}
public static IRType get( Class cls ) {
IJavaClassInfo clsInfo = TypeSystem.getJavaClassInfo(cls, TypeSystem.getGlobalModule());
return get( clsInfo );
}
public static IRType get( IJavaClassInfo cls ) {
JavaClassIRType javaClassIRType = IR_TYPES_BY_CLASS_INFO.get(cls);
// NOTE pdalbora 11-Oct-2012 -- There are certain classes, in particular, the entity proxy classes, which get
// re-defined during tests. So, I added this check to update the cache if the ClassLoader of the incoming class
// does not match that of the cached class. This means the cache is holding a stale version of the class.
if (javaClassIRType == null || shouldReplaceAnyway(cls, javaClassIRType)) {
javaClassIRType = new JavaClassIRType( cls );
IR_TYPES_BY_CLASS_INFO.put(cls, javaClassIRType);
}
return javaClassIRType;
}
private static boolean shouldReplaceAnyway(IJavaClassInfo cls, JavaClassIRType javaClassIRType) {
return !CommonServices.getPlatformHelper().isInIDE() && !equal(javaClassIRType.getJavaClassInfo().getBackingClass().getClassLoader(), cls.getBackingClass().getClassLoader());
}
private static boolean equal(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
private JavaClassIRType(IJavaClassInfo aClass) {
_class = aClass;
_isArray = aClass.isArray();
_isPrimitive = aClass.isPrimitive();
_slashName = computeSlashName();
_descriptor = computeDescriptor();
}
@Override
public String getName() {
return _class.getName();
}
@Override
public String getRelativeName() {
return _class.getRelativeName();
}
@Override
public String getDescriptor() {
return _descriptor;
}
public IJavaClassInfo getJavaClassInfo() {
return _class;
}
private String computeDescriptor() {
if (isByte()) {
return "B";
} else if (isChar()) {
return "C";
} else if (isDouble()) {
return "D";
} else if (isFloat()) {
return "F";
} else if (isInt()) {
return "I";
} else if (isLong()) {
return "J";
} else if (isLong()) {
return "J";
} else if (isShort()) {
return "S";
} else if (isBoolean()) {
return "Z";
} else if (isVoid()) {
return "V";
} else if (isArray()) {
return '[' + getComponentType().getDescriptor();
} else {
return 'L' + getSlashName() + ';';
}
}
@Override
//## todo: remove this method; class loading should not happen during compilation
public Class getJavaClass() {
if (_class instanceof ClassJavaClassInfo) {
return ((ClassJavaClassInfo) _class).getJavaClass();
} else {
if (isArray()) {
return Array.newInstance(getComponentType().getJavaClass(), 0).getClass();
} else {
if (isPrimitive()) {
return getPrimitiveClass();
} else {
// Class.forName(_class.getName())
try {
Class backingClass = _class.getBackingClass();
return backingClass == null ? thisShouldNeverHappenButDoes() : _class.getBackingClass();
}
catch( ClassNotFoundException e ) {
throw new RuntimeException( e );
}
}
}
}
}
private Class<?> thisShouldNeverHappenButDoes() throws ClassNotFoundException {
return Class.forName( _class.getName() );
}
@Override
public String getSlashName() {
return _slashName;
}
private String computeSlashName() {
if (isArray()) {
return getComponentType().getSlashName() + "[]";
}
IType outerType = _class.getEnclosingType();
if( outerType != null )
{
return IRTypeResolver.getDescriptor(outerType).getSlashName( ) + "$" + GosuClassUtil.getNameNoPackage( _class.getName() );
}
return _class.getName().replace( '.', '/' );
}
@Override
public boolean isStructural() {
return false;
}
@Override
public boolean isStructuralAndErased( IRType ownersType ) {
return false;
}
@Override
public IRType getArrayType() {
return get( _class.getArrayType() );
}
@Override
public IRType getComponentType() {
return get( _class.getComponentType() );
}
public IType getType() {
return TypeSystem.get( _class );
}
@Override
public boolean isArray() {
return _isArray;
}
@Override
public boolean isAssignableFrom(IRType otherType) {
if (_class.getName().equals(Object.class.getName()) && !otherType.isPrimitive()) {
return true;
}
if (isArray() && otherType.isArray()) {
return getComponentType().isAssignableFrom(otherType.getComponentType());
} else if (isArray() || otherType.isArray()) {
return false;
}
if (otherType instanceof JavaClassIRType) {
return _class.isAssignableFrom(((JavaClassIRType) otherType)._class);
} else if (otherType instanceof GosuClassIRType) {
Set<? extends IType> allTypesInHierarchy = ((GosuClassIRType)otherType).getType().getAllTypesInHierarchy();
for (IType hierarchyType : allTypesInHierarchy) {
IJavaClassInfo javaClassForType = resolveJavaClassForType(hierarchyType);
if (javaClassForType != null && javaClassForType.getName().equals(_class.getName())) {
return true;
}
}
return false;
} else if (otherType instanceof SyntheticIRType) {
return _class.isAssignableFrom(TypeSystem.getJavaClassInfo(((SyntheticIRType) otherType).getSuperClass(), TypeSystem.getGlobalModule()));
} else {
return false;
}
}
private IJavaClassInfo resolveJavaClassForType( IType hierarchyType ) {
if (hierarchyType instanceof IJavaType) {
return ((IJavaType) hierarchyType).getBackingClassInfo();
}
if (hierarchyType instanceof IGosuClassInternal) {
IGosuClassInternal gosuClass = (IGosuClassInternal) hierarchyType;
if (gosuClass.isProxy()) {
return gosuClass.getJavaType().getBackingClassInfo();
}
}
return null;
}
@Override
public boolean equals(Object obj) {
return obj instanceof JavaClassIRType && _class.getNameSignature().equals(((JavaClassIRType) obj)._class.getNameSignature());
}
@Override
public boolean isByte() {
return _class.getName().equals( byte.class.getName() );
}
@Override
public boolean isBoolean() {
return _class.getName().equals( boolean.class.getName() );
}
@Override
public boolean isShort() {
return _class.getName().equals( short.class.getName() );
}
@Override
public boolean isChar() {
return _class.getName().equals( char.class.getName() );
}
@Override
public boolean isInt() {
return _class.getName().equals( int.class.getName() );
}
@Override
public boolean isLong() {
return _class.getName().equals( long.class.getName() );
}
@Override
public boolean isFloat() {
return _class.getName().equals( float.class.getName() );
}
@Override
public boolean isDouble() {
return _class.getName().equals( double.class.getName() );
}
@Override
public boolean isVoid() {
return _class.getName().equals( void.class.getName() );
}
@Override
public boolean isPrimitive() {
return _isPrimitive;
}
@Override
public boolean isInterface() {
return _class.isInterface();
}
@Override
public String toString()
{
return getName();
}
public Class getPrimitiveClass() {
if (isBoolean()) {
return boolean.class;
} else if (isByte()) {
return byte.class;
} else if (isChar()) {
return char.class;
} else if (isShort()) {
return short.class;
} else if (isInt()) {
return int.class;
} else if (isLong()) {
return long.class;
} else if (isFloat()) {
return float.class;
} else if (isDouble()) {
return double.class;
} else if (isVoid()) {
return void.class;
} else {
throw new IllegalStateException("Cannot ask for primitive class from " + getName());
}
}
}