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
17package com.google.doclava;
18
19import com.google.doclava.apicheck.ApiInfo;
20import com.google.clearsilver.jsilver.data.Data;
21import com.sun.javadoc.*;
22
23import java.util.*;
24
25public 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  @Override
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
137  public boolean isHiddenOrRemoved() {
138    return isHidden() || isRemoved();
139  }
140
141  /**
142   * Used by ClassInfo to determine packages default visability before annoations.
143   */
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
155  public boolean hasRemovedComment() {
156    if (mRemovedByComment == null) {
157      mRemovedByComment = comment().isRemoved();
158    }
159
160    return mRemovedByComment;
161  }
162
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
169  public String name() {
170    return mName;
171  }
172
173  public String qualifiedName() {
174    return mName;
175  }
176
177  public TagInfo[] inlineTags() {
178    return comment().tags();
179  }
180
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   */
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
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
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
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
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
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
247  public ClassInfo[] enums() {
248    if (mEnums == null) {
249      mEnums = ClassInfo.sortByName(filterHiddenAndRemoved(
250          Converter.convertClasses(mPackage.enums())));
251    }
252    return mEnums;
253  }
254
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
264  public ClassInfo[] errors() {
265    if (mErrors == null) {
266      mErrors = ClassInfo.sortByName(filterHiddenAndRemoved(
267          Converter.convertClasses(mPackage.errors())));
268    }
269    return mErrors;
270  }
271
272  public ApiInfo containingApi() {
273    return mContainingApi;
274  }
275
276  public void setContainingApi(ApiInfo api) {
277    mContainingApi = api;
278  }
279
280  // in hashed containers, treat the name as the key
281  @Override
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
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
341  public void addAnnotation(ClassInfo cls) {
342      cls.setPackage(this);
343      mAnnotationsMap.put(cls.name(), cls);
344  }
345
346  public ClassInfo getAnnotation(String annotationName) {
347      return mAnnotationsMap.get(annotationName);
348  }
349
350  public void addInterface(ClassInfo cls) {
351      cls.setPackage(this);
352      mInterfacesMap.put(cls.name(), cls);
353  }
354
355  public ClassInfo getInterface(String interfaceName) {
356      return mInterfacesMap.get(interfaceName);
357  }
358
359  public ClassInfo getOrdinaryClass(String className) {
360      return mOrdinaryClassesMap.get(className);
361  }
362
363  public void addOrdinaryClass(ClassInfo cls) {
364      cls.setPackage(this);
365      mOrdinaryClassesMap.put(cls.name(), cls);
366  }
367
368  public ClassInfo getEnum(String enumName) {
369      return mEnumsMap.get(enumName);
370  }
371
372  public void addEnum(ClassInfo cls) {
373      cls.setPackage(this);
374      this.mEnumsMap.put(cls.name(), cls);
375  }
376
377  public ClassInfo getException(String exceptionName) {
378      return mExceptionsMap.get(exceptionName);
379  }
380
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
388  public void addClass(ClassInfo cls) {
389    cls.setPackage(this);
390    mClasses.put(cls.name(), cls);
391  }
392
393  public HashMap<String, ClassInfo> allClasses() {
394    return mClasses;
395  }
396
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   */
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   */
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   */
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