Home | History | Annotate | Download | only in doclava
      1 /*
      2  * Copyright (C) 2010 Google Inc.
      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 package com.google.doclava;
     18 
     19 import com.google.doclava.apicheck.ApiInfo;
     20 import com.google.clearsilver.jsilver.data.Data;
     21 import com.sun.javadoc.*;
     22 
     23 import java.util.*;
     24 
     25 public class PackageInfo extends DocInfo implements ContainerInfo {
     26   public static final String DEFAULT_PACKAGE = "default package";
     27 
     28   public static final Comparator<PackageInfo> comparator = new Comparator<PackageInfo>() {
     29     public int compare(PackageInfo a, PackageInfo b) {
     30       return a.name().compareTo(b.name());
     31     }
     32   };
     33 
     34   public PackageInfo(PackageDoc pkg, String name, SourcePositionInfo position) {
     35     super(pkg.getRawCommentText(), position);
     36     if (name.isEmpty()) {
     37       mName = DEFAULT_PACKAGE;
     38     } else {
     39       mName = name;
     40     }
     41 
     42     mPackage = pkg;
     43     initializeMaps();
     44   }
     45 
     46   public PackageInfo(String name) {
     47     super("", null);
     48     mName = name;
     49     initializeMaps();
     50   }
     51 
     52   public PackageInfo(String name, SourcePositionInfo position) {
     53     super("", position);
     54 
     55     if (name.isEmpty()) {
     56       mName = "default package";
     57     } else {
     58       mName = name;
     59     }
     60     initializeMaps();
     61   }
     62 
     63   private void initializeMaps() {
     64       mAnnotationsMap = new HashMap<String, ClassInfo>();
     65       mInterfacesMap = new HashMap<String, ClassInfo>();
     66       mOrdinaryClassesMap = new HashMap<String, ClassInfo>();
     67       mEnumsMap = new HashMap<String, ClassInfo>();
     68       mExceptionsMap = new HashMap<String, ClassInfo>();
     69       mErrorsMap = new HashMap<String, ClassInfo>();
     70   }
     71 
     72   public String htmlPage() {
     73     String s = mName;
     74     s = s.replace('.', '/');
     75     s += "/package-summary.html";
     76     s = Doclava.javadocDir + s;
     77     return s;
     78   }
     79 
     80   @Override
     81   public ContainerInfo parent() {
     82     return null;
     83   }
     84 
     85   @Override
     86   public boolean isHidden() {
     87     if (mHidden == null) {
     88       if (hasHideComment()) {
     89         // We change the hidden value of the package if a class wants to be not hidden.
     90         ClassInfo[][] types = new ClassInfo[][] { annotations(), interfaces(), ordinaryClasses(),
     91             enums(), exceptions() };
     92         for (ClassInfo[] type : types) {
     93           if (type != null) {
     94             for (ClassInfo c : type) {
     95               if (c.hasShowAnnotation()) {
     96                 mHidden = false;
     97                 return false;
     98               }
     99             }
    100           }
    101         }
    102         mHidden = true;
    103       } else {
    104         mHidden = false;
    105       }
    106     }
    107     return mHidden;
    108   }
    109 
    110   public void setHidden(boolean hidden) {
    111     mHidden = hidden;
    112   }
    113 
    114   @Override
    115   public boolean isRemoved() {
    116     if (mRemoved == null) {
    117       if (hasRemovedComment()) {
    118         // We change the removed value of the package if a class wants to be not hidden.
    119         ClassInfo[][] types = new ClassInfo[][] { annotations(), interfaces(), ordinaryClasses(),
    120             enums(), exceptions() };
    121         for (ClassInfo[] type : types) {
    122           if (type != null) {
    123             for (ClassInfo c : type) {
    124               if (c.hasShowAnnotation()) {
    125                 mRemoved = false;
    126                 return false;
    127               }
    128             }
    129           }
    130         }
    131         mRemoved = true;
    132       } else {
    133         mRemoved = false;
    134       }
    135     }
    136 
    137     return mRemoved;
    138   }
    139 
    140   @Override
    141   public boolean isHiddenOrRemoved() {
    142     return isHidden() || isRemoved();
    143   }
    144 
    145   /**
    146    * Used by ClassInfo to determine packages default visability before annoations.
    147    */
    148   public boolean hasHideComment() {
    149     if (mHiddenByComment == null) {
    150       if (Doclava.hiddenPackages.contains(mName)) {
    151         mHiddenByComment = true;
    152       } else {
    153         mHiddenByComment = comment().isHidden();
    154       }
    155     }
    156     return mHiddenByComment;
    157   }
    158 
    159   public boolean hasRemovedComment() {
    160     if (mRemovedByComment == null) {
    161       mRemovedByComment = comment().isRemoved();
    162     }
    163 
    164     return mRemovedByComment;
    165   }
    166 
    167   public boolean checkLevel() {
    168     // TODO should return false if all classes are hidden but the package isn't.
    169     // We don't have this so I'm not doing it now.
    170     return !isHiddenOrRemoved();
    171   }
    172 
    173   public String name() {
    174     return mName;
    175   }
    176 
    177   public String qualifiedName() {
    178     return mName;
    179   }
    180 
    181   public TagInfo[] inlineTags() {
    182     return comment().tags();
    183   }
    184 
    185   public TagInfo[] firstSentenceTags() {
    186     return comment().briefTags();
    187   }
    188 
    189   /**
    190    * @param classes the Array of ClassInfo to be filtered
    191    * @return an Array of ClassInfo without any hidden or removed classes
    192    */
    193   public static ClassInfo[] filterHiddenAndRemoved(ClassInfo[] classes) {
    194     ArrayList<ClassInfo> out = new ArrayList<ClassInfo>();
    195 
    196     for (ClassInfo cl : classes) {
    197       if (!cl.isHiddenOrRemoved()) {
    198         out.add(cl);
    199       }
    200     }
    201 
    202     return out.toArray(new ClassInfo[0]);
    203   }
    204 
    205   public void makeLink(Data data, String base) {
    206     if (checkLevel()) {
    207       data.setValue(base + ".link", htmlPage());
    208     }
    209     data.setValue(base + ".name", name());
    210     data.setValue(base + ".since", getSince());
    211   }
    212 
    213   public void makeClassLinkListHDF(Data data, String base) {
    214     makeLink(data, base);
    215     ClassInfo.makeLinkListHDF(data, base + ".annotations", annotations());
    216     ClassInfo.makeLinkListHDF(data, base + ".interfaces", interfaces());
    217     ClassInfo.makeLinkListHDF(data, base + ".classes", ordinaryClasses());
    218     ClassInfo.makeLinkListHDF(data, base + ".enums", enums());
    219     ClassInfo.makeLinkListHDF(data, base + ".exceptions", exceptions());
    220     ClassInfo.makeLinkListHDF(data, base + ".errors", errors());
    221     data.setValue(base + ".since", getSince());
    222   }
    223 
    224   public ClassInfo[] annotations() {
    225     if (mAnnotations == null) {
    226       mAnnotations =
    227           ClassInfo.sortByName(filterHiddenAndRemoved(
    228               Converter.convertClasses(mPackage.annotationTypes())));
    229     }
    230     return mAnnotations;
    231   }
    232 
    233   public ClassInfo[] interfaces() {
    234     if (mInterfaces == null) {
    235       mInterfaces =
    236           ClassInfo.sortByName(filterHiddenAndRemoved(
    237               Converter.convertClasses(mPackage.interfaces())));
    238     }
    239     return mInterfaces;
    240   }
    241 
    242   public ClassInfo[] ordinaryClasses() {
    243     if (mOrdinaryClasses == null) {
    244       mOrdinaryClasses =
    245           ClassInfo.sortByName(filterHiddenAndRemoved(
    246               Converter.convertClasses(mPackage.ordinaryClasses())));
    247     }
    248     return mOrdinaryClasses;
    249   }
    250 
    251   public ClassInfo[] enums() {
    252     if (mEnums == null) {
    253       mEnums = ClassInfo.sortByName(filterHiddenAndRemoved(
    254           Converter.convertClasses(mPackage.enums())));
    255     }
    256     return mEnums;
    257   }
    258 
    259   public ClassInfo[] exceptions() {
    260     if (mExceptions == null) {
    261       mExceptions =
    262           ClassInfo.sortByName(filterHiddenAndRemoved(
    263               Converter.convertClasses(mPackage.exceptions())));
    264     }
    265     return mExceptions;
    266   }
    267 
    268   public ClassInfo[] errors() {
    269     if (mErrors == null) {
    270       mErrors = ClassInfo.sortByName(filterHiddenAndRemoved(
    271           Converter.convertClasses(mPackage.errors())));
    272     }
    273     return mErrors;
    274   }
    275 
    276   public ApiInfo containingApi() {
    277     return mContainingApi;
    278   }
    279 
    280   public void setContainingApi(ApiInfo api) {
    281     mContainingApi = api;
    282   }
    283 
    284   @Override
    285   public String toString() {
    286     return this.name();
    287   }
    288 
    289   @Override
    290   public boolean equals(Object o) {
    291     if (this == o) {
    292       return true;
    293     } else if (o instanceof PackageInfo) {
    294       final PackageInfo p = (PackageInfo) o;
    295       return mName.equals(p.mName);
    296     } else {
    297       return false;
    298     }
    299   }
    300 
    301   @Override
    302   public int hashCode() {
    303     return mName.hashCode();
    304   }
    305 
    306   private Boolean mHidden = null;
    307   private Boolean mHiddenByComment = null;
    308   private Boolean mRemoved = null;
    309   private Boolean mRemovedByComment = null;
    310   private String mName;
    311   private PackageDoc mPackage;
    312   private ApiInfo mContainingApi;
    313   private ClassInfo[] mAnnotations;
    314   private ClassInfo[] mInterfaces;
    315   private ClassInfo[] mOrdinaryClasses;
    316   private ClassInfo[] mEnums;
    317   private ClassInfo[] mExceptions;
    318   private ClassInfo[] mErrors;
    319 
    320   private HashMap<String, ClassInfo> mAnnotationsMap;
    321   private HashMap<String, ClassInfo> mInterfacesMap;
    322   private HashMap<String, ClassInfo> mOrdinaryClassesMap;
    323   private HashMap<String, ClassInfo> mEnumsMap;
    324   private HashMap<String, ClassInfo> mExceptionsMap;
    325   private HashMap<String, ClassInfo> mErrorsMap;
    326 
    327 
    328   public ClassInfo getClass(String className) {
    329       ClassInfo cls = mInterfacesMap.get(className);
    330 
    331       if (cls != null) {
    332           return cls;
    333       }
    334 
    335       cls = mOrdinaryClassesMap.get(className);
    336 
    337       if (cls != null) {
    338           return cls;
    339       }
    340 
    341       cls = mEnumsMap.get(className);
    342 
    343       if (cls != null) {
    344           return cls;
    345       }
    346 
    347       cls = mEnumsMap.get(className);
    348 
    349       if (cls != null) {
    350           return cls;
    351       }
    352       cls = mAnnotationsMap.get(className);
    353 
    354       if (cls != null) {
    355           return cls;
    356       }
    357 
    358       return mErrorsMap.get(className);
    359   }
    360 
    361   public void addAnnotation(ClassInfo cls) {
    362       cls.setPackage(this);
    363       mAnnotationsMap.put(cls.name(), cls);
    364   }
    365 
    366   public ClassInfo getAnnotation(String annotationName) {
    367       return mAnnotationsMap.get(annotationName);
    368   }
    369 
    370   public void addInterface(ClassInfo cls) {
    371       cls.setPackage(this);
    372       mInterfacesMap.put(cls.name(), cls);
    373   }
    374 
    375   public ClassInfo getInterface(String interfaceName) {
    376       return mInterfacesMap.get(interfaceName);
    377   }
    378 
    379   public ClassInfo getOrdinaryClass(String className) {
    380       return mOrdinaryClassesMap.get(className);
    381   }
    382 
    383   public void addOrdinaryClass(ClassInfo cls) {
    384       cls.setPackage(this);
    385       mOrdinaryClassesMap.put(cls.name(), cls);
    386   }
    387 
    388   public ClassInfo getEnum(String enumName) {
    389       return mEnumsMap.get(enumName);
    390   }
    391 
    392   public void addEnum(ClassInfo cls) {
    393       cls.setPackage(this);
    394       this.mEnumsMap.put(cls.name(), cls);
    395   }
    396 
    397   public ClassInfo getException(String exceptionName) {
    398       return mExceptionsMap.get(exceptionName);
    399   }
    400 
    401   public ClassInfo getError(String errorName) {
    402       return mErrorsMap.get(errorName);
    403   }
    404 
    405   // TODO: Leftovers from ApiCheck that should be better merged.
    406   private HashMap<String, ClassInfo> mClasses = new HashMap<String, ClassInfo>();
    407 
    408   public void addClass(ClassInfo cls) {
    409     cls.setPackage(this);
    410     mClasses.put(cls.name(), cls);
    411   }
    412 
    413   public HashMap<String, ClassInfo> allClasses() {
    414     return mClasses;
    415   }
    416 
    417   public boolean isConsistent(PackageInfo pInfo) {
    418     return isConsistent(pInfo, null);
    419   }
    420 
    421   /**
    422    * Creates the delta class by copying class signatures from original, but use provided list of
    423    * constructors and methods.
    424    */
    425   private ClassInfo createDeltaClass(ClassInfo original,
    426       ArrayList<MethodInfo> constructors, ArrayList<MethodInfo> methods) {
    427     ArrayList<FieldInfo> emptyFields = new ArrayList<>();
    428     ArrayList<ClassInfo> emptyClasses = new ArrayList<>();
    429     ArrayList<TypeInfo> emptyTypes = new ArrayList<>();
    430     ArrayList<MethodInfo> emptyMethods = new ArrayList<>();
    431     ClassInfo ret = new ClassInfo(null, original.getRawCommentText(), original.position(),
    432         original.isPublic(), original.isProtected(), original.isPackagePrivate(),
    433         original.isPrivate(), original.isStatic(), original.isInterface(),
    434         original.isAbstract(), original.isOrdinaryClass(),
    435         original.isException(), original.isError(), original.isEnum(), original.isAnnotation(),
    436         original.isFinal(), original.isIncluded(), original.name(), original.qualifiedName(),
    437         original.qualifiedTypeName(), original.isPrimitive());
    438     ArrayList<ClassInfo> interfaces = original.interfaces();
    439     // avoid providing null to init method, replace with empty array list when needed
    440     if (interfaces == null) {
    441       interfaces = emptyClasses;
    442     }
    443     ArrayList<TypeInfo> interfaceTypes = original.interfaceTypes();
    444     if (interfaceTypes == null) {
    445       interfaceTypes = emptyTypes;
    446     }
    447     ArrayList<ClassInfo> innerClasses = original.innerClasses();
    448     if (innerClasses == null) {
    449       innerClasses = emptyClasses;
    450     }
    451     ArrayList<MethodInfo> annotationElements = original.annotationElements();
    452     if (annotationElements == null) {
    453       annotationElements = emptyMethods;
    454     }
    455     ArrayList<AnnotationInstanceInfo> annotations = original.annotations();
    456     if (annotations == null) {
    457       annotations = new ArrayList<>();
    458     }
    459     ret.init(original.type(), interfaces, interfaceTypes, innerClasses,
    460         constructors, methods, annotationElements,
    461         emptyFields /* fields */, emptyFields /* enum */,
    462         original.containingPackage(), original.containingClass(), original.superclass(),
    463         original.superclassType(), annotations);
    464     return ret;
    465   }
    466 
    467   /**
    468    * Check if packages are consistent, also record class deltas.
    469    * <p>
    470    * <ul>class deltas are:
    471    * <li>brand new classes that are not present in current package
    472    * <li>stripped existing classes stripped where only newly added methods are kept
    473    * @param pInfo
    474    * @param clsInfoDiff
    475    * @return
    476    */
    477   public boolean isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff) {
    478       return isConsistent(pInfo, clsInfoDiff, null);
    479   }
    480 
    481   /**
    482    * Check if packages are consistent, also record class deltas.
    483    * <p>
    484    * <ul>class deltas are:
    485    * <li>brand new classes that are not present in current package
    486    * <li>stripped existing classes stripped where only newly added methods are kept
    487    * @param pInfo
    488    * @param clsInfoDiff
    489    * @param ignoredClasses
    490    * @return
    491    */
    492   public boolean isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff,
    493       Collection<String> ignoredClasses) {
    494     boolean consistent = true;
    495     boolean diffMode = clsInfoDiff != null;
    496     for (ClassInfo cInfo : mClasses.values()) {
    497       ArrayList<MethodInfo> newClsApis = null;
    498       ArrayList<MethodInfo> newClsCtors = null;
    499 
    500       // TODO: Add support for matching inner classes (e.g, something like
    501       //  example.Type.* should match example.Type.InnerType)
    502       if (ignoredClasses != null && ignoredClasses.contains(cInfo.qualifiedName())) {
    503           // TODO: Log skipping this?
    504           continue;
    505       }
    506       if (pInfo.mClasses.containsKey(cInfo.name())) {
    507         if (diffMode) {
    508           newClsApis = new ArrayList<>();
    509           newClsCtors = new ArrayList<>();
    510         }
    511         if (!cInfo.isConsistent(pInfo.mClasses.get(cInfo.name()), newClsCtors, newClsApis)) {
    512           consistent = false;
    513         }
    514         // if we are in diff mode, add class to list if there's new ctor or new apis
    515         if (diffMode && !(newClsCtors.isEmpty() && newClsApis.isEmpty())) {
    516           // generate a "delta" class with only added methods and constructors, but no fields etc
    517           ClassInfo deltaClsInfo = createDeltaClass(cInfo, newClsCtors, newClsApis);
    518           clsInfoDiff.add(deltaClsInfo);
    519         }
    520       } else {
    521         if (cInfo.isDeprecated()) {
    522           Errors.error(Errors.REMOVED_DEPRECATED_CLASS, cInfo.position(),
    523               "Removed deprecated public class " + cInfo.qualifiedName());
    524         } else {
    525           Errors.error(Errors.REMOVED_CLASS, cInfo.position(),
    526               "Removed public class " + cInfo.qualifiedName());
    527         }
    528         consistent = false;
    529       }
    530     }
    531     for (ClassInfo cInfo : pInfo.mClasses.values()) {
    532       if (ignoredClasses != null && ignoredClasses.contains(cInfo.qualifiedName())) {
    533           // TODO: Log skipping this?
    534           continue;
    535       }
    536       if (!mClasses.containsKey(cInfo.name())) {
    537         Errors.error(Errors.ADDED_CLASS, cInfo.position(), "Added class " + cInfo.name()
    538             + " to package " + pInfo.name());
    539         consistent = false;
    540         // brand new class, add everything as is
    541         if (diffMode) {
    542             clsInfoDiff.add(cInfo);
    543         }
    544       }
    545     }
    546     if (diffMode) {
    547       Collections.sort(clsInfoDiff, ClassInfo.comparator);
    548     }
    549     return consistent;
    550   }
    551 }
    552