• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
PackageInfo(PackageDoc pkg, String name, SourcePositionInfo position)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 
PackageInfo(String name)46   public PackageInfo(String name) {
47     super("", null);
48     mName = name;
49     initializeMaps();
50   }
51 
PackageInfo(String name, SourcePositionInfo position)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 
initializeMaps()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 
htmlPage()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
parent()81   public ContainerInfo parent() {
82     return null;
83   }
84 
85   @Override
isHidden()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 
setHidden(boolean hidden)110   public void setHidden(boolean hidden) {
111     mHidden = hidden;
112   }
113 
114   @Override
isRemoved()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
isHiddenOrRemoved()141   public boolean isHiddenOrRemoved() {
142     return isHidden() || isRemoved();
143   }
144 
145   /**
146    * Used by ClassInfo to determine packages default visability before annoations.
147    */
hasHideComment()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 
hasRemovedComment()159   public boolean hasRemovedComment() {
160     if (mRemovedByComment == null) {
161       mRemovedByComment = comment().isRemoved();
162     }
163 
164     return mRemovedByComment;
165   }
166 
checkLevel()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 
name()173   public String name() {
174     return mName;
175   }
176 
qualifiedName()177   public String qualifiedName() {
178     return mName;
179   }
180 
inlineTags()181   public TagInfo[] inlineTags() {
182     return comment().tags();
183   }
184 
firstSentenceTags()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    */
filterHiddenAndRemoved(ClassInfo[] classes)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 
makeLink(Data data, String base)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     data.setValue(base + ".sdkextsince", getSdkExtSince());
212   }
213 
makeClassLinkListHDF(Data data, String base)214   public void makeClassLinkListHDF(Data data, String base) {
215     makeLink(data, base);
216     ClassInfo.makeLinkListHDF(data, base + ".annotations", annotations());
217     ClassInfo.makeLinkListHDF(data, base + ".interfaces", interfaces());
218     ClassInfo.makeLinkListHDF(data, base + ".classes", ordinaryClasses());
219     ClassInfo.makeLinkListHDF(data, base + ".enums", enums());
220     ClassInfo.makeLinkListHDF(data, base + ".exceptions", exceptions());
221     ClassInfo.makeLinkListHDF(data, base + ".errors", errors());
222     data.setValue(base + ".since", getSince());
223     data.setValue(base + ".sdkextsince", getSdkExtSince());
224   }
225 
annotations()226   public ClassInfo[] annotations() {
227     if (mAnnotations == null) {
228       mAnnotations =
229           ClassInfo.sortByName(filterHiddenAndRemoved(
230               Converter.convertClasses(mPackage.annotationTypes())));
231     }
232     return mAnnotations;
233   }
234 
interfaces()235   public ClassInfo[] interfaces() {
236     if (mInterfaces == null) {
237       mInterfaces =
238           ClassInfo.sortByName(filterHiddenAndRemoved(
239               Converter.convertClasses(mPackage.interfaces())));
240     }
241     return mInterfaces;
242   }
243 
ordinaryClasses()244   public ClassInfo[] ordinaryClasses() {
245     if (mOrdinaryClasses == null) {
246       mOrdinaryClasses =
247           ClassInfo.sortByName(filterHiddenAndRemoved(
248               Converter.convertClasses(mPackage.ordinaryClasses())));
249     }
250     return mOrdinaryClasses;
251   }
252 
enums()253   public ClassInfo[] enums() {
254     if (mEnums == null) {
255       mEnums = ClassInfo.sortByName(filterHiddenAndRemoved(
256           Converter.convertClasses(mPackage.enums())));
257     }
258     return mEnums;
259   }
260 
exceptions()261   public ClassInfo[] exceptions() {
262     if (mExceptions == null) {
263       mExceptions =
264           ClassInfo.sortByName(filterHiddenAndRemoved(
265               Converter.convertClasses(mPackage.exceptions())));
266     }
267     return mExceptions;
268   }
269 
errors()270   public ClassInfo[] errors() {
271     if (mErrors == null) {
272       mErrors = ClassInfo.sortByName(filterHiddenAndRemoved(
273           Converter.convertClasses(mPackage.errors())));
274     }
275     return mErrors;
276   }
277 
containingApi()278   public ApiInfo containingApi() {
279     return mContainingApi;
280   }
281 
setContainingApi(ApiInfo api)282   public void setContainingApi(ApiInfo api) {
283     mContainingApi = api;
284   }
285 
286   @Override
toString()287   public String toString() {
288     return this.name();
289   }
290 
291   @Override
equals(Object o)292   public boolean equals(Object o) {
293     if (this == o) {
294       return true;
295     } else if (o instanceof PackageInfo) {
296       final PackageInfo p = (PackageInfo) o;
297       return mName.equals(p.mName);
298     } else {
299       return false;
300     }
301   }
302 
303   @Override
hashCode()304   public int hashCode() {
305     return mName.hashCode();
306   }
307 
308   private Boolean mHidden = null;
309   private Boolean mHiddenByComment = null;
310   private Boolean mRemoved = null;
311   private Boolean mRemovedByComment = null;
312   private String mName;
313   private PackageDoc mPackage;
314   private ApiInfo mContainingApi;
315   private ClassInfo[] mAnnotations;
316   private ClassInfo[] mInterfaces;
317   private ClassInfo[] mOrdinaryClasses;
318   private ClassInfo[] mEnums;
319   private ClassInfo[] mExceptions;
320   private ClassInfo[] mErrors;
321 
322   private HashMap<String, ClassInfo> mAnnotationsMap;
323   private HashMap<String, ClassInfo> mInterfacesMap;
324   private HashMap<String, ClassInfo> mOrdinaryClassesMap;
325   private HashMap<String, ClassInfo> mEnumsMap;
326   private HashMap<String, ClassInfo> mExceptionsMap;
327   private HashMap<String, ClassInfo> mErrorsMap;
328 
329 
getClass(String className)330   public ClassInfo getClass(String className) {
331       ClassInfo cls = mInterfacesMap.get(className);
332 
333       if (cls != null) {
334           return cls;
335       }
336 
337       cls = mOrdinaryClassesMap.get(className);
338 
339       if (cls != null) {
340           return cls;
341       }
342 
343       cls = mEnumsMap.get(className);
344 
345       if (cls != null) {
346           return cls;
347       }
348 
349       cls = mEnumsMap.get(className);
350 
351       if (cls != null) {
352           return cls;
353       }
354       cls = mAnnotationsMap.get(className);
355 
356       if (cls != null) {
357           return cls;
358       }
359 
360       return mErrorsMap.get(className);
361   }
362 
addAnnotation(ClassInfo cls)363   public void addAnnotation(ClassInfo cls) {
364       cls.setPackage(this);
365       mAnnotationsMap.put(cls.name(), cls);
366   }
367 
getAnnotation(String annotationName)368   public ClassInfo getAnnotation(String annotationName) {
369       return mAnnotationsMap.get(annotationName);
370   }
371 
addInterface(ClassInfo cls)372   public void addInterface(ClassInfo cls) {
373       cls.setPackage(this);
374       mInterfacesMap.put(cls.name(), cls);
375   }
376 
getInterface(String interfaceName)377   public ClassInfo getInterface(String interfaceName) {
378       return mInterfacesMap.get(interfaceName);
379   }
380 
getOrdinaryClass(String className)381   public ClassInfo getOrdinaryClass(String className) {
382       return mOrdinaryClassesMap.get(className);
383   }
384 
addOrdinaryClass(ClassInfo cls)385   public void addOrdinaryClass(ClassInfo cls) {
386       cls.setPackage(this);
387       mOrdinaryClassesMap.put(cls.name(), cls);
388   }
389 
getEnum(String enumName)390   public ClassInfo getEnum(String enumName) {
391       return mEnumsMap.get(enumName);
392   }
393 
addEnum(ClassInfo cls)394   public void addEnum(ClassInfo cls) {
395       cls.setPackage(this);
396       this.mEnumsMap.put(cls.name(), cls);
397   }
398 
getException(String exceptionName)399   public ClassInfo getException(String exceptionName) {
400       return mExceptionsMap.get(exceptionName);
401   }
402 
getError(String errorName)403   public ClassInfo getError(String errorName) {
404       return mErrorsMap.get(errorName);
405   }
406 
407   // TODO: Leftovers from ApiCheck that should be better merged.
408   private HashMap<String, ClassInfo> mClasses = new HashMap<String, ClassInfo>();
409 
addClass(ClassInfo cls)410   public void addClass(ClassInfo cls) {
411     cls.setPackage(this);
412     mClasses.put(cls.name(), cls);
413   }
414 
allClasses()415   public HashMap<String, ClassInfo> allClasses() {
416     return mClasses;
417   }
418 
isConsistent(PackageInfo pInfo)419   public boolean isConsistent(PackageInfo pInfo) {
420     return isConsistent(pInfo, null);
421   }
422 
423   /**
424    * Creates the delta class by copying class signatures from original, but use provided list of
425    * constructors and methods.
426    */
createDeltaClass(ClassInfo original, ArrayList<MethodInfo> constructors, ArrayList<MethodInfo> methods)427   private ClassInfo createDeltaClass(ClassInfo original,
428       ArrayList<MethodInfo> constructors, ArrayList<MethodInfo> methods) {
429     ArrayList<FieldInfo> emptyFields = new ArrayList<>();
430     ArrayList<ClassInfo> emptyClasses = new ArrayList<>();
431     ArrayList<TypeInfo> emptyTypes = new ArrayList<>();
432     ArrayList<MethodInfo> emptyMethods = new ArrayList<>();
433     ClassInfo ret = new ClassInfo(null, original.getRawCommentText(), original.position(),
434         original.isPublic(), original.isProtected(), original.isPackagePrivate(),
435         original.isPrivate(), original.isStatic(), original.isInterface(),
436         original.isAbstract(), original.isOrdinaryClass(),
437         original.isException(), original.isError(), original.isEnum(), original.isAnnotation(),
438         original.isFinal(), original.isIncluded(), original.name(), original.qualifiedName(),
439         original.qualifiedTypeName(), original.isPrimitive());
440     ArrayList<ClassInfo> interfaces = original.interfaces();
441     // avoid providing null to init method, replace with empty array list when needed
442     if (interfaces == null) {
443       interfaces = emptyClasses;
444     }
445     ArrayList<TypeInfo> interfaceTypes = original.interfaceTypes();
446     if (interfaceTypes == null) {
447       interfaceTypes = emptyTypes;
448     }
449     ArrayList<ClassInfo> innerClasses = original.innerClasses();
450     if (innerClasses == null) {
451       innerClasses = emptyClasses;
452     }
453     ArrayList<MethodInfo> annotationElements = original.annotationElements();
454     if (annotationElements == null) {
455       annotationElements = emptyMethods;
456     }
457     ArrayList<AnnotationInstanceInfo> annotations = original.annotations();
458     if (annotations == null) {
459       annotations = new ArrayList<>();
460     }
461     ret.init(original.type(), interfaces, interfaceTypes, innerClasses,
462         constructors, methods, annotationElements,
463         emptyFields /* fields */, emptyFields /* enum */,
464         original.containingPackage(), original.containingClass(), original.superclass(),
465         original.superclassType(), annotations);
466     return ret;
467   }
468 
469   /**
470    * Check if packages are consistent, also record class deltas.
471    * <p>
472    * <ul>class deltas are:
473    * <li>brand new classes that are not present in current package
474    * <li>stripped existing classes stripped where only newly added methods are kept
475    * @param pInfo
476    * @param clsInfoDiff
477    * @return
478    */
isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff)479   public boolean isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff) {
480       return isConsistent(pInfo, clsInfoDiff, null);
481   }
482 
483   /**
484    * Check if packages are consistent, also record class deltas.
485    * <p>
486    * <ul>class deltas are:
487    * <li>brand new classes that are not present in current package
488    * <li>stripped existing classes stripped where only newly added methods are kept
489    * @param pInfo
490    * @param clsInfoDiff
491    * @param ignoredClasses
492    * @return
493    */
isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff, Collection<String> ignoredClasses)494   public boolean isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff,
495       Collection<String> ignoredClasses) {
496     boolean consistent = true;
497     boolean diffMode = clsInfoDiff != null;
498     for (ClassInfo cInfo : mClasses.values()) {
499       ArrayList<MethodInfo> newClsApis = null;
500       ArrayList<MethodInfo> newClsCtors = null;
501 
502       // TODO: Add support for matching inner classes (e.g, something like
503       //  example.Type.* should match example.Type.InnerType)
504       if (ignoredClasses != null && ignoredClasses.contains(cInfo.qualifiedName())) {
505           // TODO: Log skipping this?
506           continue;
507       }
508       if (pInfo.mClasses.containsKey(cInfo.name())) {
509         if (diffMode) {
510           newClsApis = new ArrayList<>();
511           newClsCtors = new ArrayList<>();
512         }
513         if (!cInfo.isConsistent(pInfo.mClasses.get(cInfo.name()), newClsCtors, newClsApis)) {
514           consistent = false;
515         }
516         // if we are in diff mode, add class to list if there's new ctor or new apis
517         if (diffMode && !(newClsCtors.isEmpty() && newClsApis.isEmpty())) {
518           // generate a "delta" class with only added methods and constructors, but no fields etc
519           ClassInfo deltaClsInfo = createDeltaClass(cInfo, newClsCtors, newClsApis);
520           clsInfoDiff.add(deltaClsInfo);
521         }
522       } else {
523         if (cInfo.isDeprecated()) {
524           Errors.error(Errors.REMOVED_DEPRECATED_CLASS, cInfo.position(),
525               "Removed deprecated public class " + cInfo.qualifiedName());
526         } else {
527           Errors.error(Errors.REMOVED_CLASS, cInfo.position(),
528               "Removed public class " + cInfo.qualifiedName());
529         }
530         consistent = false;
531       }
532     }
533     for (ClassInfo cInfo : pInfo.mClasses.values()) {
534       if (ignoredClasses != null && ignoredClasses.contains(cInfo.qualifiedName())) {
535           // TODO: Log skipping this?
536           continue;
537       }
538       if (!mClasses.containsKey(cInfo.name())) {
539         Errors.error(Errors.ADDED_CLASS, cInfo.position(), "Added class " + cInfo.name()
540             + " to package " + pInfo.name());
541         consistent = false;
542         // brand new class, add everything as is
543         if (diffMode) {
544             clsInfoDiff.add(cInfo);
545         }
546       }
547     }
548     if (diffMode) {
549       Collections.sort(clsInfoDiff, ClassInfo.comparator);
550     }
551     return consistent;
552   }
553 }
554