| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import org.clearsilver.HDF; |
| |
| import java.util.*; |
| |
| public class MethodInfo extends MemberInfo |
| { |
| public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() { |
| public int compare(MethodInfo a, MethodInfo b) { |
| return a.name().compareTo(b.name()); |
| } |
| }; |
| |
| private class InlineTags implements InheritedTags |
| { |
| public TagInfo[] tags() |
| { |
| return comment().tags(); |
| } |
| public InheritedTags inherited() |
| { |
| MethodInfo m = findOverriddenMethod(name(), signature()); |
| if (m != null) { |
| return m.inlineTags(); |
| } else { |
| return null; |
| } |
| } |
| } |
| |
| private static void addInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue) |
| { |
| for (ClassInfo i: ifaces) { |
| queue.add(i); |
| } |
| for (ClassInfo i: ifaces) { |
| addInterfaces(i.interfaces(), queue); |
| } |
| } |
| |
| // first looks for a superclass, and then does a breadth first search to |
| // find the least far away match |
| public MethodInfo findOverriddenMethod(String name, String signature) |
| { |
| if (mReturnType == null) { |
| // ctor |
| return null; |
| } |
| if (mOverriddenMethod != null) { |
| return mOverriddenMethod; |
| } |
| |
| ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); |
| addInterfaces(containingClass().interfaces(), queue); |
| for (ClassInfo iface: queue) { |
| for (MethodInfo me: iface.methods()) { |
| if (me.name().equals(name) |
| && me.signature().equals(signature) |
| && me.inlineTags().tags() != null |
| && me.inlineTags().tags().length > 0) { |
| return me; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private static void addRealInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue) |
| { |
| for (ClassInfo i: ifaces) { |
| queue.add(i); |
| if (i.realSuperclass() != null && i.realSuperclass().isAbstract()) { |
| queue.add(i.superclass()); |
| } |
| } |
| for (ClassInfo i: ifaces) { |
| addInterfaces(i.realInterfaces(), queue); |
| } |
| } |
| |
| public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) { |
| if (mReturnType == null) { |
| // ctor |
| return null; |
| } |
| if (mOverriddenMethod != null) { |
| return mOverriddenMethod; |
| } |
| |
| ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); |
| if (containingClass().realSuperclass() != null && |
| containingClass().realSuperclass().isAbstract()) { |
| queue.add(containingClass()); |
| } |
| addInterfaces(containingClass().realInterfaces(), queue); |
| for (ClassInfo iface: queue) { |
| for (MethodInfo me: iface.methods()) { |
| if (me.name().equals(name) |
| && me.signature().equals(signature) |
| && me.inlineTags().tags() != null |
| && me.inlineTags().tags().length > 0 |
| && notStrippable.contains(me.containingClass())) { |
| return me; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public MethodInfo findSuperclassImplementation(HashSet notStrippable) { |
| if (mReturnType == null) { |
| // ctor |
| return null; |
| } |
| if (mOverriddenMethod != null) { |
| // Even if we're told outright that this was the overridden method, we want to |
| // be conservative and ignore mismatches of parameter types -- they arise from |
| // extending generic specializations, and we want to consider the derived-class |
| // method to be a non-override. |
| if (this.signature().equals(mOverriddenMethod.signature())) { |
| return mOverriddenMethod; |
| } |
| } |
| |
| ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); |
| if (containingClass().realSuperclass() != null && |
| containingClass().realSuperclass().isAbstract()) { |
| queue.add(containingClass()); |
| } |
| addInterfaces(containingClass().realInterfaces(), queue); |
| for (ClassInfo iface: queue) { |
| for (MethodInfo me: iface.methods()) { |
| if (me.name().equals(this.name()) |
| && me.signature().equals(this.signature()) |
| && notStrippable.contains(me.containingClass())) { |
| return me; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public ClassInfo findRealOverriddenClass(String name, String signature) { |
| if (mReturnType == null) { |
| // ctor |
| return null; |
| } |
| if (mOverriddenMethod != null) { |
| return mOverriddenMethod.mRealContainingClass; |
| } |
| |
| ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>(); |
| if (containingClass().realSuperclass() != null && |
| containingClass().realSuperclass().isAbstract()) { |
| queue.add(containingClass()); |
| } |
| addInterfaces(containingClass().realInterfaces(), queue); |
| for (ClassInfo iface: queue) { |
| for (MethodInfo me: iface.methods()) { |
| if (me.name().equals(name) |
| && me.signature().equals(signature) |
| && me.inlineTags().tags() != null |
| && me.inlineTags().tags().length > 0) { |
| return iface; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private class FirstSentenceTags implements InheritedTags |
| { |
| public TagInfo[] tags() |
| { |
| return comment().briefTags(); |
| } |
| public InheritedTags inherited() |
| { |
| MethodInfo m = findOverriddenMethod(name(), signature()); |
| if (m != null) { |
| return m.firstSentenceTags(); |
| } else { |
| return null; |
| } |
| } |
| } |
| |
| private class ReturnTags implements InheritedTags { |
| public TagInfo[] tags() { |
| return comment().returnTags(); |
| } |
| public InheritedTags inherited() { |
| MethodInfo m = findOverriddenMethod(name(), signature()); |
| if (m != null) { |
| return m.returnTags(); |
| } else { |
| return null; |
| } |
| } |
| } |
| |
| public boolean isDeprecated() { |
| boolean deprecated = false; |
| if (!mDeprecatedKnown) { |
| boolean commentDeprecated = (comment().deprecatedTags().length > 0); |
| boolean annotationDeprecated = false; |
| for (AnnotationInstanceInfo annotation : annotations()) { |
| if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) { |
| annotationDeprecated = true; |
| break; |
| } |
| } |
| |
| if (commentDeprecated != annotationDeprecated) { |
| Errors.error(Errors.DEPRECATION_MISMATCH, position(), |
| "Method " + mContainingClass.qualifiedName() + "." + name() |
| + ": @Deprecated annotation and @deprecated doc tag do not match"); |
| } |
| |
| mIsDeprecated = commentDeprecated | annotationDeprecated; |
| mDeprecatedKnown = true; |
| } |
| return mIsDeprecated; |
| } |
| |
| public TypeInfo[] getTypeParameters(){ |
| return mTypeParameters; |
| } |
| |
| public MethodInfo cloneForClass(ClassInfo newContainingClass) { |
| MethodInfo result = new MethodInfo(getRawCommentText(), mTypeParameters, |
| name(), signature(), newContainingClass, realContainingClass(), |
| isPublic(), isProtected(), isPackagePrivate(), isPrivate(), isFinal(), isStatic(), |
| isSynthetic(), mIsAbstract, mIsSynchronized, mIsNative, mIsAnnotationElement, |
| kind(), mFlatSignature, mOverriddenMethod, |
| mReturnType, mParameters, mThrownExceptions, position(), annotations()); |
| result.init(mDefaultAnnotationElementValue); |
| return result; |
| } |
| |
| public MethodInfo(String rawCommentText, TypeInfo[] typeParameters, String name, |
| String signature, ClassInfo containingClass, ClassInfo realContainingClass, |
| boolean isPublic, boolean isProtected, |
| boolean isPackagePrivate, boolean isPrivate, |
| boolean isFinal, boolean isStatic, boolean isSynthetic, |
| boolean isAbstract, boolean isSynchronized, boolean isNative, |
| boolean isAnnotationElement, String kind, |
| String flatSignature, MethodInfo overriddenMethod, |
| TypeInfo returnType, ParameterInfo[] parameters, |
| ClassInfo[] thrownExceptions, SourcePositionInfo position, |
| AnnotationInstanceInfo[] annotations) |
| { |
| // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match |
| // the Java5-emitted base API description. |
| super(rawCommentText, name, signature, containingClass, realContainingClass, |
| isPublic, isProtected, isPackagePrivate, isPrivate, |
| ((name.equals("values") && containingClass.isEnum()) ? true : isFinal), |
| isStatic, isSynthetic, kind, position, annotations); |
| |
| // The underlying MethodDoc for an interface's declared methods winds up being marked |
| // non-abstract. Correct that here by looking at the immediate-parent class, and marking |
| // this method abstract if it is an unimplemented interface method. |
| if (containingClass.isInterface()) { |
| isAbstract = true; |
| } |
| |
| mReasonOpened = "0:0"; |
| mIsAnnotationElement = isAnnotationElement; |
| mTypeParameters = typeParameters; |
| mIsAbstract = isAbstract; |
| mIsSynchronized = isSynchronized; |
| mIsNative = isNative; |
| mFlatSignature = flatSignature; |
| mOverriddenMethod = overriddenMethod; |
| mReturnType = returnType; |
| mParameters = parameters; |
| mThrownExceptions = thrownExceptions; |
| } |
| |
| public void init(AnnotationValueInfo defaultAnnotationElementValue) |
| { |
| mDefaultAnnotationElementValue = defaultAnnotationElementValue; |
| } |
| |
| public boolean isAbstract() |
| { |
| return mIsAbstract; |
| } |
| |
| public boolean isSynchronized() |
| { |
| return mIsSynchronized; |
| } |
| |
| public boolean isNative() |
| { |
| return mIsNative; |
| } |
| |
| public String flatSignature() |
| { |
| return mFlatSignature; |
| } |
| |
| public InheritedTags inlineTags() |
| { |
| return new InlineTags(); |
| } |
| |
| public InheritedTags firstSentenceTags() |
| { |
| return new FirstSentenceTags(); |
| } |
| |
| public InheritedTags returnTags() { |
| return new ReturnTags(); |
| } |
| |
| public TypeInfo returnType() |
| { |
| return mReturnType; |
| } |
| |
| public String prettySignature() |
| { |
| String s = "("; |
| int N = mParameters.length; |
| for (int i=0; i<N; i++) { |
| ParameterInfo p = mParameters[i]; |
| TypeInfo t = p.type(); |
| if (t.isPrimitive()) { |
| s += t.simpleTypeName(); |
| } else { |
| s += t.asClassInfo().name(); |
| } |
| if (i != N-1) { |
| s += ','; |
| } |
| } |
| s += ')'; |
| return s; |
| } |
| |
| /** |
| * Returns a name consistent with the {@link |
| * com.android.apicheck.MethodInfo#getHashableName()}. |
| */ |
| public String getHashableName() { |
| StringBuilder result = new StringBuilder(); |
| result.append(name()); |
| for (int p = 0; p < mParameters.length; p++) { |
| result.append(":"); |
| if (p == mParameters.length - 1 && isVarArgs()) { |
| // TODO: note that this does not attempt to handle hypothetical |
| // vararg methods whose last parameter is a list of arrays, e.g. |
| // "Object[]...". |
| result.append(mParameters[p].type().fullNameNoDimension(typeVariables())) |
| .append("..."); |
| } else { |
| result.append(mParameters[p].type().fullName(typeVariables())); |
| } |
| } |
| return result.toString(); |
| } |
| |
| private boolean inList(ClassInfo item, ThrowsTagInfo[] list) |
| { |
| int len = list.length; |
| String qn = item.qualifiedName(); |
| for (int i=0; i<len; i++) { |
| ClassInfo ex = list[i].exception(); |
| if (ex != null && ex.qualifiedName().equals(qn)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public ThrowsTagInfo[] throwsTags() |
| { |
| if (mThrowsTags == null) { |
| ThrowsTagInfo[] documented = comment().throwsTags(); |
| ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>(); |
| |
| int len = documented.length; |
| for (int i=0; i<len; i++) { |
| rv.add(documented[i]); |
| } |
| |
| ClassInfo[] all = mThrownExceptions; |
| len = all.length; |
| for (int i=0; i<len; i++) { |
| ClassInfo cl = all[i]; |
| if (documented == null || !inList(cl, documented)) { |
| rv.add(new ThrowsTagInfo("@throws", "@throws", |
| cl.qualifiedName(), cl, "", |
| containingClass(), position())); |
| } |
| } |
| mThrowsTags = rv.toArray(new ThrowsTagInfo[rv.size()]); |
| } |
| return mThrowsTags; |
| } |
| |
| private static int indexOfParam(String name, String[] list) |
| { |
| final int N = list.length; |
| for (int i=0; i<N; i++) { |
| if (name.equals(list[i])) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| public ParamTagInfo[] paramTags() |
| { |
| if (mParamTags == null) { |
| final int N = mParameters.length; |
| |
| String[] names = new String[N]; |
| String[] comments = new String[N]; |
| SourcePositionInfo[] positions = new SourcePositionInfo[N]; |
| |
| // get the right names so we can handle our names being different from |
| // our parent's names. |
| for (int i=0; i<N; i++) { |
| names[i] = mParameters[i].name(); |
| comments[i] = ""; |
| positions[i] = mParameters[i].position(); |
| } |
| |
| // gather our comments, and complain about misnamed @param tags |
| for (ParamTagInfo tag: comment().paramTags()) { |
| int index = indexOfParam(tag.parameterName(), names); |
| if (index >= 0) { |
| comments[index] = tag.parameterComment(); |
| positions[index] = tag.position(); |
| } else { |
| Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(), |
| "@param tag with name that doesn't match the parameter list: '" |
| + tag.parameterName() + "'"); |
| } |
| } |
| |
| // get our parent's tags to fill in the blanks |
| MethodInfo overridden = this.findOverriddenMethod(name(), signature()); |
| if (overridden != null) { |
| ParamTagInfo[] maternal = overridden.paramTags(); |
| for (int i=0; i<N; i++) { |
| if (comments[i].equals("")) { |
| comments[i] = maternal[i].parameterComment(); |
| positions[i] = maternal[i].position(); |
| } |
| } |
| } |
| |
| // construct the results, and cache them for next time |
| mParamTags = new ParamTagInfo[N]; |
| for (int i=0; i<N; i++) { |
| mParamTags[i] = new ParamTagInfo("@param", "@param", names[i] + " " + comments[i], |
| parent(), positions[i]); |
| |
| // while we're here, if we find any parameters that are still undocumented at this |
| // point, complain. (this warning is off by default, because it's really, really |
| // common; but, it's good to be able to enforce it) |
| if (comments[i].equals("")) { |
| Errors.error(Errors.UNDOCUMENTED_PARAMETER, positions[i], |
| "Undocumented parameter '" + names[i] + "' on method '" |
| + name() + "'"); |
| } |
| } |
| } |
| return mParamTags; |
| } |
| |
| public SeeTagInfo[] seeTags() |
| { |
| SeeTagInfo[] result = comment().seeTags(); |
| if (result == null) { |
| if (mOverriddenMethod != null) { |
| result = mOverriddenMethod.seeTags(); |
| } |
| } |
| return result; |
| } |
| |
| public TagInfo[] deprecatedTags() |
| { |
| TagInfo[] result = comment().deprecatedTags(); |
| if (result.length == 0) { |
| if (comment().undeprecateTags().length == 0) { |
| if (mOverriddenMethod != null) { |
| result = mOverriddenMethod.deprecatedTags(); |
| } |
| } |
| } |
| return result; |
| } |
| |
| public ParameterInfo[] parameters() |
| { |
| return mParameters; |
| } |
| |
| |
| public boolean matchesParams(String[] params, String[] dimensions) |
| { |
| if (mParamStrings == null) { |
| ParameterInfo[] mine = mParameters; |
| int len = mine.length; |
| if (len != params.length) { |
| return false; |
| } |
| for (int i=0; i<len; i++) { |
| TypeInfo t = mine[i].type(); |
| if (!t.dimension().equals(dimensions[i])) { |
| return false; |
| } |
| String qn = t.qualifiedTypeName(); |
| String s = params[i]; |
| int slen = s.length(); |
| int qnlen = qn.length(); |
| if (!(qn.equals(s) || |
| ((slen+1)<qnlen && qn.charAt(qnlen-slen-1)=='.' |
| && qn.endsWith(s)))) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| public void makeHDF(HDF data, String base) |
| { |
| data.setValue(base + ".kind", kind()); |
| data.setValue(base + ".name", name()); |
| data.setValue(base + ".href", htmlPage()); |
| data.setValue(base + ".anchor", anchor()); |
| |
| if (mReturnType != null) { |
| returnType().makeHDF(data, base + ".returnType", false, typeVariables()); |
| data.setValue(base + ".abstract", mIsAbstract ? "abstract" : ""); |
| } |
| |
| data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : ""); |
| data.setValue(base + ".final", isFinal() ? "final" : ""); |
| data.setValue(base + ".static", isStatic() ? "static" : ""); |
| |
| TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags()); |
| TagInfo.makeHDF(data, base + ".descr", inlineTags()); |
| TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags()); |
| TagInfo.makeHDF(data, base + ".seeAlso", seeTags()); |
| data.setValue(base + ".since", getSince()); |
| ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags()); |
| AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags()); |
| ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags()); |
| ParameterInfo.makeHDF(data, base + ".params", parameters(), isVarArgs(), typeVariables()); |
| if (isProtected()) { |
| data.setValue(base + ".scope", "protected"); |
| } |
| else if (isPublic()) { |
| data.setValue(base + ".scope", "public"); |
| } |
| TagInfo.makeHDF(data, base + ".returns", returnTags()); |
| |
| if (mTypeParameters != null) { |
| TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false); |
| } |
| } |
| |
| public HashSet<String> typeVariables() |
| { |
| HashSet<String> result = TypeInfo.typeVariables(mTypeParameters); |
| ClassInfo cl = containingClass(); |
| while (cl != null) { |
| TypeInfo[] types = cl.asTypeInfo().typeArguments(); |
| if (types != null) { |
| TypeInfo.typeVariables(types, result); |
| } |
| cl = cl.containingClass(); |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean isExecutable() |
| { |
| return true; |
| } |
| |
| public ClassInfo[] thrownExceptions() |
| { |
| return mThrownExceptions; |
| } |
| |
| public String typeArgumentsName(HashSet<String> typeVars) |
| { |
| if (mTypeParameters == null || mTypeParameters.length == 0) { |
| return ""; |
| } else { |
| return TypeInfo.typeArgumentsName(mTypeParameters, typeVars); |
| } |
| } |
| |
| public boolean isAnnotationElement() |
| { |
| return mIsAnnotationElement; |
| } |
| |
| public AnnotationValueInfo defaultAnnotationElementValue() |
| { |
| return mDefaultAnnotationElementValue; |
| } |
| |
| public void setVarargs(boolean set){ |
| mIsVarargs = set; |
| } |
| public boolean isVarArgs(){ |
| return mIsVarargs; |
| } |
| |
| @Override |
| public String toString(){ |
| return this.name(); |
| } |
| |
| public void setReason(String reason) { |
| mReasonOpened = reason; |
| } |
| |
| public String getReason() { |
| return mReasonOpened; |
| } |
| |
| private String mFlatSignature; |
| private MethodInfo mOverriddenMethod; |
| private TypeInfo mReturnType; |
| private boolean mIsAnnotationElement; |
| private boolean mIsAbstract; |
| private boolean mIsSynchronized; |
| private boolean mIsNative; |
| private boolean mIsVarargs; |
| private boolean mDeprecatedKnown; |
| private boolean mIsDeprecated; |
| private ParameterInfo[] mParameters; |
| private ClassInfo[] mThrownExceptions; |
| private String[] mParamStrings; |
| ThrowsTagInfo[] mThrowsTags; |
| private ParamTagInfo[] mParamTags; |
| private TypeInfo[] mTypeParameters; |
| private AnnotationValueInfo mDefaultAnnotationElementValue; |
| private String mReasonOpened; |
| } |
| |