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