/* * 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 static com.redhat.ceylon.eclipse.core.model.LookupEnvironmentUtilities.doWithMethodBinding; import static com.redhat.ceylon.eclipse.core.model.LookupEnvironmentUtilities.doWithResolvedType; import static java.lang.Character.toLowerCase; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding; import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.jdt.internal.compiler.lookup.MethodVerifier; import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding; import com.redhat.ceylon.common.JVMModuleUtil; import com.redhat.ceylon.compiler.java.metadata.Ignore; import com.redhat.ceylon.compiler.java.metadata.Name; import com.redhat.ceylon.eclipse.core.model.LookupEnvironmentUtilities.ActionOnMethodBinding; import com.redhat.ceylon.eclipse.core.model.LookupEnvironmentUtilities.ActionOnResolvedType; import com.redhat.ceylon.model.loader.AbstractModelLoader; import com.redhat.ceylon.model.loader.ModelResolutionException; import com.redhat.ceylon.model.loader.mirror.AnnotationMirror; import com.redhat.ceylon.model.loader.mirror.ClassMirror; import com.redhat.ceylon.model.loader.mirror.MethodMirror; import com.redhat.ceylon.model.loader.mirror.TypeMirror; import com.redhat.ceylon.model.loader.mirror.TypeParameterMirror; import com.redhat.ceylon.model.loader.mirror.VariableMirror; public class JDTMethod implements MethodMirror, IBindingProvider { private static final short IS_OVERRIDING_MASK = 1; private static final short IS_OVERRIDING_SET_MASK = 2; private static final short IS_OVERLOADING_MASK = 4; private static final short IS_OVERLOADING_SET_MASK = 8; private static final short IS_CONSTRUCTOR_MASK = 16; private static final short IS_STATIC_INIT_MASK = 32; private static final short IS_DECLARED_VOID_MASK = 64; private static final short IS_VARIADIC_MASK = 128; private static final short IS_DEFAULT_MASK = 256; private static final short IS_DEFAULT_METHOD_MASK = 512; // A bit field that allows us to save memory by using the masks above private short properties = 0; private Reference<MethodBinding> bindingRef; private Map<String, AnnotationMirror> annotations; private String name; private List<VariableMirror> parameters; private TypeMirror returnType; private List<TypeParameterMirror> typeParameters; private JDTClass enclosingClass; private int modifiers; private char[] bindingKey; private String readableName; private static final Map<String, AnnotationMirror> noAnnotations = Collections.emptyMap(); public JDTMethod(JDTClass enclosingClass, MethodBinding method) { this.enclosingClass = enclosingClass; bindingRef = new SoftReference<MethodBinding>(method); name = new String(method.selector); readableName = new String(method.readableName()); modifiers = method.modifiers; if (method.isConstructor()) { set(IS_CONSTRUCTOR_MASK); } if (method.selector == TypeConstants.CLINIT) { // TODO : check if it is right set(IS_STATIC_INIT_MASK); } if (method.returnType.id == TypeIds.T_void) { set(IS_DECLARED_VOID_MASK); } if (method.isVarargs()) { set(IS_VARIADIC_MASK); } if (method.getDefaultValue()!=null) { set(IS_DEFAULT_MASK); } if (method.isDefaultMethod()) { set(IS_DEFAULT_METHOD_MASK); } bindingKey = method.computeUniqueKey(); if (method instanceof ProblemMethodBinding) { annotations = new HashMap<>(); parameters = Collections.emptyList(); returnType = JDTType.UNKNOWN_TYPE; typeParameters = Collections.emptyList(); set(IS_OVERRIDING_SET_MASK); set(IS_OVERLOADING_SET_MASK); } } @Override public AnnotationMirror getAnnotation(String type) { retrieveAnnotations(); return annotations.get(type); } private void retrieveAnnotations() { if (annotations == null) { doWithBindings(new ActionOnMethodBinding() { @Override public void doWithBinding(IType declaringClassModel, ReferenceBinding declaringClass, MethodBinding method) { Map<String, AnnotationMirror> annots = JDTUtils.getAnnotations(method.getAnnotations()); if (annots.isEmpty()) { annotations = noAnnotations; } else { annotations = annots; } } }); } } @Override public Set<String> getAnnotationNames() { retrieveAnnotations(); return annotations.keySet(); } @Override public String getName() { return name; } @Override public boolean isStatic() { return (this.modifiers & ClassFileConstants.AccStatic) != 0; } @Override public boolean isPublic() { return (this.modifiers & ClassFileConstants.AccPublic) != 0; } @Override public boolean isConstructor() { return isSet(IS_CONSTRUCTOR_MASK); } @Override public boolean isStaticInit() { return isSet(IS_STATIC_INIT_MASK); } @Override public List<VariableMirror> getParameters() { if (parameters == null) { doWithBindings(new ActionOnMethodBinding() { private String toParameterName(TypeBinding parameterType) { String typeName = new String(parameterType.sourceName()); StringTokenizer tokens = new StringTokenizer(typeName, "$.[]"); String result = null; while (tokens.hasMoreTokens()) { result = tokens.nextToken(); } if (typeName.endsWith("[]")) { result = result + "Array"; } return toLowerCase(result.charAt(0)) + result.substring(1); } @Override public void doWithBinding(IType declaringClassModel, ReferenceBinding declaringClassBinding, MethodBinding methodBinding) { TypeBinding[] parameterBindings; AnnotationBinding[][] parameterAnnotationBindings; parameterBindings = ((MethodBinding)methodBinding).parameters; String[] parameterNames = null; parameterAnnotationBindings = ((MethodBinding)methodBinding).getParameterAnnotations(); if (parameterAnnotationBindings == null) { parameterAnnotationBindings = new AnnotationBinding[parameterBindings.length][]; for (int i=0; i<parameterAnnotationBindings.length; i++) { parameterAnnotationBindings[i] = new AnnotationBinding[0]; } } parameters = new ArrayList<VariableMirror>(parameterBindings.length); List<String> givenNames = new ArrayList<>(parameterBindings.length); for(int i=0;i<parameterBindings.length;i++) { Map<String, AnnotationMirror> parameterAnnotations = JDTUtils.getAnnotations(parameterAnnotationBindings[i]); String parameterName; AnnotationMirror nameAnnotation = parameterAnnotations.get(Name.class.getName()); AnnotationMirror ignoredAnnotation = parameterAnnotations.get(Ignore.class.getName()); TypeBinding parameterTypeBinding = parameterBindings[i]; if(nameAnnotation != null) { parameterName = (String) nameAnnotation.getValue(); givenNames.add(parameterName); } else { String baseName = null; if (ignoredAnnotation == null) { if (parameterNames == null) { try { for (IMethod imethod : declaringClassModel.getMethods()) { if (new String(methodBinding.signature()).equals(imethod.getSignature())) { parameterNames = imethod.getParameterNames(); break; } } } catch (JavaModelException e) { } if (parameterNames == null) { parameterNames = new String[0]; } } if (parameterNames.length > i) { baseName = parameterNames[i]; } } if (baseName == null || baseName.isEmpty()){ baseName = toParameterName(parameterTypeBinding); } int count = 0; String nameToReturn = baseName; for (String givenName : givenNames) { if (givenName.equals(nameToReturn)) { count ++; nameToReturn = baseName + Integer.toString(count); } } parameterName = nameToReturn; givenNames.add(parameterName); if (JVMModuleUtil.isJavaKeyword(parameterName)) { parameterName = parameterName + "_"; } } parameters.add(new JDTVariable(parameterName, JDTType.newJDTType(parameterTypeBinding), parameterAnnotations)); } } }); } return parameters; } @Override public boolean isAbstract() { return (this.modifiers & ClassFileConstants.AccAbstract) != 0; } @Override public boolean isFinal() { return (this.modifiers & ClassFileConstants.AccFinal) != 0; } @Override public TypeMirror getReturnType() { if (returnType == null) { doWithBindings(new ActionOnMethodBinding() { @Override public void doWithBinding(IType declaringClassModel, ReferenceBinding declaringClassBinding, MethodBinding methodBinding) { returnType = JDTType.newJDTType(methodBinding.returnType); } }); } return returnType; } @Override public List<TypeParameterMirror> getTypeParameters() { if (typeParameters == null) { doWithBindings(new ActionOnMethodBinding() { @Override public void doWithBinding(IType declaringClassModel, ReferenceBinding declaringClassBinding, MethodBinding methodBinding) { TypeVariableBinding[] jdtTypeParameters = methodBinding.typeVariables(); typeParameters = new ArrayList<TypeParameterMirror>(jdtTypeParameters.length); for(TypeVariableBinding jdtTypeParameter : jdtTypeParameters) typeParameters.add(new JDTTypeParameter(jdtTypeParameter)); } }); } return typeParameters; } public boolean isOverridingMethod() { if (!isSet(IS_OVERRIDING_SET_MASK)) { doWithBindings(new ActionOnMethodBinding() { @Override public void doWithBinding(IType declaringClassModel, ReferenceBinding declaringClass, MethodBinding method) { if (CharOperation.equals(declaringClass.readableName(), "ceylon.language.Identifiable".toCharArray())) { if ("equals".equals(name) || "hashCode".equals(name)) { set(IS_OVERRIDING_MASK); return; } } if (CharOperation.equals(declaringClass.readableName(), "ceylon.language.Object".toCharArray())) { if ("equals".equals(name) || "hashCode".equals(name) || "toString".equals(name)) { //isOverriding = false; return; } } // try the superclass first if (isDefinedInSuperClass(declaringClass, method)) { set(IS_OVERRIDING_MASK); } if (isDefinedInSuperInterfaces(declaringClass, method)) { set(IS_OVERRIDING_MASK); } } }); set(IS_OVERRIDING_SET_MASK); } return isSet(IS_OVERRIDING_MASK); } private void doWithBindings(final ActionOnMethodBinding action) { final IType declaringClassModel = enclosingClass.getType(); if (!doWithMethodBinding(declaringClassModel, bindingRef.get(), action)) { doWithResolvedType(declaringClassModel, new ActionOnResolvedType() { @Override public void doWithBinding(ReferenceBinding declaringClass) { MethodBinding method = null; for (MethodBinding m : declaringClass.methods()) { if (CharOperation.equals(m.computeUniqueKey(), bindingKey)) { method = m; break; } } if (method == null) { throw new ModelResolutionException("Function '" + readableName + "' not found in the binding of class '" + declaringClassModel.getFullyQualifiedName() + "'"); } bindingRef = new SoftReference<MethodBinding>(method); action.doWithBinding(declaringClassModel, declaringClass, method); } }); } } public boolean isOverloadingMethod() { if (!isSet(IS_OVERLOADING_SET_MASK)) { doWithBindings(new ActionOnMethodBinding() { @Override public void doWithBinding(IType declaringClassModel, ReferenceBinding declaringClass, MethodBinding method) { // Exception has a pretend supertype of Object, unlike its Java supertype of java.lang.RuntimeException // so we stop there for it, especially since it does not have any overloading if(CharOperation.equals(declaringClass.qualifiedSourceName(), "ceylon.language.Exception".toCharArray())) { //isOverloading = false; return; } // try the superclass first if (isOverloadingInSuperClasses(declaringClass, method)) { set(IS_OVERLOADING_MASK); } if (isOverloadingInSuperInterfaces(declaringClass, method)) { set(IS_OVERLOADING_MASK); } } }); set(IS_OVERLOADING_SET_MASK); } return isSet(IS_OVERLOADING_MASK); } public static boolean ignoreMethodInAncestorSearch(MethodBinding methodBinding) { String name = CharOperation.charToString(methodBinding.selector); if(name.equals("finalize") || name.equals("clone")){ if(methodBinding.declaringClass != null && CharOperation.toString(methodBinding.declaringClass.compoundName).equals("java.lang.Object")) { return true; } } // skip ignored methods too if(JDTUtils.hasAnnotation(methodBinding, AbstractModelLoader.CEYLON_IGNORE_ANNOTATION)) { return true; } return false; } private boolean isDefinedInType(ReferenceBinding superClass, MethodBinding method) { MethodVerifier methodVerifier = superClass.getPackage().environment.methodVerifier(); for (MethodBinding inheritedMethod : superClass.methods()) { // skip ignored methods if(ignoreMethodInAncestorSearch(inheritedMethod)) { continue; } if (methodVerifier.doesMethodOverride(method, inheritedMethod)) { return true; } } return false; } private boolean isOverloadingInType(ReferenceBinding superClass, MethodBinding method) { MethodVerifier methodVerifier = superClass.getPackage().environment.methodVerifier(); for (MethodBinding inheritedMethod : superClass.methods()) { if(inheritedMethod.isPrivate() || inheritedMethod.isStatic() || inheritedMethod.isConstructor() || inheritedMethod.isBridge() || inheritedMethod.isSynthetic() || !Arrays.equals(inheritedMethod.constantPoolName(), method.selector)) continue; // skip ignored methods if(ignoreMethodInAncestorSearch(inheritedMethod)) { continue; } // if it does not override it and has the same name, it's overloading if (!methodVerifier.doesMethodOverride(method, inheritedMethod)) { return true; } } return false; } boolean isDefinedInSuperClass(ReferenceBinding declaringClass, MethodBinding method) { ReferenceBinding superClass = declaringClass.superclass(); if (superClass == null) { return false; } superClass = JDTUtils.inferTypeParametersFromSuperClass(declaringClass, superClass); if (isDefinedInType(superClass, method)) { return true; } if (isDefinedInSuperInterfaces(superClass, method)) { return true; } return isDefinedInSuperClass(superClass, method); } boolean isDefinedInSuperInterfaces(ReferenceBinding declaringType, MethodBinding method) { ReferenceBinding[] superInterfaces = declaringType.superInterfaces(); if (superInterfaces == null) { return false; } for (ReferenceBinding superInterface : superInterfaces) { if (isDefinedInType(superInterface, method)) { return true; } if (isDefinedInSuperInterfaces(superInterface, method)) { return true; } } return false; } boolean isOverloadingInSuperClasses(ReferenceBinding declaringClass, MethodBinding method) { ReferenceBinding superClass = declaringClass.superclass(); if (superClass == null) { return false; } // Exception has a pretend supertype of Object, unlike its Java supertype of java.lang.RuntimeException // so we stop there for it, especially since it does not have any overloading if(CharOperation.equals(superClass.qualifiedSourceName(), "ceylon.language.Exception".toCharArray())) return false; superClass = JDTUtils.inferTypeParametersFromSuperClass(declaringClass, superClass); if (isOverloadingInType(superClass, method)) { return true; } return isOverloadingInSuperClasses(superClass, method); } boolean isOverloadingInSuperInterfaces(ReferenceBinding declaringType, MethodBinding method) { ReferenceBinding[] superInterfaces = declaringType.superInterfaces(); if (superInterfaces == null) { return false; } for (ReferenceBinding superInterface : superInterfaces) { if (isOverloadingInType(superInterface, method)) { return true; } if (isOverloadingInSuperInterfaces(superInterface, method)) { return true; } } return false; } @Override public boolean isProtected() { return (this.modifiers & ClassFileConstants.AccProtected) != 0; } @Override public boolean isDefaultAccess() { return !isPublic() && !isProtected() && !isPrivate(); } public final boolean isPrivate() { return (this.modifiers & ClassFileConstants.AccPrivate) != 0; } public boolean isDeprecated() { return (this.modifiers & ClassFileConstants.AccDeprecated) != 0; } @Override public boolean isDeclaredVoid() { return isSet(IS_DECLARED_VOID_MASK); } @Override public boolean isVariadic() { return isSet(IS_VARIADIC_MASK); } @Override public boolean isDefault() { return isSet(IS_DEFAULT_MASK); } @Override public char[] getBindingKey() { return bindingKey; } @Override public ClassMirror getEnclosingClass() { return enclosingClass; } @Override public boolean isDefaultMethod() { return isSet(IS_DEFAULT_METHOD_MASK); } private boolean isSet(int mask) { return (properties & mask) == mask; } private void set(int mask) { properties |= mask; } }