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