/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.doclava; import com.google.doclava.apicheck.ApiInfo; import com.google.clearsilver.jsilver.data.Data; import com.sun.javadoc.*; import java.util.*; public class PackageInfo extends DocInfo implements ContainerInfo { public static final String DEFAULT_PACKAGE = "default package"; public static final Comparator comparator = new Comparator() { public int compare(PackageInfo a, PackageInfo b) { return a.name().compareTo(b.name()); } }; public PackageInfo(PackageDoc pkg, String name, SourcePositionInfo position) { super(pkg.getRawCommentText(), position); if (name.isEmpty()) { mName = DEFAULT_PACKAGE; } else { mName = name; } mPackage = pkg; initializeMaps(); } public PackageInfo(String name) { super("", null); mName = name; initializeMaps(); } public PackageInfo(String name, SourcePositionInfo position) { super("", position); if (name.isEmpty()) { mName = "default package"; } else { mName = name; } initializeMaps(); } private void initializeMaps() { mAnnotationsMap = new HashMap(); mInterfacesMap = new HashMap(); mOrdinaryClassesMap = new HashMap(); mEnumsMap = new HashMap(); mExceptionsMap = new HashMap(); mErrorsMap = new HashMap(); } public String htmlPage() { String s = mName; s = s.replace('.', '/'); s += "/package-summary.html"; s = Doclava.javadocDir + s; return s; } @Override public ContainerInfo parent() { return null; } @Override public boolean isHidden() { if (mHidden == null) { if (hasHideComment()) { // We change the hidden value of the package if a class wants to be not hidden. ClassInfo[][] types = new ClassInfo[][] { annotations(), interfaces(), ordinaryClasses(), enums(), exceptions() }; for (ClassInfo[] type : types) { if (type != null) { for (ClassInfo c : type) { if (c.hasShowAnnotation()) { mHidden = false; return false; } } } } mHidden = true; } else { mHidden = false; } } return mHidden; } public void setHidden(boolean hidden) { mHidden = hidden; } @Override public boolean isRemoved() { if (mRemoved == null) { if (hasRemovedComment()) { // We change the removed value of the package if a class wants to be not hidden. ClassInfo[][] types = new ClassInfo[][] { annotations(), interfaces(), ordinaryClasses(), enums(), exceptions() }; for (ClassInfo[] type : types) { if (type != null) { for (ClassInfo c : type) { if (c.hasShowAnnotation()) { mRemoved = false; return false; } } } } mRemoved = true; } else { mRemoved = false; } } return mRemoved; } @Override public boolean isHiddenOrRemoved() { return isHidden() || isRemoved(); } /** * Used by ClassInfo to determine packages default visability before annoations. */ public boolean hasHideComment() { if (mHiddenByComment == null) { if (Doclava.hiddenPackages.contains(mName)) { mHiddenByComment = true; } else { mHiddenByComment = comment().isHidden(); } } return mHiddenByComment; } public boolean hasRemovedComment() { if (mRemovedByComment == null) { mRemovedByComment = comment().isRemoved(); } return mRemovedByComment; } public boolean checkLevel() { // TODO should return false if all classes are hidden but the package isn't. // We don't have this so I'm not doing it now. return !isHiddenOrRemoved(); } public String name() { return mName; } public String qualifiedName() { return mName; } public TagInfo[] inlineTags() { return comment().tags(); } public TagInfo[] firstSentenceTags() { return comment().briefTags(); } /** * @param classes the Array of ClassInfo to be filtered * @return an Array of ClassInfo without any hidden or removed classes */ public static ClassInfo[] filterHiddenAndRemoved(ClassInfo[] classes) { ArrayList out = new ArrayList(); for (ClassInfo cl : classes) { if (!cl.isHiddenOrRemoved()) { out.add(cl); } } return out.toArray(new ClassInfo[0]); } public void makeLink(Data data, String base) { if (checkLevel()) { data.setValue(base + ".link", htmlPage()); } data.setValue(base + ".name", name()); data.setValue(base + ".since", getSince()); } public void makeClassLinkListHDF(Data data, String base) { makeLink(data, base); ClassInfo.makeLinkListHDF(data, base + ".annotations", annotations()); ClassInfo.makeLinkListHDF(data, base + ".interfaces", interfaces()); ClassInfo.makeLinkListHDF(data, base + ".classes", ordinaryClasses()); ClassInfo.makeLinkListHDF(data, base + ".enums", enums()); ClassInfo.makeLinkListHDF(data, base + ".exceptions", exceptions()); ClassInfo.makeLinkListHDF(data, base + ".errors", errors()); data.setValue(base + ".since", getSince()); } public ClassInfo[] annotations() { if (mAnnotations == null) { mAnnotations = ClassInfo.sortByName(filterHiddenAndRemoved( Converter.convertClasses(mPackage.annotationTypes()))); } return mAnnotations; } public ClassInfo[] interfaces() { if (mInterfaces == null) { mInterfaces = ClassInfo.sortByName(filterHiddenAndRemoved( Converter.convertClasses(mPackage.interfaces()))); } return mInterfaces; } public ClassInfo[] ordinaryClasses() { if (mOrdinaryClasses == null) { mOrdinaryClasses = ClassInfo.sortByName(filterHiddenAndRemoved( Converter.convertClasses(mPackage.ordinaryClasses()))); } return mOrdinaryClasses; } public ClassInfo[] enums() { if (mEnums == null) { mEnums = ClassInfo.sortByName(filterHiddenAndRemoved( Converter.convertClasses(mPackage.enums()))); } return mEnums; } public ClassInfo[] exceptions() { if (mExceptions == null) { mExceptions = ClassInfo.sortByName(filterHiddenAndRemoved( Converter.convertClasses(mPackage.exceptions()))); } return mExceptions; } public ClassInfo[] errors() { if (mErrors == null) { mErrors = ClassInfo.sortByName(filterHiddenAndRemoved( Converter.convertClasses(mPackage.errors()))); } return mErrors; } public ApiInfo containingApi() { return mContainingApi; } public void setContainingApi(ApiInfo api) { mContainingApi = api; } @Override public String toString() { return this.name(); } @Override public boolean equals(Object o) { if (this == o) { return true; } else if (o instanceof PackageInfo) { final PackageInfo p = (PackageInfo) o; return mName.equals(p.mName); } else { return false; } } @Override public int hashCode() { return mName.hashCode(); } private Boolean mHidden = null; private Boolean mHiddenByComment = null; private Boolean mRemoved = null; private Boolean mRemovedByComment = null; private String mName; private PackageDoc mPackage; private ApiInfo mContainingApi; private ClassInfo[] mAnnotations; private ClassInfo[] mInterfaces; private ClassInfo[] mOrdinaryClasses; private ClassInfo[] mEnums; private ClassInfo[] mExceptions; private ClassInfo[] mErrors; private HashMap mAnnotationsMap; private HashMap mInterfacesMap; private HashMap mOrdinaryClassesMap; private HashMap mEnumsMap; private HashMap mExceptionsMap; private HashMap mErrorsMap; public ClassInfo getClass(String className) { ClassInfo cls = mInterfacesMap.get(className); if (cls != null) { return cls; } cls = mOrdinaryClassesMap.get(className); if (cls != null) { return cls; } cls = mEnumsMap.get(className); if (cls != null) { return cls; } cls = mEnumsMap.get(className); if (cls != null) { return cls; } cls = mAnnotationsMap.get(className); if (cls != null) { return cls; } return mErrorsMap.get(className); } public void addAnnotation(ClassInfo cls) { cls.setPackage(this); mAnnotationsMap.put(cls.name(), cls); } public ClassInfo getAnnotation(String annotationName) { return mAnnotationsMap.get(annotationName); } public void addInterface(ClassInfo cls) { cls.setPackage(this); mInterfacesMap.put(cls.name(), cls); } public ClassInfo getInterface(String interfaceName) { return mInterfacesMap.get(interfaceName); } public ClassInfo getOrdinaryClass(String className) { return mOrdinaryClassesMap.get(className); } public void addOrdinaryClass(ClassInfo cls) { cls.setPackage(this); mOrdinaryClassesMap.put(cls.name(), cls); } public ClassInfo getEnum(String enumName) { return mEnumsMap.get(enumName); } public void addEnum(ClassInfo cls) { cls.setPackage(this); this.mEnumsMap.put(cls.name(), cls); } public ClassInfo getException(String exceptionName) { return mExceptionsMap.get(exceptionName); } public ClassInfo getError(String errorName) { return mErrorsMap.get(errorName); } // TODO: Leftovers from ApiCheck that should be better merged. private HashMap mClasses = new HashMap(); public void addClass(ClassInfo cls) { cls.setPackage(this); mClasses.put(cls.name(), cls); } public HashMap allClasses() { return mClasses; } public boolean isConsistent(PackageInfo pInfo) { return isConsistent(pInfo, null); } /** * Creates the delta class by copying class signatures from original, but use provided list of * constructors and methods. */ private ClassInfo createDeltaClass(ClassInfo original, ArrayList constructors, ArrayList methods) { ArrayList emptyFields = new ArrayList<>(); ArrayList emptyClasses = new ArrayList<>(); ArrayList emptyTypes = new ArrayList<>(); ArrayList emptyMethods = new ArrayList<>(); ClassInfo ret = new ClassInfo(null, original.getRawCommentText(), original.position(), original.isPublic(), original.isProtected(), original.isPackagePrivate(), original.isPrivate(), original.isStatic(), original.isInterface(), original.isAbstract(), original.isOrdinaryClass(), original.isException(), original.isError(), original.isEnum(), original.isAnnotation(), original.isFinal(), original.isIncluded(), original.name(), original.qualifiedName(), original.qualifiedTypeName(), original.isPrimitive()); ArrayList interfaces = original.interfaces(); // avoid providing null to init method, replace with empty array list when needed if (interfaces == null) { interfaces = emptyClasses; } ArrayList interfaceTypes = original.interfaceTypes(); if (interfaceTypes == null) { interfaceTypes = emptyTypes; } ArrayList innerClasses = original.innerClasses(); if (innerClasses == null) { innerClasses = emptyClasses; } ArrayList annotationElements = original.annotationElements(); if (annotationElements == null) { annotationElements = emptyMethods; } ArrayList annotations = original.annotations(); if (annotations == null) { annotations = new ArrayList<>(); } ret.init(original.type(), interfaces, interfaceTypes, innerClasses, constructors, methods, annotationElements, emptyFields /* fields */, emptyFields /* enum */, original.containingPackage(), original.containingClass(), original.superclass(), original.superclassType(), annotations); return ret; } /** * Check if packages are consistent, also record class deltas. *

*

    class deltas are: *
  • brand new classes that are not present in current package *
  • stripped existing classes stripped where only newly added methods are kept * @param pInfo * @param clsInfoDiff * @return */ public boolean isConsistent(PackageInfo pInfo, List clsInfoDiff) { return isConsistent(pInfo, clsInfoDiff, null); } /** * Check if packages are consistent, also record class deltas. *

    *

      class deltas are: *
    • brand new classes that are not present in current package *
    • stripped existing classes stripped where only newly added methods are kept * @param pInfo * @param clsInfoDiff * @param ignoredClasses * @return */ public boolean isConsistent(PackageInfo pInfo, List clsInfoDiff, Collection ignoredClasses) { boolean consistent = true; boolean diffMode = clsInfoDiff != null; for (ClassInfo cInfo : mClasses.values()) { ArrayList newClsApis = null; ArrayList newClsCtors = null; // TODO: Add support for matching inner classes (e.g, something like // example.Type.* should match example.Type.InnerType) if (ignoredClasses != null && ignoredClasses.contains(cInfo.qualifiedName())) { // TODO: Log skipping this? continue; } if (pInfo.mClasses.containsKey(cInfo.name())) { if (diffMode) { newClsApis = new ArrayList<>(); newClsCtors = new ArrayList<>(); } if (!cInfo.isConsistent(pInfo.mClasses.get(cInfo.name()), newClsCtors, newClsApis)) { consistent = false; } // if we are in diff mode, add class to list if there's new ctor or new apis if (diffMode && !(newClsCtors.isEmpty() && newClsApis.isEmpty())) { // generate a "delta" class with only added methods and constructors, but no fields etc ClassInfo deltaClsInfo = createDeltaClass(cInfo, newClsCtors, newClsApis); clsInfoDiff.add(deltaClsInfo); } } else { if (cInfo.isDeprecated()) { Errors.error(Errors.REMOVED_DEPRECATED_CLASS, cInfo.position(), "Removed deprecated public class " + cInfo.qualifiedName()); } else { Errors.error(Errors.REMOVED_CLASS, cInfo.position(), "Removed public class " + cInfo.qualifiedName()); } consistent = false; } } for (ClassInfo cInfo : pInfo.mClasses.values()) { if (ignoredClasses != null && ignoredClasses.contains(cInfo.qualifiedName())) { // TODO: Log skipping this? continue; } if (!mClasses.containsKey(cInfo.name())) { Errors.error(Errors.ADDED_CLASS, cInfo.position(), "Added class " + cInfo.name() + " to package " + pInfo.name()); consistent = false; // brand new class, add everything as is if (diffMode) { clsInfoDiff.add(cInfo); } } } if (diffMode) { Collections.sort(clsInfoDiff, ClassInfo.comparator); } return consistent; } }