/* * Copyright Red Hat Inc. and/or its affiliates and other contributors * as indicated by the authors tag. All rights reserved. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU General Public License version 2. * * This particular file is subject to the "Classpath" exception as provided in the * LICENSE file that accompanied this code. * * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License, * along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package com.redhat.ceylon.eclipse.core.model.mirror; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.env.IDependent; import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope; import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment; import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding; import static com.redhat.ceylon.eclipse.core.model.LookupEnvironmentUtilities.*; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.modelJ2C; import com.redhat.ceylon.eclipse.core.model.LookupEnvironmentUtilities; import com.redhat.ceylon.eclipse.core.model.LookupEnvironmentUtilities.ActionOnClassBinding; import com.redhat.ceylon.eclipse.core.model.LookupEnvironmentUtilities.ActionOnResolvedType; import com.redhat.ceylon.eclipse.ui.CeylonPlugin; import com.redhat.ceylon.ide.common.model.unknownClassMirror_; import com.redhat.ceylon.ide.common.model.mirror.IdeClassMirror; import com.redhat.ceylon.model.loader.AbstractModelLoader; import com.redhat.ceylon.model.loader.ModelResolutionException; import com.redhat.ceylon.model.loader.NamingBase; import com.redhat.ceylon.model.loader.mirror.AnnotationMirror; import com.redhat.ceylon.model.loader.mirror.ClassMirror; import com.redhat.ceylon.model.loader.mirror.FieldMirror; import com.redhat.ceylon.model.loader.mirror.MethodMirror; import com.redhat.ceylon.model.loader.mirror.PackageMirror; import com.redhat.ceylon.model.loader.mirror.TypeMirror; import com.redhat.ceylon.model.loader.mirror.TypeParameterMirror; import com.redhat.ceylon.model.typechecker.model.Module; public class JDTClass implements IdeClassMirror, IBindingProvider { public static final ClassMirror UNKNOWN_CLASS = unknownClassMirror_.get_(); private static final short SUPERCLASS_MASK = 1; private static final short ENCLOSING_METHOD_MASK = 2; private static final short ENCLOSING_CLASS_MASK = 4; private static final short FUNCTIONAL_INTERFACE_MASK = 8; private static final short IS_INNER_TYPE_MASK = 16; private static final short IS_LOCAL_TYPE_MASK = 32; private static final short IS_BINARY_MASK = 64; private static final short IS_ANONYMOUS_MASK = 128; private static final short IS_JAVA_SOURCE_MASK = 256; // A bit field that allows us to save memory by using the masks above private short properties = 0; Reference<ReferenceBinding> bindingRef; private PackageMirror pkg; private TypeMirror superclass; private List<MethodMirror> methods; private List<TypeMirror> interfaces; private Map<String, AnnotationMirror> annotations; private List<TypeParameterMirror> typeParams; private List<FieldMirror> fields; private String qualifiedName; private String flatName; private String simpleName; private List<ClassMirror> innerClasses; private String cacheKey; private JDTMethod enclosingMethod; private JDTClass enclosingClass; private String functionalInterface; private IType type = null; private int modifiers; private String fileName; private String javaModelPath; private String fullPath; private char[] bindingKey; private String sourceFileName=null; private static final Map<String, AnnotationMirror> noAnnotations = Collections.emptyMap(); public JDTClass(ReferenceBinding klass, IType type, ClassFileReader classFileReader) { this(klass, type); if (classFileReader != null) { sourceFileName = CharOperation.charToString(classFileReader.sourceFileName()); } } /* * the klass parameter should not be null * the type parameter might be null (in case of a * MissingBinaryType). In such a case, take care of * setting in the constructor all the lazy values calculated * from the type. */ public JDTClass(ReferenceBinding klass, IType type) { this.type = type; bindingRef = new SoftReference<ReferenceBinding>(klass); pkg = new JDTPackage(klass.getPackage()); simpleName = new String(klass.sourceName()); qualifiedName = JDTUtils.getFullyQualifiedName(klass); flatName = JDTUtils.getFlatName(klass); if (flatName.equals(qualifiedName)) { flatName = qualifiedName; } modifiers = klass.modifiers; if (klass.isLocalType()) { set(IS_LOCAL_TYPE_MASK); } if (klass.isBinaryBinding()) { set(IS_BINARY_MASK); } if (klass.isAnonymousType()) { set(IS_ANONYMOUS_MASK); } if ((klass instanceof SourceTypeBinding) && new String(((SourceTypeBinding) klass).getFileName()).endsWith(".java")) { set(IS_JAVA_SOURCE_MASK); } bindingKey = klass.computeUniqueKey(); char[] bindingFileName = klass.getFileName(); int start = CharOperation.lastIndexOf('/', bindingFileName) + 1; if (start == 0 || start < CharOperation.lastIndexOf('\\', bindingFileName)) start = CharOperation.lastIndexOf('\\', bindingFileName) + 1; fileName = new String(CharOperation.subarray(bindingFileName, start, -1)); int jarFileEntrySeparatorIndex = CharOperation.indexOf(IDependent.JAR_FILE_ENTRY_SEPARATOR, bindingFileName); if (jarFileEntrySeparatorIndex > 0) { char[] jarPart = CharOperation.subarray(bindingFileName, 0, jarFileEntrySeparatorIndex); IJavaElement jarPackageFragmentRoot = JavaCore.create(new String(jarPart)); String jarPath = jarPackageFragmentRoot.getPath().toOSString(); char[] entryPart = CharOperation.subarray(bindingFileName, jarFileEntrySeparatorIndex + 1, bindingFileName.length); fullPath = new StringBuilder(jarPath).append("!/").append(entryPart).toString(); } else { fullPath = new String(bindingFileName); } ReferenceBinding sourceOrClass = klass; if (! klass.isBinaryBinding()) { sourceOrClass = klass.outermostEnclosingType(); } char[] classFullName = new char[0]; for (char[] part : sourceOrClass.compoundName) { classFullName = CharOperation.concat(classFullName, part, '/'); } char[][] temp = CharOperation.splitOn('.', sourceOrClass.getFileName()); String extension = temp.length > 1 ? "." + new String(temp[temp.length-1]) : ""; javaModelPath = new String(classFullName) + extension; if (type == null) { annotations = new HashMap<>(); methods = Collections.emptyList(); interfaces = Collections.emptyList(); typeParams = Collections.emptyList(); fields = Collections.emptyList(); innerClasses = Collections.emptyList(); } } @Override public AnnotationMirror getAnnotation(String annotationType) { retrieveAnnotations(); return annotations.get(annotationType); } private synchronized void retrieveAnnotations() { if (annotations == null) { doWithBindings(new ActionOnClassBinding() { @Override public void doWithBinding(IType classModel, ReferenceBinding klass) { Map<String, AnnotationMirror> annots = JDTUtils.getAnnotations(klass.getAnnotations()); if (sourceFileName != null && qualifiedName.startsWith("ceylon.language") && annots.containsKey(com.redhat.ceylon.compiler.java.metadata.Ceylon.class.getName()) && sourceFileName.endsWith(".java")) { HashMap<String, Object> values = new HashMap<>(); values.put("backend", "jvm"); annots.put("ceylon.language.NativeAnnotation$annotation$", new JDTAnnotation(values)); } if (annots.isEmpty()) { annots = noAnnotations; } annotations = annots; if (getAnnotation(AbstractModelLoader.CEYLON_CONTAINER_ANNOTATION) != null || klass.isMemberType()) { set(IS_INNER_TYPE_MASK); } } }); } } @Override public Set<String> getAnnotationNames() { retrieveAnnotations(); return annotations.keySet(); } @Override public boolean isPublic() { return (this.modifiers & ClassFileConstants.AccPublic) != 0; } @Override public String getQualifiedName() { return qualifiedName; } @Override public String getFlatName() { return flatName; } @Override public String getName() { return simpleName; } @Override public PackageMirror getPackage() { return pkg; } @Override public boolean isInterface() { return (this.modifiers & ClassFileConstants.AccInterface) != 0; } @Override public boolean isAbstract() { return (this.modifiers & ClassFileConstants.AccAbstract) != 0; } @Override public boolean isProtected() { return (this.modifiers & ClassFileConstants.AccProtected) != 0; } @Override public boolean isDefaultAccess() { return (this.modifiers & (ClassFileConstants.AccPublic | ClassFileConstants.AccProtected | ClassFileConstants.AccPrivate)) == 0; } public boolean isDeprecated() { return (this.modifiers & ClassFileConstants.AccDeprecated) != 0; } public void doWithBindings(final ActionOnClassBinding action) { if (!doWithReferenceBinding(type, bindingRef.get(), action)) { doWithResolvedType(type, new ActionOnResolvedType() { @Override public void doWithBinding(ReferenceBinding classBinding) { bindingRef = new WeakReference<ReferenceBinding>(classBinding); action.doWithBinding(type, classBinding); } }); } } @Override public List<MethodMirror> getDirectMethods() { if (methods == null) { doWithBindings(new ActionOnClassBinding() { @Override public void doWithBinding(IType classModel, ReferenceBinding klass) { MethodBinding[] directMethods; directMethods = klass.methods(); methods = new ArrayList<MethodMirror>(directMethods.length); for(MethodBinding method : directMethods) { if(!method.isBridge() && !method.isSynthetic() && !method.isPrivate()) methods.add(new JDTMethod(JDTClass.this, method)); } } }); } return methods; } @Override public TypeMirror getSuperclass() { if (!isSet(SUPERCLASS_MASK)) { doWithBindings(new ActionOnClassBinding() { @Override public void doWithBinding(IType classModel, ReferenceBinding klass) { if (klass.isInterface() || "java.lang.Object".equals(getQualifiedName())) { superclass = null; } else { ReferenceBinding superClassBinding = klass.superclass(); if (superClassBinding != null) { superClassBinding = JDTUtils.inferTypeParametersFromSuperClass(klass, superClassBinding); superclass = JDTType.newJDTType(superClassBinding); } } } }); set(SUPERCLASS_MASK); } return superclass; } @Override public List<TypeMirror> getInterfaces() { if (interfaces == null) { doWithBindings(new ActionOnClassBinding() { @Override public void doWithBinding(IType classModel, ReferenceBinding klass) { ReferenceBinding[] superInterfaces = klass.superInterfaces(); interfaces = new ArrayList<TypeMirror>(superInterfaces.length); for(ReferenceBinding superInterface : superInterfaces) interfaces.add(JDTType.newJDTType(superInterface)); } }); } return interfaces; } @Override public List<TypeParameterMirror> getTypeParameters() { if (typeParams == null) { doWithBindings(new ActionOnClassBinding() { @Override public void doWithBinding(IType classModel, ReferenceBinding klass) { TypeVariableBinding[] typeParameters = klass.typeVariables(); typeParams = new ArrayList<TypeParameterMirror>(typeParameters.length); for(TypeVariableBinding parameter : typeParameters) typeParams.add(new JDTTypeParameter(parameter)); } }); } return typeParams; } private boolean isAnnotationPresent(Class<?> clazz) { return getAnnotation(clazz.getName()) != null; } @Override public boolean isCeylonToplevelAttribute() { return !isInnerClass() && isAnnotationPresent(com.redhat.ceylon.compiler.java.metadata.Attribute.class); } @Override public boolean isCeylonToplevelObject() { return !isInnerClass() && isAnnotationPresent(com.redhat.ceylon.compiler.java.metadata.Object.class); } @Override public boolean isCeylonToplevelMethod() { return !isInnerClass() && isAnnotationPresent(com.redhat.ceylon.compiler.java.metadata.Method.class); } @Override public boolean getIsCeylon() { return isAnnotationPresent(com.redhat.ceylon.compiler.java.metadata.Ceylon.class); } @Override public List<FieldMirror> getDirectFields() { if (fields == null) { doWithBindings(new ActionOnClassBinding() { @Override public void doWithBinding(IType classModel, ReferenceBinding klass) { FieldBinding[] directFields = klass.fields(); fields = new ArrayList<FieldMirror>(directFields.length); for(FieldBinding field : directFields){ if(!field.isSynthetic() && !field.isPrivate()){ fields.add(new JDTField(field)); } } } }); } return fields; } @Override public boolean isInnerClass() { retrieveAnnotations(); return isSet(IS_INNER_TYPE_MASK); } @Override public ClassMirror getEnclosingClass() { if(!isSet(ENCLOSING_CLASS_MASK)){ doWithBindings(new ActionOnClassBinding() { @Override public void doWithBinding(IType classModel, ReferenceBinding klass) { ReferenceBinding enclosingType = klass.enclosingType(); IType enclosingTypeModel = type.getDeclaringType(); if (enclosingType != null) { if (enclosingTypeModel == null) { throw new ModelResolutionException("JDT reference binding without a JDT IType element !"); } enclosingClass = new JDTClass(enclosingType, enclosingTypeModel); } else { enclosingClass = null; } } }); set(ENCLOSING_CLASS_MASK); } return enclosingClass; } @Override public MethodMirror getEnclosingMethod() { if(!isSet(ENCLOSING_METHOD_MASK)){ if(isSet(IS_LOCAL_TYPE_MASK)){ doWithBindings(new ActionOnClassBinding() { @Override public void doWithBinding(IType classModel, ReferenceBinding klass) { LocalTypeBinding localClass = (LocalTypeBinding) klass; MethodBinding enclosingMethodBinding = localClass.enclosingMethod; enclosingMethod = enclosingMethodBinding != null ? new JDTMethod(JDTClass.this, enclosingMethodBinding) : null; } }); } set(ENCLOSING_METHOD_MASK); } return enclosingMethod; } @Override public List<ClassMirror> getDirectInnerClasses() { if (innerClasses == null) { doWithBindings(new ActionOnClassBinding() { @Override public void doWithBinding(IType classModel, ReferenceBinding klass) { ReferenceBinding[] memberTypeBindings = klass.memberTypes(); innerClasses = new ArrayList<ClassMirror>(memberTypeBindings.length); for(ReferenceBinding memberTypeBinding : memberTypeBindings) { ReferenceBinding classBinding = memberTypeBinding; IType classTypeModel = toType(classBinding); innerClasses.add(new JDTClass(classBinding, classTypeModel)); } } }); } return innerClasses; } @Override public boolean isStatic() { return (this.modifiers & ClassFileConstants.AccStatic) != 0; } @Override public boolean isFinal() { return (this.modifiers & ClassFileConstants.AccFinal) != 0; } @Override public boolean isEnum() { return (this.modifiers & ClassFileConstants.AccEnum) != 0; } @Override public String getFileName() { return fileName; } @Override public boolean getIsBinary() { return isSet(IS_BINARY_MASK); } @Override public boolean isLoadedFromSource() { return isJavaSource(); } @Override public boolean isAnonymous() { return isSet(IS_ANONYMOUS_MASK); } @Override public boolean isJavaSource() { return isSet(IS_JAVA_SOURCE_MASK); } public String getJavaModelPath() { return javaModelPath; } public String getFullPath() { return fullPath; } @Override public boolean isAnnotationType() { return (this.modifiers & ClassFileConstants.AccAnnotation) != 0; } @Override public boolean isLocalClass() { return getAnnotation(AbstractModelLoader.CEYLON_LOCAL_CONTAINER_ANNOTATION) != null || isSet(IS_LOCAL_TYPE_MASK); } @Override public char[] getBindingKey() { return bindingKey; } @Override public String getCacheKey(Module module) { if(cacheKey == null){ String className = getQualifiedName(); cacheKey = AbstractModelLoader.getCacheKeyByModule(module, className); } return cacheKey; } public IType getType() { return type; } @Override public String toString() { return "[JDTClass: "+qualifiedName+" ( " + fileName + ")]"; } public String isFunctionalInterface() { if (!isSet(FUNCTIONAL_INTERFACE_MASK)) { doWithBindings(new ActionOnClassBinding() { @Override public void doWithBinding(IType classModel, ReferenceBinding klass) { try { LookupEnvironment environment = klass.fPackage.environment; Scope scope = new CompilationUnitScope( new CompilationUnitDeclaration( environment.problemReporter, null, 0), environment); MethodBinding method = klass.getSingleAbstractMethod(scope, true); if (method != null && method.isValidBinding() && ! JDTMethod.ignoreMethodInAncestorSearch(method)) { String name = CharOperation.charToString(method.selector); LookupEnvironmentUtilities.Provider modelLoader = modelJ2C().getLookupEnvironmentProvider(type); if(modelLoader != null && modelLoader.isGetter(method, name)) { name = NamingBase.getJavaAttributeName(name); } functionalInterface = name; } } catch(Exception e) { CeylonPlugin.log(Status.ERROR, "Exception when trying to retrieve Functional interface of type" + klass.debugName() + "\n -> functional interface search skipped:", e); } } }); set(FUNCTIONAL_INTERFACE_MASK); } return functionalInterface; } private boolean isSet(int mask) { return (properties & mask) == mask; } private void set(int mask) { properties |= mask; } }