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