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