Home | History | Annotate | Download | only in src
      1 // Copyright 2009 Google Inc. All Rights Reserved.
      2 
      3 import com.android.apicheck.ApiCheck;
      4 import com.android.apicheck.ApiInfo;
      5 
      6 import java.util.ArrayList;
      7 import java.util.LinkedHashMap;
      8 import java.util.List;
      9 import java.util.Map;
     10 import java.util.Collections;
     11 
     12 import org.clearsilver.HDF;
     13 
     14 /**
     15  * Applies version information to the DroidDoc class model from apicheck XML
     16  * files. Sample usage:
     17  * <pre>
     18  *   ClassInfo[] classInfos = ...
     19  *
     20  *   SinceTagger sinceTagger = new SinceTagger()
     21  *   sinceTagger.addVersion("frameworks/base/api/1.xml", "Android 1.0")
     22  *   sinceTagger.addVersion("frameworks/base/api/2.xml", "Android 1.5")
     23  *   sinceTagger.tagAll(...);
     24  * </pre>
     25  */
     26 public class SinceTagger {
     27 
     28     private final Map<String, String> xmlToName
     29             = new LinkedHashMap<String, String>();
     30 
     31     /**
     32      * Specifies the apicheck XML file and the API version it holds. Calls to
     33      * this method should be called in order from oldest version to newest.
     34      */
     35     public void addVersion(String file, String name) {
     36         xmlToName.put(file, name);
     37     }
     38 
     39     public void tagAll(ClassInfo[] classDocs) {
     40         // read through the XML files in order, applying their since information
     41         // to the Javadoc models
     42         for (Map.Entry<String, String> versionSpec : xmlToName.entrySet()) {
     43             String xmlFile = versionSpec.getKey();
     44             String versionName = versionSpec.getValue();
     45             ApiInfo specApi = new ApiCheck().parseApi(xmlFile);
     46 
     47             applyVersionsFromSpec(versionName, specApi, classDocs);
     48         }
     49 
     50         if (!xmlToName.isEmpty()) {
     51             warnForMissingVersions(classDocs);
     52         }
     53     }
     54 
     55     /**
     56      * Writes an index of the version names to {@code data}.
     57      */
     58     public void writeVersionNames(HDF data) {
     59         int index = 1;
     60         for (String version : xmlToName.values()) {
     61             data.setValue("since." + index + ".name", version);
     62             index++;
     63         }
     64     }
     65 
     66     /**
     67      * Applies the version information to {@code classDocs} where not already
     68      * present.
     69      *
     70      * @param versionName the version name
     71      * @param specApi the spec for this version. If a symbol is in this spec, it
     72      *      was present in the named version
     73      * @param classDocs the doc model to update
     74      */
     75     private void applyVersionsFromSpec(String versionName,
     76             ApiInfo specApi, ClassInfo[] classDocs) {
     77         for (ClassInfo classDoc : classDocs) {
     78             com.android.apicheck.PackageInfo packageSpec
     79                     = specApi.getPackages().get(classDoc.containingPackage().name());
     80 
     81             if (packageSpec == null) {
     82                 continue;
     83             }
     84 
     85             com.android.apicheck.ClassInfo classSpec
     86                     = packageSpec.allClasses().get(classDoc.name());
     87 
     88             if (classSpec == null) {
     89                 continue;
     90             }
     91 
     92             versionPackage(versionName, classDoc.containingPackage());
     93             versionClass(versionName, classDoc);
     94             versionConstructors(versionName, classSpec, classDoc);
     95             versionFields(versionName, classSpec, classDoc);
     96             versionMethods(versionName, classSpec, classDoc);
     97         }
     98     }
     99 
    100     /**
    101      * Applies version information to {@code doc} where not already present.
    102      */
    103     private void versionPackage(String versionName, PackageInfo doc) {
    104         if (doc.getSince() == null) {
    105             doc.setSince(versionName);
    106         }
    107     }
    108 
    109     /**
    110      * Applies version information to {@code doc} where not already present.
    111      */
    112     private void versionClass(String versionName, ClassInfo doc) {
    113         if (doc.getSince() == null) {
    114             doc.setSince(versionName);
    115         }
    116     }
    117 
    118     /**
    119      * Applies version information from {@code spec} to {@code doc} where not
    120      * already present.
    121      */
    122     private void versionConstructors(String versionName,
    123             com.android.apicheck.ClassInfo spec, ClassInfo doc) {
    124         for (MethodInfo constructor : doc.constructors()) {
    125             if (constructor.getSince() == null
    126                     && spec.allConstructors().containsKey(constructor.getHashableName())) {
    127                 constructor.setSince(versionName);
    128             }
    129         }
    130     }
    131 
    132     /**
    133      * Applies version information from {@code spec} to {@code doc} where not
    134      * already present.
    135      */
    136     private void versionFields(String versionName,
    137             com.android.apicheck.ClassInfo spec, ClassInfo doc) {
    138         for (FieldInfo field : doc.fields()) {
    139             if (field.getSince() == null
    140                     && spec.allFields().containsKey(field.name())) {
    141                 field.setSince(versionName);
    142             }
    143         }
    144     }
    145 
    146     /**
    147      * Applies version information from {@code spec} to {@code doc} where not
    148      * already present.
    149      */
    150     private void versionMethods(String versionName,
    151             com.android.apicheck.ClassInfo spec, ClassInfo doc) {
    152         for (MethodInfo method : doc.methods()) {
    153             if (method.getSince() != null) {
    154                 continue;
    155             }
    156 
    157             for (com.android.apicheck.ClassInfo superclass : spec.hierarchy()) {
    158                 if (superclass.allMethods().containsKey(method.getHashableName())) {
    159                     method.setSince(versionName);
    160                     break;
    161                 }
    162             }
    163         }
    164     }
    165 
    166     /**
    167      * Warns if any symbols are missing version information. When configured
    168      * properly, this will yield zero warnings because {@code apicheck}
    169      * guarantees that all symbols are present in the most recent API.
    170      */
    171     private void warnForMissingVersions(ClassInfo[] classDocs) {
    172         for (ClassInfo claz : classDocs) {
    173             if (!checkLevelRecursive(claz)) {
    174                 continue;
    175             }
    176 
    177             if (claz.getSince() == null) {
    178                 Errors.error(Errors.NO_SINCE_DATA, claz.position(),
    179                         "XML missing class " + claz.qualifiedName());
    180             }
    181 
    182             for (FieldInfo field : missingVersions(claz.fields())) {
    183                 Errors.error(Errors.NO_SINCE_DATA, field.position(),
    184                         "XML missing field " + claz.qualifiedName()
    185                                 + "#" + field.name());
    186             }
    187 
    188             for (MethodInfo constructor : missingVersions(claz.constructors())) {
    189                 Errors.error(Errors.NO_SINCE_DATA, constructor.position(),
    190                         "XML missing constructor " + claz.qualifiedName()
    191                                 + "#" + constructor.getHashableName());
    192             }
    193 
    194             for (MethodInfo method : missingVersions(claz.methods())) {
    195                 Errors.error(Errors.NO_SINCE_DATA, method.position(),
    196                         "XML missing method " + claz.qualifiedName()
    197                                 + "#" + method.getHashableName());
    198             }
    199         }
    200     }
    201 
    202     /**
    203      * Returns the DocInfos in {@code all} that are documented but do not have
    204      * since tags.
    205      */
    206     private <T extends MemberInfo> Iterable<T> missingVersions(T[] all) {
    207         List<T> result = Collections.emptyList();
    208         for (T t : all) {
    209             // if this member has version info or isn't documented, skip it
    210             if (t.getSince() != null
    211                     || t.isHidden()
    212                     || !checkLevelRecursive(t.realContainingClass())) {
    213                 continue;
    214             }
    215 
    216             if (result.isEmpty()) {
    217                 result = new ArrayList<T>(); // lazily construct a mutable list
    218             }
    219             result.add(t);
    220         }
    221         return result;
    222     }
    223 
    224     /**
    225      * Returns true if {@code claz} and all containing classes are documented.
    226      * The result may be used to filter out members that exist in the API
    227      * data structure but aren't a part of the API.
    228      */
    229     private boolean checkLevelRecursive(ClassInfo claz) {
    230         for (ClassInfo c = claz; c != null; c = c.containingClass()) {
    231             if (!c.checkLevel()) {
    232                 return false;
    233             }
    234         }
    235         return true;
    236     }
    237 }
    238