• 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   }
212 
makeClassLinkListHDF(Data data, String base)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 
annotations()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 
interfaces()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 
ordinaryClasses()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 
enums()251   public ClassInfo[] enums() {
252     if (mEnums == null) {
253       mEnums = ClassInfo.sortByName(filterHiddenAndRemoved(
254           Converter.convertClasses(mPackage.enums())));
255     }
256     return mEnums;
257   }
258 
exceptions()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 
errors()268   public ClassInfo[] errors() {
269     if (mErrors == null) {
270       mErrors = ClassInfo.sortByName(filterHiddenAndRemoved(
271           Converter.convertClasses(mPackage.errors())));
272     }
273     return mErrors;
274   }
275 
containingApi()276   public ApiInfo containingApi() {
277     return mContainingApi;
278   }
279 
setContainingApi(ApiInfo api)280   public void setContainingApi(ApiInfo api) {
281     mContainingApi = api;
282   }
283 
284   @Override
toString()285   public String toString() {
286     return this.name();
287   }
288 
289   @Override
equals(Object o)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
hashCode()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 
getClass(String className)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 
addAnnotation(ClassInfo cls)361   public void addAnnotation(ClassInfo cls) {
362       cls.setPackage(this);
363       mAnnotationsMap.put(cls.name(), cls);
364   }
365 
getAnnotation(String annotationName)366   public ClassInfo getAnnotation(String annotationName) {
367       return mAnnotationsMap.get(annotationName);
368   }
369 
addInterface(ClassInfo cls)370   public void addInterface(ClassInfo cls) {
371       cls.setPackage(this);
372       mInterfacesMap.put(cls.name(), cls);
373   }
374 
getInterface(String interfaceName)375   public ClassInfo getInterface(String interfaceName) {
376       return mInterfacesMap.get(interfaceName);
377   }
378 
getOrdinaryClass(String className)379   public ClassInfo getOrdinaryClass(String className) {
380       return mOrdinaryClassesMap.get(className);
381   }
382 
addOrdinaryClass(ClassInfo cls)383   public void addOrdinaryClass(ClassInfo cls) {
384       cls.setPackage(this);
385       mOrdinaryClassesMap.put(cls.name(), cls);
386   }
387 
getEnum(String enumName)388   public ClassInfo getEnum(String enumName) {
389       return mEnumsMap.get(enumName);
390   }
391 
addEnum(ClassInfo cls)392   public void addEnum(ClassInfo cls) {
393       cls.setPackage(this);
394       this.mEnumsMap.put(cls.name(), cls);
395   }
396 
getException(String exceptionName)397   public ClassInfo getException(String exceptionName) {
398       return mExceptionsMap.get(exceptionName);
399   }
400 
getError(String errorName)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 
addClass(ClassInfo cls)408   public void addClass(ClassInfo cls) {
409     cls.setPackage(this);
410     mClasses.put(cls.name(), cls);
411   }
412 
allClasses()413   public HashMap<String, ClassInfo> allClasses() {
414     return mClasses;
415   }
416 
isConsistent(PackageInfo pInfo)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    */
createDeltaClass(ClassInfo original, ArrayList<MethodInfo> constructors, ArrayList<MethodInfo> methods)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    */
isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff)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    */
isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff, Collection<String> ignoredClasses)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