Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 import org.clearsilver.HDF;
     18 
     19 import java.util.*;
     20 
     21 public class MethodInfo extends MemberInfo
     22 {
     23     public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() {
     24         public int compare(MethodInfo a, MethodInfo b) {
     25             return a.name().compareTo(b.name());
     26         }
     27     };
     28 
     29     private class InlineTags implements InheritedTags
     30     {
     31         public TagInfo[] tags()
     32         {
     33             return comment().tags();
     34         }
     35         public InheritedTags inherited()
     36         {
     37             MethodInfo m = findOverriddenMethod(name(), signature());
     38             if (m != null) {
     39                 return m.inlineTags();
     40             } else {
     41                 return null;
     42             }
     43         }
     44     }
     45 
     46     private static void addInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue)
     47     {
     48         for (ClassInfo i: ifaces) {
     49             queue.add(i);
     50         }
     51         for (ClassInfo i: ifaces) {
     52             addInterfaces(i.interfaces(), queue);
     53         }
     54     }
     55 
     56     // first looks for a superclass, and then does a breadth first search to
     57     // find the least far away match
     58     public MethodInfo findOverriddenMethod(String name, String signature)
     59     {
     60         if (mReturnType == null) {
     61             // ctor
     62             return null;
     63         }
     64         if (mOverriddenMethod != null) {
     65             return mOverriddenMethod;
     66         }
     67 
     68         ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
     69         addInterfaces(containingClass().interfaces(), queue);
     70         for (ClassInfo iface: queue) {
     71             for (MethodInfo me: iface.methods()) {
     72                 if (me.name().equals(name)
     73                         && me.signature().equals(signature)
     74                         && me.inlineTags().tags() != null
     75                         && me.inlineTags().tags().length > 0) {
     76                     return me;
     77                 }
     78             }
     79         }
     80         return null;
     81     }
     82 
     83     private static void addRealInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue)
     84     {
     85         for (ClassInfo i: ifaces) {
     86             queue.add(i);
     87             if (i.realSuperclass() != null &&  i.realSuperclass().isAbstract()) {
     88                 queue.add(i.superclass());
     89             }
     90         }
     91         for (ClassInfo i: ifaces) {
     92             addInterfaces(i.realInterfaces(), queue);
     93         }
     94     }
     95 
     96     public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) {
     97         if (mReturnType == null) {
     98         // ctor
     99         return null;
    100         }
    101         if (mOverriddenMethod != null) {
    102             return mOverriddenMethod;
    103         }
    104 
    105         ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
    106         if (containingClass().realSuperclass() != null &&
    107             containingClass().realSuperclass().isAbstract()) {
    108             queue.add(containingClass());
    109         }
    110         addInterfaces(containingClass().realInterfaces(), queue);
    111         for (ClassInfo iface: queue) {
    112             for (MethodInfo me: iface.methods()) {
    113                 if (me.name().equals(name)
    114                     && me.signature().equals(signature)
    115                     && me.inlineTags().tags() != null
    116                     && me.inlineTags().tags().length > 0
    117                     && notStrippable.contains(me.containingClass())) {
    118                 return me;
    119                 }
    120             }
    121         }
    122         return null;
    123     }
    124 
    125     public MethodInfo findSuperclassImplementation(HashSet notStrippable) {
    126         if (mReturnType == null) {
    127             // ctor
    128             return null;
    129         }
    130         if (mOverriddenMethod != null) {
    131             // Even if we're told outright that this was the overridden method, we want to
    132             // be conservative and ignore mismatches of parameter types -- they arise from
    133             // extending generic specializations, and we want to consider the derived-class
    134             // method to be a non-override.
    135             if (this.signature().equals(mOverriddenMethod.signature())) {
    136                 return mOverriddenMethod;
    137             }
    138         }
    139 
    140         ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
    141         if (containingClass().realSuperclass() != null &&
    142                 containingClass().realSuperclass().isAbstract()) {
    143             queue.add(containingClass());
    144         }
    145         addInterfaces(containingClass().realInterfaces(), queue);
    146         for (ClassInfo iface: queue) {
    147             for (MethodInfo me: iface.methods()) {
    148                 if (me.name().equals(this.name())
    149                         && me.signature().equals(this.signature())
    150                         && notStrippable.contains(me.containingClass())) {
    151                     return me;
    152                 }
    153             }
    154         }
    155         return null;
    156     }
    157 
    158     public ClassInfo findRealOverriddenClass(String name, String signature) {
    159         if (mReturnType == null) {
    160         // ctor
    161         return null;
    162         }
    163         if (mOverriddenMethod != null) {
    164             return mOverriddenMethod.mRealContainingClass;
    165         }
    166 
    167         ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
    168         if (containingClass().realSuperclass() != null &&
    169             containingClass().realSuperclass().isAbstract()) {
    170             queue.add(containingClass());
    171         }
    172         addInterfaces(containingClass().realInterfaces(), queue);
    173         for (ClassInfo iface: queue) {
    174             for (MethodInfo me: iface.methods()) {
    175                 if (me.name().equals(name)
    176                     && me.signature().equals(signature)
    177                     && me.inlineTags().tags() != null
    178                     && me.inlineTags().tags().length > 0) {
    179                 return iface;
    180                 }
    181             }
    182         }
    183         return null;
    184     }
    185 
    186     private class FirstSentenceTags implements InheritedTags
    187     {
    188         public TagInfo[] tags()
    189         {
    190             return comment().briefTags();
    191         }
    192         public InheritedTags inherited()
    193         {
    194             MethodInfo m = findOverriddenMethod(name(), signature());
    195             if (m != null) {
    196                 return m.firstSentenceTags();
    197             } else {
    198                 return null;
    199             }
    200         }
    201     }
    202 
    203     private class ReturnTags implements InheritedTags {
    204         public TagInfo[] tags() {
    205             return comment().returnTags();
    206         }
    207         public InheritedTags inherited() {
    208             MethodInfo m = findOverriddenMethod(name(), signature());
    209             if (m != null) {
    210                 return m.returnTags();
    211             } else {
    212                 return null;
    213             }
    214         }
    215     }
    216 
    217     public boolean isDeprecated() {
    218         boolean deprecated = false;
    219         if (!mDeprecatedKnown) {
    220             boolean commentDeprecated = (comment().deprecatedTags().length > 0);
    221             boolean annotationDeprecated = false;
    222             for (AnnotationInstanceInfo annotation : annotations()) {
    223                 if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) {
    224                     annotationDeprecated = true;
    225                     break;
    226                 }
    227             }
    228 
    229             if (commentDeprecated != annotationDeprecated) {
    230                 Errors.error(Errors.DEPRECATION_MISMATCH, position(),
    231                         "Method " + mContainingClass.qualifiedName() + "." + name()
    232                         + ": @Deprecated annotation and @deprecated doc tag do not match");
    233             }
    234 
    235             mIsDeprecated = commentDeprecated | annotationDeprecated;
    236             mDeprecatedKnown = true;
    237         }
    238         return mIsDeprecated;
    239     }
    240 
    241     public TypeInfo[] getTypeParameters(){
    242         return mTypeParameters;
    243     }
    244 
    245     public MethodInfo cloneForClass(ClassInfo newContainingClass) {
    246         MethodInfo result =  new MethodInfo(getRawCommentText(), mTypeParameters,
    247                 name(), signature(), newContainingClass, realContainingClass(),
    248                 isPublic(), isProtected(), isPackagePrivate(), isPrivate(), isFinal(), isStatic(),
    249                 isSynthetic(), mIsAbstract, mIsSynchronized, mIsNative, mIsAnnotationElement,
    250                 kind(), mFlatSignature, mOverriddenMethod,
    251                 mReturnType, mParameters, mThrownExceptions, position(), annotations());
    252         result.init(mDefaultAnnotationElementValue);
    253         return result;
    254     }
    255 
    256     public MethodInfo(String rawCommentText, TypeInfo[] typeParameters, String name,
    257                         String signature, ClassInfo containingClass, ClassInfo realContainingClass,
    258                         boolean isPublic, boolean isProtected,
    259                         boolean isPackagePrivate, boolean isPrivate,
    260                         boolean isFinal, boolean isStatic, boolean isSynthetic,
    261                         boolean isAbstract, boolean isSynchronized, boolean isNative,
    262                         boolean isAnnotationElement, String kind,
    263                         String flatSignature, MethodInfo overriddenMethod,
    264                         TypeInfo returnType, ParameterInfo[] parameters,
    265                         ClassInfo[] thrownExceptions, SourcePositionInfo position,
    266                         AnnotationInstanceInfo[] annotations)
    267     {
    268         // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match
    269         // the Java5-emitted base API description.
    270         super(rawCommentText, name, signature, containingClass, realContainingClass,
    271                 isPublic, isProtected, isPackagePrivate, isPrivate,
    272                 ((name.equals("values") && containingClass.isEnum()) ? true : isFinal),
    273                 isStatic, isSynthetic, kind, position, annotations);
    274 
    275         // The underlying MethodDoc for an interface's declared methods winds up being marked
    276         // non-abstract.  Correct that here by looking at the immediate-parent class, and marking
    277         // this method abstract if it is an unimplemented interface method.
    278         if (containingClass.isInterface()) {
    279             isAbstract = true;
    280         }
    281 
    282         mReasonOpened = "0:0";
    283         mIsAnnotationElement = isAnnotationElement;
    284         mTypeParameters = typeParameters;
    285         mIsAbstract = isAbstract;
    286         mIsSynchronized = isSynchronized;
    287         mIsNative = isNative;
    288         mFlatSignature = flatSignature;
    289         mOverriddenMethod = overriddenMethod;
    290         mReturnType = returnType;
    291         mParameters = parameters;
    292         mThrownExceptions = thrownExceptions;
    293     }
    294 
    295     public void init(AnnotationValueInfo defaultAnnotationElementValue)
    296     {
    297         mDefaultAnnotationElementValue = defaultAnnotationElementValue;
    298     }
    299 
    300     public boolean isAbstract()
    301     {
    302         return mIsAbstract;
    303     }
    304 
    305     public boolean isSynchronized()
    306     {
    307         return mIsSynchronized;
    308     }
    309 
    310     public boolean isNative()
    311     {
    312         return mIsNative;
    313     }
    314 
    315     public String flatSignature()
    316     {
    317         return mFlatSignature;
    318     }
    319 
    320     public InheritedTags inlineTags()
    321     {
    322         return new InlineTags();
    323     }
    324 
    325     public InheritedTags firstSentenceTags()
    326     {
    327         return new FirstSentenceTags();
    328     }
    329 
    330     public InheritedTags returnTags() {
    331         return new ReturnTags();
    332     }
    333 
    334     public TypeInfo returnType()
    335     {
    336         return mReturnType;
    337     }
    338 
    339     public String prettySignature()
    340     {
    341         String s = "(";
    342         int N = mParameters.length;
    343         for (int i=0; i<N; i++) {
    344             ParameterInfo p = mParameters[i];
    345             TypeInfo t = p.type();
    346             if (t.isPrimitive()) {
    347                 s += t.simpleTypeName();
    348             } else {
    349                 s += t.asClassInfo().name();
    350             }
    351             if (i != N-1) {
    352                 s += ',';
    353             }
    354         }
    355         s += ')';
    356         return s;
    357     }
    358 
    359     /**
    360      * Returns a name consistent with the {@link
    361      * com.android.apicheck.MethodInfo#getHashableName()}.
    362      */
    363     public String getHashableName() {
    364         StringBuilder result = new StringBuilder();
    365         result.append(name());
    366         for (int p = 0; p < mParameters.length; p++) {
    367             result.append(":");
    368             if (p == mParameters.length - 1 && isVarArgs()) {
    369                 // TODO: note that this does not attempt to handle hypothetical
    370                 // vararg methods whose last parameter is a list of arrays, e.g.
    371                 // "Object[]...".
    372                 result.append(mParameters[p].type().fullNameNoDimension(typeVariables()))
    373                         .append("...");
    374             } else {
    375                 result.append(mParameters[p].type().fullName(typeVariables()));
    376             }
    377         }
    378         return result.toString();
    379     }
    380 
    381     private boolean inList(ClassInfo item, ThrowsTagInfo[] list)
    382     {
    383         int len = list.length;
    384         String qn = item.qualifiedName();
    385         for (int i=0; i<len; i++) {
    386             ClassInfo ex = list[i].exception();
    387             if (ex != null && ex.qualifiedName().equals(qn)) {
    388                 return true;
    389             }
    390         }
    391         return false;
    392     }
    393 
    394     public ThrowsTagInfo[] throwsTags()
    395     {
    396         if (mThrowsTags == null) {
    397             ThrowsTagInfo[] documented = comment().throwsTags();
    398             ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>();
    399 
    400             int len = documented.length;
    401             for (int i=0; i<len; i++) {
    402                 rv.add(documented[i]);
    403             }
    404 
    405             ClassInfo[] all = mThrownExceptions;
    406             len = all.length;
    407             for (int i=0; i<len; i++) {
    408                 ClassInfo cl = all[i];
    409                 if (documented == null || !inList(cl, documented)) {
    410                     rv.add(new ThrowsTagInfo("@throws", "@throws",
    411                                         cl.qualifiedName(), cl, "",
    412                                         containingClass(), position()));
    413                 }
    414             }
    415             mThrowsTags = rv.toArray(new ThrowsTagInfo[rv.size()]);
    416         }
    417         return mThrowsTags;
    418     }
    419 
    420     private static int indexOfParam(String name, String[] list)
    421     {
    422         final int N = list.length;
    423         for (int i=0; i<N; i++) {
    424             if (name.equals(list[i])) {
    425                 return i;
    426             }
    427         }
    428         return -1;
    429     }
    430 
    431     public ParamTagInfo[] paramTags()
    432     {
    433         if (mParamTags == null) {
    434             final int N = mParameters.length;
    435 
    436             String[] names = new String[N];
    437             String[] comments = new String[N];
    438             SourcePositionInfo[] positions = new SourcePositionInfo[N];
    439 
    440             // get the right names so we can handle our names being different from
    441             // our parent's names.
    442             for (int i=0; i<N; i++) {
    443                 names[i] = mParameters[i].name();
    444                 comments[i] = "";
    445                 positions[i] = mParameters[i].position();
    446             }
    447 
    448             // gather our comments, and complain about misnamed @param tags
    449             for (ParamTagInfo tag: comment().paramTags()) {
    450                 int index = indexOfParam(tag.parameterName(), names);
    451                 if (index >= 0) {
    452                     comments[index] = tag.parameterComment();
    453                     positions[index] = tag.position();
    454                 } else {
    455                     Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(),
    456                             "@param tag with name that doesn't match the parameter list: '"
    457                             + tag.parameterName() + "'");
    458                 }
    459             }
    460 
    461             // get our parent's tags to fill in the blanks
    462             MethodInfo overridden = this.findOverriddenMethod(name(), signature());
    463             if (overridden != null) {
    464                 ParamTagInfo[] maternal = overridden.paramTags();
    465                 for (int i=0; i<N; i++) {
    466                     if (comments[i].equals("")) {
    467                         comments[i] = maternal[i].parameterComment();
    468                         positions[i] = maternal[i].position();
    469                     }
    470                 }
    471             }
    472 
    473             // construct the results, and cache them for next time
    474             mParamTags = new ParamTagInfo[N];
    475             for (int i=0; i<N; i++) {
    476                 mParamTags[i] = new ParamTagInfo("@param", "@param", names[i] + " " + comments[i],
    477                         parent(), positions[i]);
    478 
    479                 // while we're here, if we find any parameters that are still undocumented at this
    480                 // point, complain. (this warning is off by default, because it's really, really
    481                 // common; but, it's good to be able to enforce it)
    482                 if (comments[i].equals("")) {
    483                     Errors.error(Errors.UNDOCUMENTED_PARAMETER, positions[i],
    484                             "Undocumented parameter '" + names[i] + "' on method '"
    485                             + name() + "'");
    486                 }
    487             }
    488         }
    489         return mParamTags;
    490     }
    491 
    492     public SeeTagInfo[] seeTags()
    493     {
    494         SeeTagInfo[] result = comment().seeTags();
    495         if (result == null) {
    496             if (mOverriddenMethod != null) {
    497                 result = mOverriddenMethod.seeTags();
    498             }
    499         }
    500         return result;
    501     }
    502 
    503     public TagInfo[] deprecatedTags()
    504     {
    505         TagInfo[] result = comment().deprecatedTags();
    506         if (result.length == 0) {
    507             if (comment().undeprecateTags().length == 0) {
    508                 if (mOverriddenMethod != null) {
    509                     result = mOverriddenMethod.deprecatedTags();
    510                 }
    511             }
    512         }
    513         return result;
    514     }
    515 
    516     public ParameterInfo[] parameters()
    517     {
    518         return mParameters;
    519     }
    520 
    521 
    522     public boolean matchesParams(String[] params, String[] dimensions)
    523     {
    524         if (mParamStrings == null) {
    525             ParameterInfo[] mine = mParameters;
    526             int len = mine.length;
    527             if (len != params.length) {
    528                 return false;
    529             }
    530             for (int i=0; i<len; i++) {
    531                 TypeInfo t = mine[i].type();
    532                 if (!t.dimension().equals(dimensions[i])) {
    533                     return false;
    534                 }
    535                 String qn = t.qualifiedTypeName();
    536                 String s = params[i];
    537                 int slen = s.length();
    538                 int qnlen = qn.length();
    539                 if (!(qn.equals(s) ||
    540                         ((slen+1)<qnlen && qn.charAt(qnlen-slen-1)=='.'
    541                          && qn.endsWith(s)))) {
    542                     return false;
    543                 }
    544             }
    545         }
    546         return true;
    547     }
    548 
    549     public void makeHDF(HDF data, String base)
    550     {
    551         data.setValue(base + ".kind", kind());
    552         data.setValue(base + ".name", name());
    553         data.setValue(base + ".href", htmlPage());
    554         data.setValue(base + ".anchor", anchor());
    555 
    556         if (mReturnType != null) {
    557             returnType().makeHDF(data, base + ".returnType", false, typeVariables());
    558             data.setValue(base + ".abstract", mIsAbstract ? "abstract" : "");
    559         }
    560 
    561         data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : "");
    562         data.setValue(base + ".final", isFinal() ? "final" : "");
    563         data.setValue(base + ".static", isStatic() ? "static" : "");
    564 
    565         TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
    566         TagInfo.makeHDF(data, base + ".descr", inlineTags());
    567         TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags());
    568         TagInfo.makeHDF(data, base + ".seeAlso", seeTags());
    569         data.setValue(base + ".since", getSince());
    570         ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags());
    571         AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags());
    572         ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags());
    573         ParameterInfo.makeHDF(data, base + ".params", parameters(), isVarArgs(), typeVariables());
    574         if (isProtected()) {
    575             data.setValue(base + ".scope", "protected");
    576         }
    577         else if (isPublic()) {
    578             data.setValue(base + ".scope", "public");
    579         }
    580         TagInfo.makeHDF(data, base + ".returns", returnTags());
    581 
    582         if (mTypeParameters != null) {
    583             TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false);
    584         }
    585     }
    586 
    587     public HashSet<String> typeVariables()
    588     {
    589         HashSet<String> result = TypeInfo.typeVariables(mTypeParameters);
    590         ClassInfo cl = containingClass();
    591         while (cl != null) {
    592             TypeInfo[] types = cl.asTypeInfo().typeArguments();
    593             if (types != null) {
    594                 TypeInfo.typeVariables(types, result);
    595             }
    596             cl = cl.containingClass();
    597         }
    598         return result;
    599     }
    600 
    601     @Override
    602     public boolean isExecutable()
    603     {
    604         return true;
    605     }
    606 
    607     public ClassInfo[] thrownExceptions()
    608     {
    609         return mThrownExceptions;
    610     }
    611 
    612     public String typeArgumentsName(HashSet<String> typeVars)
    613     {
    614         if (mTypeParameters == null || mTypeParameters.length == 0) {
    615             return "";
    616         } else {
    617             return TypeInfo.typeArgumentsName(mTypeParameters, typeVars);
    618         }
    619     }
    620 
    621     public boolean isAnnotationElement()
    622     {
    623         return mIsAnnotationElement;
    624     }
    625 
    626     public AnnotationValueInfo defaultAnnotationElementValue()
    627     {
    628         return mDefaultAnnotationElementValue;
    629     }
    630 
    631     public void setVarargs(boolean set){
    632         mIsVarargs = set;
    633     }
    634     public boolean isVarArgs(){
    635       return mIsVarargs;
    636     }
    637 
    638     @Override
    639     public String toString(){
    640       return this.name();
    641     }
    642 
    643     public void setReason(String reason) {
    644         mReasonOpened = reason;
    645     }
    646 
    647     public String getReason() {
    648         return mReasonOpened;
    649     }
    650 
    651     private String mFlatSignature;
    652     private MethodInfo mOverriddenMethod;
    653     private TypeInfo mReturnType;
    654     private boolean mIsAnnotationElement;
    655     private boolean mIsAbstract;
    656     private boolean mIsSynchronized;
    657     private boolean mIsNative;
    658     private boolean mIsVarargs;
    659     private boolean mDeprecatedKnown;
    660     private boolean mIsDeprecated;
    661     private ParameterInfo[] mParameters;
    662     private ClassInfo[] mThrownExceptions;
    663     private String[] mParamStrings;
    664     ThrowsTagInfo[] mThrowsTags;
    665     private ParamTagInfo[] mParamTags;
    666     private TypeInfo[] mTypeParameters;
    667     private AnnotationValueInfo mDefaultAnnotationElementValue;
    668     private String mReasonOpened;
    669 }
    670 
    671