1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /** 4 ******************************************************************************* 5 * Copyright (C) 2005-2013, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9 10 /** 11 * Represents the API information on a doc element. 12 */ 13 14 package com.ibm.icu.dev.tool.docs; 15 16 import java.io.BufferedReader; 17 import java.io.BufferedWriter; 18 import java.io.IOException; 19 import java.io.PrintWriter; 20 import java.util.Comparator; 21 22 class APIInfo { 23 // version id for the format of the APIInfo data 24 25 public static final int VERSION = 2; 26 27 // public keys and values for queries on info 28 29 public static final int STA = 0, STA_DRAFT = 0, STA_STABLE = 1, STA_DEPRECATED = 2, 30 STA_OBSOLETE = 3, STA_INTERNAL = 4; 31 public static final int VIS = 1, VIS_PACKAGE = 0, VIS_PUBLIC= 1, VIS_PROTECTED = 2, 32 VIS_PRIVATE = 3; 33 public static final int STK = 2, STK_STATIC = 1; 34 public static final int FIN = 3, FIN_FINAL = 1; 35 public static final int SYN = 4, SYN_SYNCHRONIZED = 1; 36 public static final int ABS = 5, ABS_ABSTRACT = 1; 37 public static final int CAT = 6, CAT_CLASS = 0, CAT_FIELD = 1, CAT_CONSTRUCTOR = 2, 38 CAT_METHOD = 3, CAT_ENUM = 4, CAT_ENUM_CONSTANT = 5; 39 public static final int PAK = 7; 40 public static final int CLS = 8; 41 public static final int NAM = 9; 42 public static final int SIG = 10; 43 public static final int EXC = 11; 44 public static final int NUM_TYPES = 11; 45 46 // the separator between tokens in the data file 47 public int[] masks = { 0x7, 0x3, 0x1, 0x1, 0x1, 0x1, 0x7 }; 48 public int[] shifts = { 0, 3, 5, 6, 7, 8, 9 }; 49 50 public static final char SEP = ';'; 51 52 // Internal State 53 54 private int info; // information about numeric values packed into an int 55 // as variable-length nibbles 56 private String pack = ""; // package 57 private String cls = ""; // enclosing class 58 private String name = ""; // name 59 private String sig = ""; // signature, class: inheritance, method: signature, 60 // field: type, const: signature 61 private String exc = ""; // throws 62 private String stver = ""; // status version 63 64 private boolean includeStatusVer = false; 65 66 @Override hashCode()67 public int hashCode() { 68 return (((pack.hashCode() << 3) ^ cls.hashCode()) << 3) ^ name.hashCode(); 69 } 70 71 @Override equals(Object rhs)72 public boolean equals(Object rhs) { 73 if (rhs == this) return true; 74 if (rhs == null) return false; 75 try { 76 APIInfo that = (APIInfo)rhs; 77 return this.info == that.info && 78 this.pack.equals(that.pack) && 79 this.cls.equals(that.cls) && 80 this.name.equals(that.name) && 81 this.sig.equals(that.sig) && 82 this.exc.equals(that.exc) && 83 this.stver.equals(this.stver); 84 } 85 catch (ClassCastException e) { 86 return false; 87 } 88 } 89 setDraft()90 public void setDraft() { setType(STA, STA_DRAFT); } setStable()91 public void setStable() { setType(STA, STA_STABLE); } setDeprecated()92 public void setDeprecated() { setType(STA, STA_DEPRECATED); } setObsolete()93 public void setObsolete() { setType(STA, STA_OBSOLETE); } setInternal()94 public void setInternal() { setType(STA, STA_INTERNAL); } setPackage()95 public void setPackage() { setType(VIS, VIS_PACKAGE); } setPublic()96 public void setPublic() { setType(VIS, VIS_PUBLIC); } setProtected()97 public void setProtected() { setType(VIS, VIS_PROTECTED); } setPrivate()98 public void setPrivate() { setType(VIS, VIS_PRIVATE); } setStatic()99 public void setStatic() { setType(STK, STK_STATIC); } setFinal()100 public void setFinal() { setType(FIN, FIN_FINAL); } setSynchronized()101 public void setSynchronized() { setType(SYN, SYN_SYNCHRONIZED); } setAbstract()102 public void setAbstract() { setType(ABS, ABS_ABSTRACT); } setClass()103 public void setClass() { setType(CAT, CAT_CLASS); } setField()104 public void setField() { setType(CAT, CAT_FIELD); } setConstructor()105 public void setConstructor() { setType(CAT, CAT_CONSTRUCTOR); } setMethod()106 public void setMethod() { setType(CAT, CAT_METHOD); } setEnum()107 public void setEnum() { setType(CAT, CAT_ENUM); } setEnumConstant()108 public void setEnumConstant() { setType(CAT, CAT_ENUM_CONSTANT); } 109 setPackage(String val)110 public void setPackage(String val) { setType(PAK, val); } setClassName(String val)111 public void setClassName(String val) { setType(CLS, val); } setName(String val)112 public void setName(String val) { setType(NAM, val); } setSignature(String val)113 public void setSignature(String val) { setType(SIG, val); } setExceptions(String val)114 public void setExceptions(String val) { setType(EXC, val); } 115 isDraft()116 public boolean isDraft() { return getVal(STA) == STA_DRAFT; } isStable()117 public boolean isStable() { return getVal(STA) == STA_STABLE; } isDeprecated()118 public boolean isDeprecated() { return getVal(STA) == STA_DEPRECATED; } isObsolete()119 public boolean isObsolete() { return getVal(STA) == STA_OBSOLETE; } isInternal()120 public boolean isInternal() { return getVal(STA) == STA_INTERNAL; } isPackage()121 public boolean isPackage() { return getVal(VIS) == VIS_PACKAGE; } isPublic()122 public boolean isPublic() { return getVal(VIS) == VIS_PUBLIC; } isProtected()123 public boolean isProtected() { return getVal(VIS) == VIS_PROTECTED; } isPrivate()124 public boolean isPrivate() { return getVal(VIS) == VIS_PRIVATE; } isStatic()125 public boolean isStatic() { return getVal(STK) == STK_STATIC; } isFinal()126 public boolean isFinal() { return getVal(FIN) == FIN_FINAL; } isSynchronized()127 public boolean isSynchronized() { return getVal(SYN) == SYN_SYNCHRONIZED; } isAbstract()128 public boolean isAbstract() { return getVal(ABS) == ABS_ABSTRACT; } isClass()129 public boolean isClass() { return getVal(CAT) == CAT_CLASS; } isField()130 public boolean isField() { return getVal(CAT) == CAT_FIELD; } isConstructor()131 public boolean isConstructor() { return getVal(CAT) == CAT_CONSTRUCTOR; } isMethod()132 public boolean isMethod() { return getVal(CAT) == CAT_METHOD; } isEnum()133 public boolean isEnum() { return getVal(CAT) == CAT_ENUM; } isEnumConstant()134 public boolean isEnumConstant() { return getVal(CAT) == CAT_ENUM_CONSTANT; } 135 getPackageName()136 public String getPackageName() { return get(PAK, true); } getClassName()137 public String getClassName() { return get(CLS, true); } getName()138 public String getName() { return get(NAM, true); } getSignature()139 public String getSignature() { return get(SIG, true); } getExceptions()140 public String getExceptions() { return get(EXC, true); } 141 setStatusVersion(String v)142 public void setStatusVersion(String v) { stver = v; } getStatusVersion()143 public String getStatusVersion() { return stver; } 144 145 /** 146 * Return the integer value for the provided type. The type 147 * must be one of the defined type names. The return value 148 * will be one of corresponding values for that type. 149 */ getVal(int typ)150 public int getVal(int typ) { 151 validateType(typ); 152 if (typ >= shifts.length) { 153 return 0; 154 } 155 return (info >>> shifts[typ]) & masks[typ]; 156 } 157 158 /** 159 * Return the string value for the provided type. The type 160 * must be one of the defined type names. The return value 161 * will be one of corresponding values for that type. Brief 162 * should be true for writing data files, false for presenting 163 * information to the user. 164 */ get(int typ, boolean brief)165 public String get(int typ, boolean brief) { 166 validateType(typ); 167 String[] vals = brief ? shortNames[typ] : names[typ]; 168 if (vals == null) { 169 switch (typ) { 170 case PAK: return pack; 171 case CLS: return cls; 172 case NAM: return name; 173 case SIG: return sig; 174 case EXC: return exc; 175 } 176 } 177 int val = (info >>> shifts[typ]) & masks[typ]; 178 return vals[val]; 179 } 180 181 /** 182 * Set the numeric value for the type. The value should be a 183 * value corresponding to the type. Only the lower two bits 184 * of the value are used. 185 */ setType(int typ, int val)186 public void setType(int typ, int val) { 187 validateType(typ); 188 if (typ < masks.length) { 189 info &= ~(masks[typ] << shifts[typ]); 190 info |= (val&masks[typ]) << shifts[typ]; 191 } 192 } 193 194 /** 195 * Set the string value for the type. For numeric types, 196 * the value should be a string in 'brief' format. For 197 * non-numeric types, the value can be any 198 * string. 199 */ setType(int typ, String val)200 private void setType(int typ, String val) { 201 validateType(typ); 202 String[] vals = shortNames[typ]; 203 if (vals == null) { 204 if (val == null) { 205 val = ""; 206 } 207 switch (typ) { 208 case PAK: pack = val; break; 209 case CLS: cls = val; break; 210 case NAM: name = val; break; 211 case SIG: sig = val; break; 212 case EXC: exc = val; break; 213 } 214 return; 215 } 216 217 // status version 218 String version = ""; 219 if (typ == STA) { 220 int idx = val.indexOf('@'); 221 if (idx != -1) { 222 version = val.substring(idx + 1); 223 val = val.substring(0, idx); 224 } 225 } 226 227 for (int i = 0; i < vals.length; ++i) { 228 if (val.equalsIgnoreCase(vals[i])) { 229 info &= ~(masks[typ] << shifts[typ]); 230 info |= i << shifts[typ]; 231 if (version.length() > 0) { 232 setStatusVersion(version); 233 } 234 return; 235 } 236 } 237 238 throw new IllegalArgumentException( 239 "unrecognized value '" + val + "' for type '" + typeNames[typ] + "'"); 240 } 241 242 /** 243 * Enable status version included in input/output 244 */ includeStatusVersion(boolean include)245 public void includeStatusVersion(boolean include) { 246 includeStatusVer = include; 247 } 248 249 /** 250 * Write the information out as a single line in brief format. 251 * If there are IO errors, throws a RuntimeException. 252 */ writeln(BufferedWriter w)253 public void writeln(BufferedWriter w) { 254 try { 255 for (int i = 0; i < NUM_TYPES; ++i) { 256 String s = get(i, true); 257 if (s != null) { 258 w.write(s); 259 } 260 if (includeStatusVer && i == STA) { 261 String ver = getStatusVersion(); 262 if (ver.length() > 0) { 263 w.write("@"); 264 w.write(getStatusVersion()); 265 } 266 } 267 w.write(SEP); 268 } 269 w.newLine(); 270 } 271 catch (IOException e) { 272 RuntimeException re = new RuntimeException("IO Error"); 273 re.initCause(e); 274 throw re; 275 } 276 } 277 278 /** 279 * Read a record from the input and initialize this APIInfo. 280 * Return true if successful, false if EOF, otherwise throw 281 * a RuntimeException. 282 */ read(BufferedReader r)283 public boolean read(BufferedReader r) { 284 int i = 0; 285 try { 286 for (; i < NUM_TYPES; ++i) { 287 setType(i, readToken(r)); 288 } 289 r.readLine(); // swallow line end sequence 290 } 291 catch (IOException e) { 292 if (i == 0) { // assume if first read returns error, we have reached end of input 293 return false; 294 } 295 RuntimeException re = new RuntimeException("IO Error"); 296 re.initCause(e); 297 throw re; 298 } 299 300 return true; 301 } 302 303 /** 304 * Read one token from input, which should have been written by 305 * APIInfo. Throws IOException if EOF is encountered before the 306 * token is complete (i.e. before the separator character is 307 * encountered) or if the token exceeds the maximum length of 308 * 511 chars. 309 */ readToken(BufferedReader r)310 public static String readToken(BufferedReader r) throws IOException { 311 char[] buf = new char[512]; 312 int i = 0; 313 for (; i < buf.length; ++i) { 314 int c = r.read(); 315 if (c == -1) { 316 throw new IOException("unexpected EOF"); 317 } else if (c == SEP) { 318 break; 319 } 320 buf[i] = (char)c; 321 } 322 if (i == buf.length) { 323 throw new IOException("unterminated token" + new String(buf)); 324 } 325 326 return new String(buf, 0, i); 327 } 328 329 /** 330 * The default comparator for APIInfo. This compares packages, class/name 331 * (as the info represents a class or other object), category, name, 332 * and signature. 333 */ defaultComparator()334 public static Comparator defaultComparator() { 335 final Comparator c = new Comparator() { 336 @Override 337 public int compare(Object lhs, Object rhs) { 338 APIInfo lhi = (APIInfo)lhs; 339 APIInfo rhi = (APIInfo)rhs; 340 int result = lhi.pack.compareTo(rhi.pack); 341 if (result == 0) { 342 result = (lhi.getVal(CAT) == CAT_CLASS || lhi.getVal(CAT) == CAT_ENUM ? lhi.name : lhi.cls) 343 .compareTo(rhi.getVal(CAT) == CAT_CLASS || rhi.getVal(CAT) == CAT_ENUM ? rhi.name : rhi.cls); 344 if (result == 0) { 345 result = lhi.getVal(CAT)- rhi.getVal(CAT); 346 if (result == 0) { 347 result = lhi.name.compareTo(rhi.name); 348 if (result == 0) { 349 result = lhi.sig.compareTo(rhi.sig); 350 } 351 } 352 } 353 } 354 return result; 355 } 356 }; 357 return c; 358 } 359 360 /** 361 * This compares two APIInfos by package, class/name, category, name, and then if 362 * the APIInfo does not represent a class, by signature. The difference between 363 * this and the default comparator is that APIInfos representing classes are considered 364 * equal regardless of their signatures (which represent inheritance for classes). 365 */ changedComparator()366 public static Comparator changedComparator() { 367 final Comparator c = new Comparator() { 368 @Override 369 public int compare(Object lhs, Object rhs) { 370 APIInfo lhi = (APIInfo)lhs; 371 APIInfo rhi = (APIInfo)rhs; 372 int result = lhi.pack.compareTo(rhi.pack); 373 if (result == 0) { 374 result = (lhi.getVal(CAT) == CAT_CLASS ? lhi.name : lhi.cls) 375 .compareTo(rhi.getVal(CAT) == CAT_CLASS ? rhi.name : rhi.cls); 376 if (result == 0) { 377 result = lhi.getVal(CAT)- rhi.getVal(CAT); 378 if (result == 0) { 379 result = lhi.name.compareTo(rhi.name); 380 if (result == 0 && lhi.getVal(CAT) != CAT_CLASS) { 381 // signature change on fields ignored 382 if (lhi.getVal(CAT) != CAT_FIELD) { 383 result = lhi.sig.compareTo(rhi.sig); 384 } 385 } 386 } 387 } 388 } 389 return result; 390 } 391 }; 392 return c; 393 } 394 395 /** 396 * This compares two APIInfos by package, then sorts classes before non-classes, then 397 * by class/name, category, name, and signature. 398 */ classFirstComparator()399 public static Comparator classFirstComparator() { 400 final Comparator c = new Comparator() { 401 @Override 402 public int compare(Object lhs, Object rhs) { 403 APIInfo lhi = (APIInfo)lhs; 404 APIInfo rhi = (APIInfo)rhs; 405 int result = lhi.pack.compareTo(rhi.pack); 406 if (result == 0) { 407 boolean lcls = lhi.getVal(CAT) == CAT_CLASS; 408 boolean rcls = rhi.getVal(CAT) == CAT_CLASS; 409 result = lcls == rcls ? 0 : (lcls ? -1 : 1); 410 if (result == 0) { 411 result = (lcls ? lhi.name : lhi.cls).compareTo( 412 rcls ? rhi.name : rhi.cls); 413 if (result == 0) { 414 result = lhi.getVal(CAT)- rhi.getVal(CAT); 415 if (result == 0) { 416 result = lhi.name.compareTo(rhi.name); 417 if (result == 0 && !lcls) { 418 result = lhi.sig.compareTo(rhi.sig); 419 } 420 } 421 } 422 } 423 } 424 return result; 425 } 426 }; 427 return c; 428 } 429 430 /** 431 * Write the data in report format. 432 */ print(PrintWriter pw, boolean detail, boolean html)433 public void print(PrintWriter pw, boolean detail, boolean html) { 434 print(pw, detail, html, true); 435 } 436 print(PrintWriter pw, boolean detail, boolean html, boolean withStatus)437 public void print(PrintWriter pw, boolean detail, boolean html, boolean withStatus) { 438 StringBuilder buf = new StringBuilder(); 439 format(buf, detail, html, withStatus); 440 pw.print(buf.toString()); 441 } 442 format(StringBuilder buf, boolean detail, boolean html, boolean withStatus)443 public void format(StringBuilder buf, boolean detail, boolean html, boolean withStatus) { 444 // remove all occurrences of icu packages from the param string 445 String xsig = sig; 446 if (!detail) { 447 final String ICUPACK = "com.ibm.icu."; 448 StringBuilder tbuf = new StringBuilder(); 449 for (int i = 0; i < sig.length();) { 450 int n = sig.indexOf(ICUPACK, i); 451 if (n == -1) { 452 tbuf.append(sig.substring(i)); 453 break; 454 } 455 tbuf.append(sig.substring(i, n)); 456 i = n + ICUPACK.length(); 457 // skip icu public package lang/math/number/text/util 458 n = sig.indexOf('.', i); 459 if (n >= 0) { 460 i = n + 1; 461 } 462 } 463 xsig = tbuf.toString(); 464 } 465 466 // construct signature 467 for (int i = (withStatus ? STA : VIS) ; i < CAT; ++i) { // include status 468 String s = get(i, false); 469 if (s != null && s.length() > 0) { 470 if (html) { 471 s = s.trim(); 472 if (i == STA) { 473 String color = null; 474 if (s.startsWith("(internal)")) { 475 color = "red"; 476 } else if (s.startsWith("(draft)")) { 477 color = "orange"; 478 } else if (s.startsWith("(stable)")) { 479 color = "green"; 480 } else if (s.startsWith("(deprecated)")) { 481 color = "gray"; 482 } 483 if (color != null) { 484 s = "<span style='color:" + color + "'>" + prepText(s, html) + "</span>"; 485 } 486 } 487 } 488 buf.append(s); 489 buf.append(' '); 490 } 491 } 492 493 int val = getVal(CAT); 494 switch (val) { 495 case CAT_CLASS: 496 if (sig.indexOf("extends") == -1) { 497 buf.append("interface "); 498 } else { 499 buf.append("class "); 500 } 501 if (html) { 502 buf.append("<i>"); 503 } 504 if (cls.length() > 0) { 505 buf.append(prepText(cls, html)); 506 buf.append('.'); 507 } 508 buf.append(prepText(name, html)); 509 if (html) { 510 buf.append("</i>"); 511 } 512 if (detail) { 513 buf.append(' '); 514 buf.append(prepText(sig, html)); 515 } 516 break; 517 518 case CAT_ENUM: 519 buf.append("enum "); 520 if (html) { 521 buf.append("<i>"); 522 } 523 if (cls.length() > 0) { 524 buf.append(prepText(cls, html)); 525 buf.append('.'); 526 } 527 buf.append(prepText(name, html)); 528 if (html) { 529 buf.append("</i>"); 530 } 531 if (detail) { 532 buf.append(' '); 533 buf.append(prepText(sig, html)); 534 } 535 break; 536 537 case CAT_FIELD: 538 case CAT_ENUM_CONSTANT: 539 buf.append(prepText(xsig, html)); 540 buf.append(' '); 541 buf.append(prepText(name, html)); 542 break; 543 544 case CAT_METHOD: 545 case CAT_CONSTRUCTOR: 546 int n = xsig.indexOf('('); 547 if (n > 0) { 548 buf.append(prepText(xsig.substring(0, n), html)); 549 buf.append(' '); 550 } else { 551 n = 0; 552 } 553 if (html) { 554 buf.append("<i>" + prepText(name, html) + "</i>"); 555 } else { 556 buf.append(name); 557 } 558 buf.append(prepText(xsig.substring(n), html)); 559 break; 560 } 561 } 562 prepText(String text, boolean html)563 private static String prepText(String text, boolean html) { 564 if (html && (text.indexOf('<') >= 0 || text.indexOf('>') >= 0)) { 565 StringBuilder buf = new StringBuilder(); 566 for (int i = 0; i < text.length(); i++) { 567 char c = text.charAt(i); 568 if (c == '<') { 569 buf.append("<"); 570 } else if (c == '>') { 571 buf.append(">"); 572 } else { 573 buf.append(c); 574 } 575 } 576 text = buf.toString(); 577 } 578 return text; 579 } 580 println(PrintWriter pw, boolean detail, boolean html)581 public void println(PrintWriter pw, boolean detail, boolean html) { 582 print(pw, detail, html); 583 pw.println(); 584 } 585 586 private static final String[] typeNames = { 587 "status", "visibility", "static", "final", "synchronized", 588 "abstract", "category", "package", "class", "name", "signature" 589 }; 590 getTypeValName(int typ, int val)591 public static final String getTypeValName(int typ, int val) { 592 try { 593 return names[typ][val]; 594 } 595 catch (Exception e) { 596 return ""; 597 } 598 } 599 600 private static final String[][] names = { 601 { "(draft) ", "(stable) ", "(deprecated)", "(obsolete) ", "*internal* " }, 602 { "package", "public", "protected", "private" }, 603 { "", "static" }, 604 { "", "final" }, 605 { "", "synchronized" }, 606 { "", "abstract" }, 607 { "class", "field", "constructor", "method", "enum", "enum constant" }, 608 null, 609 null, 610 null, 611 null, 612 null 613 }; 614 615 private static final String[][] shortNames = { 616 { "DR", "ST", "DP", "OB", "IN" }, 617 { "PK", "PB", "PT", "PR" }, 618 { "NS", "ST" }, 619 { "NF", "FN" }, 620 { "NS", "SY" }, 621 { "NA", "AB" }, 622 { "L", "F", "C", "M", "E", "K" }, 623 null, 624 null, 625 null, 626 null, 627 null 628 }; 629 validateType(int typ)630 private static void validateType(int typ) { 631 if (typ < 0 || typ > NUM_TYPES) { 632 throw new IllegalArgumentException("bad type index: " + typ); 633 } 634 } 635 636 @Override toString()637 public String toString() { 638 return get(NAM, true); 639 } 640 } 641