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 data.setValue(base + ".sdkextsince", getSdkExtSince()); 212 } 213 makeClassLinkListHDF(Data data, String base)214 public void makeClassLinkListHDF(Data data, String base) { 215 makeLink(data, base); 216 ClassInfo.makeLinkListHDF(data, base + ".annotations", annotations()); 217 ClassInfo.makeLinkListHDF(data, base + ".interfaces", interfaces()); 218 ClassInfo.makeLinkListHDF(data, base + ".classes", ordinaryClasses()); 219 ClassInfo.makeLinkListHDF(data, base + ".enums", enums()); 220 ClassInfo.makeLinkListHDF(data, base + ".exceptions", exceptions()); 221 ClassInfo.makeLinkListHDF(data, base + ".errors", errors()); 222 data.setValue(base + ".since", getSince()); 223 data.setValue(base + ".sdkextsince", getSdkExtSince()); 224 } 225 annotations()226 public ClassInfo[] annotations() { 227 if (mAnnotations == null) { 228 mAnnotations = 229 ClassInfo.sortByName(filterHiddenAndRemoved( 230 Converter.convertClasses(mPackage.annotationTypes()))); 231 } 232 return mAnnotations; 233 } 234 interfaces()235 public ClassInfo[] interfaces() { 236 if (mInterfaces == null) { 237 mInterfaces = 238 ClassInfo.sortByName(filterHiddenAndRemoved( 239 Converter.convertClasses(mPackage.interfaces()))); 240 } 241 return mInterfaces; 242 } 243 ordinaryClasses()244 public ClassInfo[] ordinaryClasses() { 245 if (mOrdinaryClasses == null) { 246 mOrdinaryClasses = 247 ClassInfo.sortByName(filterHiddenAndRemoved( 248 Converter.convertClasses(mPackage.ordinaryClasses()))); 249 } 250 return mOrdinaryClasses; 251 } 252 enums()253 public ClassInfo[] enums() { 254 if (mEnums == null) { 255 mEnums = ClassInfo.sortByName(filterHiddenAndRemoved( 256 Converter.convertClasses(mPackage.enums()))); 257 } 258 return mEnums; 259 } 260 exceptions()261 public ClassInfo[] exceptions() { 262 if (mExceptions == null) { 263 mExceptions = 264 ClassInfo.sortByName(filterHiddenAndRemoved( 265 Converter.convertClasses(mPackage.exceptions()))); 266 } 267 return mExceptions; 268 } 269 errors()270 public ClassInfo[] errors() { 271 if (mErrors == null) { 272 mErrors = ClassInfo.sortByName(filterHiddenAndRemoved( 273 Converter.convertClasses(mPackage.errors()))); 274 } 275 return mErrors; 276 } 277 containingApi()278 public ApiInfo containingApi() { 279 return mContainingApi; 280 } 281 setContainingApi(ApiInfo api)282 public void setContainingApi(ApiInfo api) { 283 mContainingApi = api; 284 } 285 286 @Override toString()287 public String toString() { 288 return this.name(); 289 } 290 291 @Override equals(Object o)292 public boolean equals(Object o) { 293 if (this == o) { 294 return true; 295 } else if (o instanceof PackageInfo) { 296 final PackageInfo p = (PackageInfo) o; 297 return mName.equals(p.mName); 298 } else { 299 return false; 300 } 301 } 302 303 @Override hashCode()304 public int hashCode() { 305 return mName.hashCode(); 306 } 307 308 private Boolean mHidden = null; 309 private Boolean mHiddenByComment = null; 310 private Boolean mRemoved = null; 311 private Boolean mRemovedByComment = null; 312 private String mName; 313 private PackageDoc mPackage; 314 private ApiInfo mContainingApi; 315 private ClassInfo[] mAnnotations; 316 private ClassInfo[] mInterfaces; 317 private ClassInfo[] mOrdinaryClasses; 318 private ClassInfo[] mEnums; 319 private ClassInfo[] mExceptions; 320 private ClassInfo[] mErrors; 321 322 private HashMap<String, ClassInfo> mAnnotationsMap; 323 private HashMap<String, ClassInfo> mInterfacesMap; 324 private HashMap<String, ClassInfo> mOrdinaryClassesMap; 325 private HashMap<String, ClassInfo> mEnumsMap; 326 private HashMap<String, ClassInfo> mExceptionsMap; 327 private HashMap<String, ClassInfo> mErrorsMap; 328 329 getClass(String className)330 public ClassInfo getClass(String className) { 331 ClassInfo cls = mInterfacesMap.get(className); 332 333 if (cls != null) { 334 return cls; 335 } 336 337 cls = mOrdinaryClassesMap.get(className); 338 339 if (cls != null) { 340 return cls; 341 } 342 343 cls = mEnumsMap.get(className); 344 345 if (cls != null) { 346 return cls; 347 } 348 349 cls = mEnumsMap.get(className); 350 351 if (cls != null) { 352 return cls; 353 } 354 cls = mAnnotationsMap.get(className); 355 356 if (cls != null) { 357 return cls; 358 } 359 360 return mErrorsMap.get(className); 361 } 362 addAnnotation(ClassInfo cls)363 public void addAnnotation(ClassInfo cls) { 364 cls.setPackage(this); 365 mAnnotationsMap.put(cls.name(), cls); 366 } 367 getAnnotation(String annotationName)368 public ClassInfo getAnnotation(String annotationName) { 369 return mAnnotationsMap.get(annotationName); 370 } 371 addInterface(ClassInfo cls)372 public void addInterface(ClassInfo cls) { 373 cls.setPackage(this); 374 mInterfacesMap.put(cls.name(), cls); 375 } 376 getInterface(String interfaceName)377 public ClassInfo getInterface(String interfaceName) { 378 return mInterfacesMap.get(interfaceName); 379 } 380 getOrdinaryClass(String className)381 public ClassInfo getOrdinaryClass(String className) { 382 return mOrdinaryClassesMap.get(className); 383 } 384 addOrdinaryClass(ClassInfo cls)385 public void addOrdinaryClass(ClassInfo cls) { 386 cls.setPackage(this); 387 mOrdinaryClassesMap.put(cls.name(), cls); 388 } 389 getEnum(String enumName)390 public ClassInfo getEnum(String enumName) { 391 return mEnumsMap.get(enumName); 392 } 393 addEnum(ClassInfo cls)394 public void addEnum(ClassInfo cls) { 395 cls.setPackage(this); 396 this.mEnumsMap.put(cls.name(), cls); 397 } 398 getException(String exceptionName)399 public ClassInfo getException(String exceptionName) { 400 return mExceptionsMap.get(exceptionName); 401 } 402 getError(String errorName)403 public ClassInfo getError(String errorName) { 404 return mErrorsMap.get(errorName); 405 } 406 407 // TODO: Leftovers from ApiCheck that should be better merged. 408 private HashMap<String, ClassInfo> mClasses = new HashMap<String, ClassInfo>(); 409 addClass(ClassInfo cls)410 public void addClass(ClassInfo cls) { 411 cls.setPackage(this); 412 mClasses.put(cls.name(), cls); 413 } 414 allClasses()415 public HashMap<String, ClassInfo> allClasses() { 416 return mClasses; 417 } 418 isConsistent(PackageInfo pInfo)419 public boolean isConsistent(PackageInfo pInfo) { 420 return isConsistent(pInfo, null); 421 } 422 423 /** 424 * Creates the delta class by copying class signatures from original, but use provided list of 425 * constructors and methods. 426 */ createDeltaClass(ClassInfo original, ArrayList<MethodInfo> constructors, ArrayList<MethodInfo> methods)427 private ClassInfo createDeltaClass(ClassInfo original, 428 ArrayList<MethodInfo> constructors, ArrayList<MethodInfo> methods) { 429 ArrayList<FieldInfo> emptyFields = new ArrayList<>(); 430 ArrayList<ClassInfo> emptyClasses = new ArrayList<>(); 431 ArrayList<TypeInfo> emptyTypes = new ArrayList<>(); 432 ArrayList<MethodInfo> emptyMethods = new ArrayList<>(); 433 ClassInfo ret = new ClassInfo(null, original.getRawCommentText(), original.position(), 434 original.isPublic(), original.isProtected(), original.isPackagePrivate(), 435 original.isPrivate(), original.isStatic(), original.isInterface(), 436 original.isAbstract(), original.isOrdinaryClass(), 437 original.isException(), original.isError(), original.isEnum(), original.isAnnotation(), 438 original.isFinal(), original.isIncluded(), original.name(), original.qualifiedName(), 439 original.qualifiedTypeName(), original.isPrimitive()); 440 ArrayList<ClassInfo> interfaces = original.interfaces(); 441 // avoid providing null to init method, replace with empty array list when needed 442 if (interfaces == null) { 443 interfaces = emptyClasses; 444 } 445 ArrayList<TypeInfo> interfaceTypes = original.interfaceTypes(); 446 if (interfaceTypes == null) { 447 interfaceTypes = emptyTypes; 448 } 449 ArrayList<ClassInfo> innerClasses = original.innerClasses(); 450 if (innerClasses == null) { 451 innerClasses = emptyClasses; 452 } 453 ArrayList<MethodInfo> annotationElements = original.annotationElements(); 454 if (annotationElements == null) { 455 annotationElements = emptyMethods; 456 } 457 ArrayList<AnnotationInstanceInfo> annotations = original.annotations(); 458 if (annotations == null) { 459 annotations = new ArrayList<>(); 460 } 461 ret.init(original.type(), interfaces, interfaceTypes, innerClasses, 462 constructors, methods, annotationElements, 463 emptyFields /* fields */, emptyFields /* enum */, 464 original.containingPackage(), original.containingClass(), original.superclass(), 465 original.superclassType(), annotations); 466 return ret; 467 } 468 469 /** 470 * Check if packages are consistent, also record class deltas. 471 * <p> 472 * <ul>class deltas are: 473 * <li>brand new classes that are not present in current package 474 * <li>stripped existing classes stripped where only newly added methods are kept 475 * @param pInfo 476 * @param clsInfoDiff 477 * @return 478 */ isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff)479 public boolean isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff) { 480 return isConsistent(pInfo, clsInfoDiff, null); 481 } 482 483 /** 484 * Check if packages are consistent, also record class deltas. 485 * <p> 486 * <ul>class deltas are: 487 * <li>brand new classes that are not present in current package 488 * <li>stripped existing classes stripped where only newly added methods are kept 489 * @param pInfo 490 * @param clsInfoDiff 491 * @param ignoredClasses 492 * @return 493 */ isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff, Collection<String> ignoredClasses)494 public boolean isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff, 495 Collection<String> ignoredClasses) { 496 boolean consistent = true; 497 boolean diffMode = clsInfoDiff != null; 498 for (ClassInfo cInfo : mClasses.values()) { 499 ArrayList<MethodInfo> newClsApis = null; 500 ArrayList<MethodInfo> newClsCtors = null; 501 502 // TODO: Add support for matching inner classes (e.g, something like 503 // example.Type.* should match example.Type.InnerType) 504 if (ignoredClasses != null && ignoredClasses.contains(cInfo.qualifiedName())) { 505 // TODO: Log skipping this? 506 continue; 507 } 508 if (pInfo.mClasses.containsKey(cInfo.name())) { 509 if (diffMode) { 510 newClsApis = new ArrayList<>(); 511 newClsCtors = new ArrayList<>(); 512 } 513 if (!cInfo.isConsistent(pInfo.mClasses.get(cInfo.name()), newClsCtors, newClsApis)) { 514 consistent = false; 515 } 516 // if we are in diff mode, add class to list if there's new ctor or new apis 517 if (diffMode && !(newClsCtors.isEmpty() && newClsApis.isEmpty())) { 518 // generate a "delta" class with only added methods and constructors, but no fields etc 519 ClassInfo deltaClsInfo = createDeltaClass(cInfo, newClsCtors, newClsApis); 520 clsInfoDiff.add(deltaClsInfo); 521 } 522 } else { 523 if (cInfo.isDeprecated()) { 524 Errors.error(Errors.REMOVED_DEPRECATED_CLASS, cInfo.position(), 525 "Removed deprecated public class " + cInfo.qualifiedName()); 526 } else { 527 Errors.error(Errors.REMOVED_CLASS, cInfo.position(), 528 "Removed public class " + cInfo.qualifiedName()); 529 } 530 consistent = false; 531 } 532 } 533 for (ClassInfo cInfo : pInfo.mClasses.values()) { 534 if (ignoredClasses != null && ignoredClasses.contains(cInfo.qualifiedName())) { 535 // TODO: Log skipping this? 536 continue; 537 } 538 if (!mClasses.containsKey(cInfo.name())) { 539 Errors.error(Errors.ADDED_CLASS, cInfo.position(), "Added class " + cInfo.name() 540 + " to package " + pInfo.name()); 541 consistent = false; 542 // brand new class, add everything as is 543 if (diffMode) { 544 clsInfoDiff.add(cInfo); 545 } 546 } 547 } 548 if (diffMode) { 549 Collections.sort(clsInfoDiff, ClassInfo.comparator); 550 } 551 return consistent; 552 } 553 } 554