/* * Copyright 2013. Guidewire Software, Inc. */ package gw.internal.gosu.parser; import gw.internal.gosu.parser.java.classinfo.AsmClassAnnotationInfo; import gw.internal.gosu.parser.java.classinfo.JavaArrayClassInfo; import gw.internal.gosu.parser.java.classinfo.JavaSourceMethodDescriptor; import gw.internal.gosu.parser.java.classinfo.JavaSourcePropertyDescriptor; import gw.internal.gosu.parser.java.classinfo.JavaSourceUtil; import gw.lang.GosuShop; import gw.lang.javadoc.IClassDocNode; import gw.lang.reflect.EnumValuePlaceholder; import gw.lang.reflect.IAnnotationInfo; import gw.lang.reflect.IEnumValue; import gw.lang.reflect.IScriptabilityModifier; import gw.lang.reflect.IType; import gw.lang.reflect.ImplicitPropertyUtil; import gw.lang.reflect.Modifier; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.ISourceFileHandle; import gw.lang.reflect.java.AbstractJavaClassInfo; import gw.lang.reflect.java.IAsmJavaClassInfo; import gw.lang.reflect.java.IJavaClassConstructor; import gw.lang.reflect.java.IJavaClassField; import gw.lang.reflect.java.IJavaClassInfo; import gw.lang.reflect.java.IJavaClassMethod; import gw.lang.reflect.java.IJavaClassType; import gw.lang.reflect.java.IJavaClassTypeVariable; import gw.lang.reflect.java.IJavaMethodDescriptor; import gw.lang.reflect.java.IJavaPropertyDescriptor; import gw.lang.reflect.java.asm.AsmAnnotation; import gw.lang.reflect.java.asm.AsmClass; import gw.lang.reflect.java.asm.AsmField; import gw.lang.reflect.java.asm.AsmInnerClassType; import gw.lang.reflect.java.asm.AsmMethod; import gw.lang.reflect.java.asm.AsmType; import gw.lang.reflect.module.IModule; import gw.util.GosuObjectUtil; import gw.util.concurrent.LocklessLazyVar; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class AsmClassJavaClassInfo extends AsmTypeJavaClassType implements IAsmJavaClassInfo { private AsmClass _class; private IJavaClassMethod[] _declaredMethods; private IJavaClassInfo[] _interfaces; private IJavaClassInfo _superclass; private IJavaClassTypeVariable[] _typeVariables; private IJavaClassField[] _declaredFields; private IJavaClassConstructor[] _declaredConstructors; private IAnnotationInfo[] _declaredAnnotations; private IJavaPropertyDescriptor[] _propertyDescriptors; private IJavaMethodDescriptor[] _methodDescriptors; private IJavaClassField[] _allFields; private IJavaClassType[] _genericInterfaces; private IJavaClassInfo[] _declaredClasses; private LocklessLazyVar<IType> _enclosingClass = new LocklessLazyVar<IType>() { protected IType init() { AsmType enclosingClass = _class.getEnclosingType(); return enclosingClass == null ? null : TypeSystem.getByFullName( enclosingClass.getName(), _module ); } }; private IEnumValue[] _enumConstants; private String _simpleName; private String _namespace; public AsmClassJavaClassInfo( AsmClass cls, IModule module ) { super( cls, module ); if( cls == null ) { throw new IllegalArgumentException( "Class cannot be null." ); } _class = cls; _module = module; } @Override public boolean isAnnotation() { return _class.isAnnotation(); } @Override public boolean isInterface() { return _class.isInterface(); } @Override public IJavaClassType getConcreteType() { return this; } @Override public String getName() { return _class.getNameWithArrayBrackets(); } public String getNameSignature() { return GosuShop.toSignature( _class.getFqn() ); } @Override public IJavaClassMethod getMethod( String methodName, IJavaClassInfo... paramTypes ) throws NoSuchMethodException { outer: for( IJavaClassMethod method : getDeclaredMethods() ) { if( !method.getName().equals( methodName ) ) { continue; } IJavaClassInfo[] methodParamTypes = method.getParameterTypes(); if( paramTypes.length != methodParamTypes.length ) { continue; } for( int i = 0; i < paramTypes.length; i++ ) { if( !paramTypes[i].equals( methodParamTypes[i] ) ) { continue outer; } } return method; } IJavaClassInfo superclass = getSuperclass(); if( superclass != null ) { // This is to be consistent with Sun's crappy impl: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6815786 // http://www.youtube.com/watch?v=AG7LjVCj50Y return superclass.getMethod( methodName, paramTypes ); } throw new NoSuchMethodException(); } public IJavaClassMethod getDeclaredMethod( String methodName, IJavaClassInfo... paramTypes ) throws NoSuchMethodException { return getMethod( methodName, paramTypes ); } public IJavaClassMethod[] getDeclaredMethods() { if( _declaredMethods == null ) { List<AsmMethod> asmMethods = _class.getDeclaredMethodsAndConstructors(); List<IJavaClassMethod> methods = new ArrayList<IJavaClassMethod>( asmMethods.size() ); for( AsmMethod asmMethod : asmMethods ) { if( !asmMethod.isConstructor() && !asmMethod.isSynthetic() ) { methods.add( new AsmMethodJavaClassMethod( asmMethod, _module ) ); } } _declaredMethods = methods.toArray( new IJavaClassMethod[methods.size()] ); } return _declaredMethods; } @Override public Object newInstance() throws InstantiationException, IllegalAccessException { return null; } @Override public Object[] getEnumConstants() { if( _enumConstants == null ) { List<IEnumValue> enums = new ArrayList<IEnumValue>(); IJavaClassField[] fields = getFields(); for( IJavaClassField field : fields ) { if( field.isEnumConstant() ) { enums.add( new EnumValuePlaceholder( field.getName() ) ); } } _enumConstants = enums.toArray( new IEnumValue[enums.size()] ); } return _enumConstants; } @Override public IType getJavaType() { return TypeSystem.get( this ); } @Override public IJavaClassInfo[] getInterfaces() { if( _interfaces == null ) { List<AsmType> rawInterfaces = _class.getInterfaces(); IJavaClassInfo[] interfaces = new IJavaClassInfo[rawInterfaces.size()]; for( int i = 0; i < rawInterfaces.size(); i++ ) { interfaces[i] = JavaSourceUtil.getClassInfo( rawInterfaces.get( i ).getName(), _module ); } _interfaces = interfaces; } return _interfaces; } @Override public IJavaClassInfo getSuperclass() { if( _superclass == null ) { _superclass = _class.getSuperClass() == null ? NULL_TYPE : JavaSourceUtil.getClassInfo( _class.getSuperClass().getName(), _module ); } return _superclass == NULL_TYPE ? null : _superclass; } public IJavaClassTypeVariable[] getTypeParameters() { if( _typeVariables == null ) { List<AsmType> rawTypeVariables = _class.getTypeParameters(); IJavaClassTypeVariable[] typeVariables = new IJavaClassTypeVariable[rawTypeVariables.size()]; for( int i = 0; i < rawTypeVariables.size(); i++ ) { typeVariables[i] = new AsmTypeVariableJavaClassTypeVariable( rawTypeVariables.get( i ), _module ); } _typeVariables = typeVariables; } return _typeVariables; } @Override public IJavaClassField[] getDeclaredFields() { if( _declaredFields == null ) { List<AsmField> rawFields = _class.getDeclaredFields(); IJavaClassField[] fields = new IJavaClassField[rawFields.size()]; for( int i = 0; i < rawFields.size(); i++ ) { fields[i] = new AsmFieldJavaClassField( rawFields.get( i ), _module ); } _declaredFields = fields; } return _declaredFields; } @Override public IJavaClassConstructor[] getDeclaredConstructors() { if( _declaredConstructors == null ) { List<AsmMethod> asmMethods = _class.getDeclaredMethodsAndConstructors(); List<IJavaClassConstructor> ctors = new ArrayList<IJavaClassConstructor>( asmMethods.size() ); for( AsmMethod asmMethod : asmMethods ) { if( asmMethod.isConstructor() && !asmMethod.isSynthetic() ) { ctors.add( new AsmConstructorJavaClassConstructor( asmMethod, _module ) ); } } _declaredConstructors = ctors.toArray( new IJavaClassConstructor[ctors.size()] ); } return _declaredConstructors; } public IJavaClassConstructor getConstructor( IJavaClassInfo... paramTypes ) throws NoSuchMethodException { outer: for( IJavaClassConstructor ctor : getDeclaredConstructors() ) { IJavaClassInfo[] methodParamTypes = ctor.getParameterTypes(); if( paramTypes.length != methodParamTypes.length ) { continue; } for( int i = 0; i < paramTypes.length; i++ ) { if( !paramTypes[i].equals( methodParamTypes[i] ) ) { continue outer; } } return ctor; } throw new NoSuchMethodException(); } @Override public boolean isAnnotationPresent( Class<? extends Annotation> annotationClass ) { return _class.isAnnotationPresent( annotationClass ); } @Override public IAnnotationInfo getAnnotation( Class annotationClass ) { for( IAnnotationInfo annotationInfo : getDeclaredAnnotations() ) { if( annotationInfo.getName().equals( annotationClass.getName() ) ) { return annotationInfo; } } return null; } @Override public IAnnotationInfo[] getDeclaredAnnotations() { if( _declaredAnnotations == null ) { List<AsmAnnotation> annotations = _class.getDeclaredAnnotations(); IAnnotationInfo[] declaredAnnotations = new IAnnotationInfo[annotations.size()]; for( int i = 0; i < declaredAnnotations.length; i++ ) { declaredAnnotations[i] = new AsmClassAnnotationInfo( annotations.get( i ), this ); } _declaredAnnotations = declaredAnnotations; } return _declaredAnnotations; } @Override public IClassDocNode createClassDocNode() { return null; } @Override public IJavaPropertyDescriptor[] getPropertyDescriptors() { if( _propertyDescriptors == null ) { _propertyDescriptors = initPropertyDescriptors(); } return _propertyDescriptors; } protected IJavaPropertyDescriptor[] initPropertyDescriptors() { Map<String, IJavaClassMethod> getters = new HashMap<String, IJavaClassMethod>(); HashMap<String, List<IJavaClassMethod>> setters = new HashMap<String, List<IJavaClassMethod>>(); List<IJavaClassMethod> methods = new ArrayList<IJavaClassMethod>(); methods.addAll( Arrays.asList( getDeclaredMethods() ) ); for( IJavaClassMethod method : methods ) { ImplicitPropertyUtil.ImplicitPropertyInfo info = JavaSourceUtil.getImplicitProperty( method ); if( info != null ) { if( info.isGetter() && !getters.containsKey( info.getName() ) ) { getters.put( info.getName(), method ); } else if( info.isSetter() ) { List<IJavaClassMethod> infoSetters = setters.get( info.getName() ); if( infoSetters == null ) { infoSetters = new ArrayList<IJavaClassMethod>( 2 ); } infoSetters.add( method ); setters.put( info.getName(), infoSetters ); } } } List<IJavaPropertyDescriptor> propertyDescriptors = new ArrayList<IJavaPropertyDescriptor>(); for( Map.Entry<String, IJavaClassMethod> entry : getters.entrySet() ) { String propName = entry.getKey(); List<IJavaClassMethod> infoSetters = setters.get( propName ); IJavaClassMethod getter = entry.getValue(); IJavaClassType getterType = getter == null ? null : getter.getGenericReturnType(); IJavaClassMethod theSetter = null; if( infoSetters != null ) { for( IJavaClassMethod setter : infoSetters ) { if( setter != null ) { setters.remove( propName ); if( getterType != null && (setter.getGenericParameterTypes()[0].equals( getterType ) || GosuObjectUtil.equals( setter.getGenericParameterTypes()[0].getConcreteType(), getterType )) ) { theSetter = setter; break; } } } } if( getterType != null ) { if( theSetter == null ) { theSetter = maybeFindSetterInSuper( getter, getSuperclass() ); } propertyDescriptors.add( new JavaSourcePropertyDescriptor( propName, (IJavaClassInfo)getterType.getConcreteType(), getter, theSetter ) ); } else if( infoSetters != null ) { for( IJavaClassMethod setter : infoSetters ) { getter = maybeFindGetterInSuper( setter, getSuperclass() ); if( getter != null ) { setters.remove( propName ); propertyDescriptors.add( new JavaSourcePropertyDescriptor( propName, (IJavaClassInfo)getterType.getConcreteType(), getter, setter ) ); } } } } for( Map.Entry<String, List<IJavaClassMethod>> entry : setters.entrySet() ) { String propName = entry.getKey(); IJavaClassMethod setter = entry.getValue().get( 0 ); IJavaClassType setterType = setter.getGenericReturnType(); propertyDescriptors.add( new JavaSourcePropertyDescriptor( propName, (IJavaClassInfo)setterType.getConcreteType(), null, setter ) ); } return propertyDescriptors.toArray( new IJavaPropertyDescriptor[propertyDescriptors.size()] ); } public static IJavaClassMethod maybeFindSetterInSuper(IJavaClassMethod getter, IJavaClassInfo superClass ) { if( superClass == null ) { return null; } for( ;superClass != null; superClass = superClass.getSuperclass() ) { for( IJavaPropertyDescriptor pd: superClass.getPropertyDescriptors() ) { if( doesSetterDescMatchGetterMethod(getter, pd) ) { return pd.getWriteMethod(); } } } return null; } private static boolean doesSetterDescMatchGetterMethod(IJavaClassMethod getter, IJavaPropertyDescriptor pd) { final IJavaClassType getterType = getter.getGenericReturnType(); if( getterType != null ) { final IJavaClassMethod setter = pd.getWriteMethod(); if( setter != null && (setter.getGenericParameterTypes()[0].equals( getterType ) || GosuObjectUtil.equals( setter.getGenericParameterTypes()[0].getConcreteType(), getterType )) ) { if( ("get" + pd.getName()).equals( getter.getName() ) || ("is" + pd.getName()).equals( getter.getName() ) ) { return true; } } } return false; } public static IJavaClassMethod maybeFindGetterInSuper(IJavaClassMethod setter, IJavaClassInfo superClass ) { if( superClass == null ) { return null; } for( ; superClass != null; superClass = superClass.getSuperclass() ) { for( IJavaPropertyDescriptor pd: superClass.getPropertyDescriptors() ) { if( doesSetterDescMatchGetterMethod(setter, pd) ) { return pd.getReadMethod(); } } } return null; } @Override public IJavaMethodDescriptor[] getMethodDescriptors() { if( _methodDescriptors == null ) { IJavaClassMethod[] declaredMethods = getDeclaredMethods(); _methodDescriptors = new IJavaMethodDescriptor[declaredMethods.length]; for( int i = 0; i < declaredMethods.length; i++ ) { _methodDescriptors[i] = new JavaSourceMethodDescriptor( declaredMethods[i] ); } } return _methodDescriptors; } @Override public boolean hasCustomBeanInfo() { return false; } @Override public String getRelativeName() { return getName().substring( getNamespace().length() + 1 ); } @Override public String getDisplayName() { return getSimpleName(); } @Override public String getSimpleName() { if( _simpleName == null ) { _simpleName = _class.getSimpleName(); } return _simpleName; } @Override public boolean isVisibleViaFeatureDescriptor( IScriptabilityModifier constraint ) { return true; } @Override public boolean isHiddenViaFeatureDescriptor() { return false; } @Override public IJavaClassField[] getFields() { if( _allFields == null ) { List<IJavaClassField> fields = new ArrayList<IJavaClassField>(); IJavaClassField[] declaredFields = getDeclaredFields(); for( int i = 0; i < declaredFields.length; i++ ) { IJavaClassField field = declaredFields[i]; if( Modifier.isPublic( field.getModifiers() ) ) { fields.add( field ); } } IJavaClassInfo superclass = getSuperclass(); if( superclass != null ) { fields.addAll( Arrays.asList( superclass.getFields() ) ); } _allFields = fields.toArray( new IJavaClassField[fields.size()] ); } return _allFields; } public AsmClass getAsmType() { return _class; } @Override public IJavaClassInfo getComponentType() { return null; } @Override public boolean isArray() { return false; } @Override public boolean isEnum() { return _class.isEnum(); } @Override public int getModifiers() { return _class.getModifiers(); } @Override public boolean isPrimitive() { return _class.isPrimitive(); } @Override public IJavaClassInfo getEnclosingClass() { AsmType enclosingClass = _class.getEnclosingType(); if( enclosingClass != null ) { return TypeSystem.getJavaClassInfo( enclosingClass.getName(), _module ); } return null; } @Override public IType getEnclosingType() { return _enclosingClass.get(); } @Override public String getNamespace() { if( _namespace == null ) { String name = _class.getName(); int iDot = name.lastIndexOf( '.' ); if( iDot < 0 ) { _namespace = ""; } else { _namespace = name.substring( 0, iDot ); } } return _namespace; } @Override public IJavaClassType[] getGenericInterfaces() { if( _genericInterfaces == null ) { List<AsmType> asmIfaces = _class.getInterfaces(); IJavaClassType[] ifaces = new IJavaClassType[asmIfaces.size()]; for( int i = 0; i < asmIfaces.size(); i++ ) { ifaces[i] = AsmTypeJavaClassType.createType( asmIfaces.get( i ), _module ); } _genericInterfaces = ifaces; } return _genericInterfaces; } @Override public IJavaClassType getGenericSuperclass() { return AsmTypeJavaClassType.createType( _class.getSuperClass(), _module ); } @Override public IJavaClassInfo getArrayType() { return new JavaArrayClassInfo( this ); } @Override public IJavaClassInfo[] getDeclaredClasses() { if( _declaredClasses == null ) { Map<String, AsmInnerClassType> innerClasses = _class.getInnerClasses(); ArrayList<IJavaClassInfo> declaredClasses = new ArrayList<IJavaClassInfo>( innerClasses.size() ); for( AsmInnerClassType innerClass : innerClasses.values() ) { IJavaClassInfo declaredClassInfo = TypeSystem.getJavaClassInfo( innerClass.getName(), _module ); declaredClasses.add( declaredClassInfo ); } _declaredClasses = declaredClasses.toArray( new IJavaClassInfo[declaredClasses.size()] ); } return _declaredClasses; } @Override public boolean isAssignableFrom( IJavaClassInfo aClass ) { return AbstractJavaClassInfo.isAssignableFrom( this, aClass ); } @Override public boolean isPublic() { return Modifier.isPublic( _class.getModifiers() ); } @Override public boolean isProtected() { return Modifier.isProtected( _class.getModifiers() ); } @Override public boolean isInternal() { return !isPublic() && !isProtected() && !isPrivate(); } @Override public boolean isPrivate() { return Modifier.isPrivate( _class.getModifiers() ); } @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals( Object obj ) { return AbstractJavaClassInfo.equals( this, obj ); } @Override public int hashCode() { return AbstractJavaClassInfo.hashCode( this ); } public String toString() { return _class.toString(); } @Override public Class getBackingClass() { try { // This should never get called in AsmClassJavaClassInfo but it does for now eg. see DataTypeImpl, JavaTypeInfo.makeLegacyAnnotationConstructor() //System.out.println( "!!!! DANGER !!!!!" + " Class.forName( \"" + getName() + "\" )" ); return Class.forName( getName(), false, getClass().getClassLoader() ); } catch( ClassNotFoundException e ) { return null; } } @Override public ISourceFileHandle getSourceFileHandle() { return null; } @Override public IModule getModule() { return _module; } @Override public IJavaClassType resolveType( String relativeName, int ignoreFlags ) { return null; } @Override public IJavaClassType resolveType( String relativeName, IJavaClassInfo whosAskin, int ignoreFlags ) { for( AsmInnerClassType innerClass : _class.getInnerClasses().values() ) { if( innerClass.getName().equals( getName() + "$" + relativeName ) ) { return JavaSourceUtil.getClassInfo( innerClass.getName(), getJavaType().getTypeLoader().getModule() ); } } return null; } @Override public IJavaClassType resolveImport( String relativeName ) { return null; } }