| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /** |
| ******************************************************************************* |
| * Copyright (C) 2005-2013, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| |
| /** |
| * Represents the API information on a doc element. |
| */ |
| |
| package com.ibm.icu.dev.tool.docs; |
| |
| import java.io.BufferedReader; |
| import java.io.BufferedWriter; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.Comparator; |
| |
| class APIInfo { |
| // version id for the format of the APIInfo data |
| |
| public static final int VERSION = 2; |
| |
| // public keys and values for queries on info |
| |
| public static final int STA = 0, STA_DRAFT = 0, STA_STABLE = 1, STA_DEPRECATED = 2, |
| STA_OBSOLETE = 3, STA_INTERNAL = 4; |
| public static final int VIS = 1, VIS_PACKAGE = 0, VIS_PUBLIC= 1, VIS_PROTECTED = 2, |
| VIS_PRIVATE = 3; |
| public static final int STK = 2, STK_STATIC = 1; |
| public static final int FIN = 3, FIN_FINAL = 1; |
| public static final int SYN = 4, SYN_SYNCHRONIZED = 1; |
| public static final int ABS = 5, ABS_ABSTRACT = 1; |
| public static final int CAT = 6, CAT_CLASS = 0, CAT_FIELD = 1, CAT_CONSTRUCTOR = 2, |
| CAT_METHOD = 3, CAT_ENUM = 4, CAT_ENUM_CONSTANT = 5; |
| public static final int PAK = 7; |
| public static final int CLS = 8; |
| public static final int NAM = 9; |
| public static final int SIG = 10; |
| public static final int EXC = 11; |
| public static final int NUM_TYPES = 11; |
| |
| // the separator between tokens in the data file |
| public int[] masks = { 0x7, 0x3, 0x1, 0x1, 0x1, 0x1, 0x7 }; |
| public int[] shifts = { 0, 3, 5, 6, 7, 8, 9 }; |
| |
| public static final char SEP = ';'; |
| |
| // Internal State |
| |
| private int info; // information about numeric values packed into an int |
| // as variable-length nibbles |
| private String pack = ""; // package |
| private String cls = ""; // enclosing class |
| private String name = ""; // name |
| private String sig = ""; // signature, class: inheritance, method: signature, |
| // field: type, const: signature |
| private String exc = ""; // throws |
| private String stver = ""; // status version |
| |
| private boolean includeStatusVer = false; |
| |
| @Override |
| public int hashCode() { |
| return (((pack.hashCode() << 3) ^ cls.hashCode()) << 3) ^ name.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(Object rhs) { |
| if (rhs == this) return true; |
| if (rhs == null) return false; |
| try { |
| APIInfo that = (APIInfo)rhs; |
| return this.info == that.info && |
| this.pack.equals(that.pack) && |
| this.cls.equals(that.cls) && |
| this.name.equals(that.name) && |
| this.sig.equals(that.sig) && |
| this.exc.equals(that.exc) && |
| this.stver.equals(this.stver); |
| } |
| catch (ClassCastException e) { |
| return false; |
| } |
| } |
| |
| public void setDraft() { setType(STA, STA_DRAFT); } |
| public void setStable() { setType(STA, STA_STABLE); } |
| public void setDeprecated() { setType(STA, STA_DEPRECATED); } |
| public void setObsolete() { setType(STA, STA_OBSOLETE); } |
| public void setInternal() { setType(STA, STA_INTERNAL); } |
| public void setPackage() { setType(VIS, VIS_PACKAGE); } |
| public void setPublic() { setType(VIS, VIS_PUBLIC); } |
| public void setProtected() { setType(VIS, VIS_PROTECTED); } |
| public void setPrivate() { setType(VIS, VIS_PRIVATE); } |
| public void setStatic() { setType(STK, STK_STATIC); } |
| public void setFinal() { setType(FIN, FIN_FINAL); } |
| public void setSynchronized() { setType(SYN, SYN_SYNCHRONIZED); } |
| public void setAbstract() { setType(ABS, ABS_ABSTRACT); } |
| public void setClass() { setType(CAT, CAT_CLASS); } |
| public void setField() { setType(CAT, CAT_FIELD); } |
| public void setConstructor() { setType(CAT, CAT_CONSTRUCTOR); } |
| public void setMethod() { setType(CAT, CAT_METHOD); } |
| public void setEnum() { setType(CAT, CAT_ENUM); } |
| public void setEnumConstant() { setType(CAT, CAT_ENUM_CONSTANT); } |
| |
| public void setPackage(String val) { setType(PAK, val); } |
| public void setClassName(String val) { setType(CLS, val); } |
| public void setName(String val) { setType(NAM, val); } |
| public void setSignature(String val) { setType(SIG, val); } |
| public void setExceptions(String val) { setType(EXC, val); } |
| |
| public boolean isDraft() { return getVal(STA) == STA_DRAFT; } |
| public boolean isStable() { return getVal(STA) == STA_STABLE; } |
| public boolean isDeprecated() { return getVal(STA) == STA_DEPRECATED; } |
| public boolean isObsolete() { return getVal(STA) == STA_OBSOLETE; } |
| public boolean isInternal() { return getVal(STA) == STA_INTERNAL; } |
| public boolean isPackage() { return getVal(VIS) == VIS_PACKAGE; } |
| public boolean isPublic() { return getVal(VIS) == VIS_PUBLIC; } |
| public boolean isProtected() { return getVal(VIS) == VIS_PROTECTED; } |
| public boolean isPrivate() { return getVal(VIS) == VIS_PRIVATE; } |
| public boolean isStatic() { return getVal(STK) == STK_STATIC; } |
| public boolean isFinal() { return getVal(FIN) == FIN_FINAL; } |
| public boolean isSynchronized() { return getVal(SYN) == SYN_SYNCHRONIZED; } |
| public boolean isAbstract() { return getVal(ABS) == ABS_ABSTRACT; } |
| public boolean isClass() { return getVal(CAT) == CAT_CLASS; } |
| public boolean isField() { return getVal(CAT) == CAT_FIELD; } |
| public boolean isConstructor() { return getVal(CAT) == CAT_CONSTRUCTOR; } |
| public boolean isMethod() { return getVal(CAT) == CAT_METHOD; } |
| public boolean isEnum() { return getVal(CAT) == CAT_ENUM; } |
| public boolean isEnumConstant() { return getVal(CAT) == CAT_ENUM_CONSTANT; } |
| |
| public String getPackageName() { return get(PAK, true); } |
| public String getClassName() { return get(CLS, true); } |
| public String getName() { return get(NAM, true); } |
| public String getSignature() { return get(SIG, true); } |
| public String getExceptions() { return get(EXC, true); } |
| |
| public void setStatusVersion(String v) { stver = v; } |
| public String getStatusVersion() { return stver; } |
| |
| /** |
| * Return the integer value for the provided type. The type |
| * must be one of the defined type names. The return value |
| * will be one of corresponding values for that type. |
| */ |
| public int getVal(int typ) { |
| validateType(typ); |
| if (typ >= shifts.length) { |
| return 0; |
| } |
| return (info >>> shifts[typ]) & masks[typ]; |
| } |
| |
| /** |
| * Return the string value for the provided type. The type |
| * must be one of the defined type names. The return value |
| * will be one of corresponding values for that type. Brief |
| * should be true for writing data files, false for presenting |
| * information to the user. |
| */ |
| public String get(int typ, boolean brief) { |
| validateType(typ); |
| String[] vals = brief ? shortNames[typ] : names[typ]; |
| if (vals == null) { |
| switch (typ) { |
| case PAK: return pack; |
| case CLS: return cls; |
| case NAM: return name; |
| case SIG: return sig; |
| case EXC: return exc; |
| } |
| } |
| int val = (info >>> shifts[typ]) & masks[typ]; |
| return vals[val]; |
| } |
| |
| /** |
| * Set the numeric value for the type. The value should be a |
| * value corresponding to the type. Only the lower two bits |
| * of the value are used. |
| */ |
| public void setType(int typ, int val) { |
| validateType(typ); |
| if (typ < masks.length) { |
| info &= ~(masks[typ] << shifts[typ]); |
| info |= (val&masks[typ]) << shifts[typ]; |
| } |
| } |
| |
| /** |
| * Set the string value for the type. For numeric types, |
| * the value should be a string in 'brief' format. For |
| * non-numeric types, the value can be any |
| * string. |
| */ |
| private void setType(int typ, String val) { |
| validateType(typ); |
| String[] vals = shortNames[typ]; |
| if (vals == null) { |
| if (val == null) { |
| val = ""; |
| } |
| switch (typ) { |
| case PAK: pack = val; break; |
| case CLS: cls = val; break; |
| case NAM: name = val; break; |
| case SIG: sig = val; break; |
| case EXC: exc = val; break; |
| } |
| return; |
| } |
| |
| // status version |
| String version = ""; |
| if (typ == STA) { |
| int idx = val.indexOf('@'); |
| if (idx != -1) { |
| version = val.substring(idx + 1); |
| val = val.substring(0, idx); |
| } |
| } |
| |
| for (int i = 0; i < vals.length; ++i) { |
| if (val.equalsIgnoreCase(vals[i])) { |
| info &= ~(masks[typ] << shifts[typ]); |
| info |= i << shifts[typ]; |
| if (version.length() > 0) { |
| setStatusVersion(version); |
| } |
| return; |
| } |
| } |
| |
| throw new IllegalArgumentException( |
| "unrecognized value '" + val + "' for type '" + typeNames[typ] + "'"); |
| } |
| |
| /** |
| * Enable status version included in input/output |
| */ |
| public void includeStatusVersion(boolean include) { |
| includeStatusVer = include; |
| } |
| |
| /** |
| * Write the information out as a single line in brief format. |
| * If there are IO errors, throws a RuntimeException. |
| */ |
| public void writeln(BufferedWriter w) { |
| try { |
| for (int i = 0; i < NUM_TYPES; ++i) { |
| String s = get(i, true); |
| if (s != null) { |
| w.write(s); |
| } |
| if (includeStatusVer && i == STA) { |
| String ver = getStatusVersion(); |
| if (ver.length() > 0) { |
| w.write("@"); |
| w.write(getStatusVersion()); |
| } |
| } |
| w.write(SEP); |
| } |
| w.newLine(); |
| } |
| catch (IOException e) { |
| RuntimeException re = new RuntimeException("IO Error"); |
| re.initCause(e); |
| throw re; |
| } |
| } |
| |
| /** |
| * Read a record from the input and initialize this APIInfo. |
| * Return true if successful, false if EOF, otherwise throw |
| * a RuntimeException. |
| */ |
| public boolean read(BufferedReader r) { |
| int i = 0; |
| try { |
| for (; i < NUM_TYPES; ++i) { |
| setType(i, readToken(r)); |
| } |
| r.readLine(); // swallow line end sequence |
| } |
| catch (IOException e) { |
| if (i == 0) { // assume if first read returns error, we have reached end of input |
| return false; |
| } |
| RuntimeException re = new RuntimeException("IO Error"); |
| re.initCause(e); |
| throw re; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Read one token from input, which should have been written by |
| * APIInfo. Throws IOException if EOF is encountered before the |
| * token is complete (i.e. before the separator character is |
| * encountered) or if the token exceeds the maximum length of |
| * 511 chars. |
| */ |
| public static String readToken(BufferedReader r) throws IOException { |
| char[] buf = new char[512]; |
| int i = 0; |
| for (; i < buf.length; ++i) { |
| int c = r.read(); |
| if (c == -1) { |
| throw new IOException("unexpected EOF"); |
| } else if (c == SEP) { |
| break; |
| } |
| buf[i] = (char)c; |
| } |
| if (i == buf.length) { |
| throw new IOException("unterminated token" + new String(buf)); |
| } |
| |
| return new String(buf, 0, i); |
| } |
| |
| /** |
| * The default comparator for APIInfo. This compares packages, class/name |
| * (as the info represents a class or other object), category, name, |
| * and signature. |
| */ |
| public static Comparator defaultComparator() { |
| final Comparator c = new Comparator() { |
| @Override |
| public int compare(Object lhs, Object rhs) { |
| APIInfo lhi = (APIInfo)lhs; |
| APIInfo rhi = (APIInfo)rhs; |
| int result = lhi.pack.compareTo(rhi.pack); |
| if (result == 0) { |
| result = (lhi.getVal(CAT) == CAT_CLASS || lhi.getVal(CAT) == CAT_ENUM ? lhi.name : lhi.cls) |
| .compareTo(rhi.getVal(CAT) == CAT_CLASS || rhi.getVal(CAT) == CAT_ENUM ? rhi.name : rhi.cls); |
| if (result == 0) { |
| result = lhi.getVal(CAT)- rhi.getVal(CAT); |
| if (result == 0) { |
| result = lhi.name.compareTo(rhi.name); |
| if (result == 0) { |
| result = lhi.sig.compareTo(rhi.sig); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| }; |
| return c; |
| } |
| |
| /** |
| * This compares two APIInfos by package, class/name, category, name, and then if |
| * the APIInfo does not represent a class, by signature. The difference between |
| * this and the default comparator is that APIInfos representing classes are considered |
| * equal regardless of their signatures (which represent inheritance for classes). |
| */ |
| public static Comparator changedComparator() { |
| final Comparator c = new Comparator() { |
| @Override |
| public int compare(Object lhs, Object rhs) { |
| APIInfo lhi = (APIInfo)lhs; |
| APIInfo rhi = (APIInfo)rhs; |
| int result = lhi.pack.compareTo(rhi.pack); |
| if (result == 0) { |
| result = (lhi.getVal(CAT) == CAT_CLASS ? lhi.name : lhi.cls) |
| .compareTo(rhi.getVal(CAT) == CAT_CLASS ? rhi.name : rhi.cls); |
| if (result == 0) { |
| result = lhi.getVal(CAT)- rhi.getVal(CAT); |
| if (result == 0) { |
| result = lhi.name.compareTo(rhi.name); |
| if (result == 0 && lhi.getVal(CAT) != CAT_CLASS) { |
| // signature change on fields ignored |
| if (lhi.getVal(CAT) != CAT_FIELD) { |
| result = lhi.sig.compareTo(rhi.sig); |
| } |
| } |
| } |
| } |
| } |
| return result; |
| } |
| }; |
| return c; |
| } |
| |
| /** |
| * This compares two APIInfos by package, then sorts classes before non-classes, then |
| * by class/name, category, name, and signature. |
| */ |
| public static Comparator classFirstComparator() { |
| final Comparator c = new Comparator() { |
| @Override |
| public int compare(Object lhs, Object rhs) { |
| APIInfo lhi = (APIInfo)lhs; |
| APIInfo rhi = (APIInfo)rhs; |
| int result = lhi.pack.compareTo(rhi.pack); |
| if (result == 0) { |
| boolean lcls = lhi.getVal(CAT) == CAT_CLASS; |
| boolean rcls = rhi.getVal(CAT) == CAT_CLASS; |
| result = lcls == rcls ? 0 : (lcls ? -1 : 1); |
| if (result == 0) { |
| result = (lcls ? lhi.name : lhi.cls).compareTo( |
| rcls ? rhi.name : rhi.cls); |
| if (result == 0) { |
| result = lhi.getVal(CAT)- rhi.getVal(CAT); |
| if (result == 0) { |
| result = lhi.name.compareTo(rhi.name); |
| if (result == 0 && !lcls) { |
| result = lhi.sig.compareTo(rhi.sig); |
| } |
| } |
| } |
| } |
| } |
| return result; |
| } |
| }; |
| return c; |
| } |
| |
| /** |
| * Write the data in report format. |
| */ |
| public void print(PrintWriter pw, boolean detail, boolean html) { |
| print(pw, detail, html, true); |
| } |
| |
| public void print(PrintWriter pw, boolean detail, boolean html, boolean withStatus) { |
| StringBuilder buf = new StringBuilder(); |
| format(buf, detail, html, withStatus); |
| pw.print(buf.toString()); |
| } |
| |
| public void format(StringBuilder buf, boolean detail, boolean html, boolean withStatus) { |
| // remove all occurrences of icu packages from the param string |
| String xsig = sig; |
| if (!detail) { |
| final String ICUPACK = "com.ibm.icu."; |
| StringBuilder tbuf = new StringBuilder(); |
| for (int i = 0; i < sig.length();) { |
| int n = sig.indexOf(ICUPACK, i); |
| if (n == -1) { |
| tbuf.append(sig.substring(i)); |
| break; |
| } |
| tbuf.append(sig.substring(i, n)); |
| i = n + ICUPACK.length(); |
| // skip icu public package lang/math/number/text/util |
| n = sig.indexOf('.', i); |
| if (n >= 0) { |
| i = n + 1; |
| } |
| } |
| xsig = tbuf.toString(); |
| } |
| |
| // construct signature |
| for (int i = (withStatus ? STA : VIS) ; i < CAT; ++i) { // include status |
| String s = get(i, false); |
| if (s != null && s.length() > 0) { |
| if (html) { |
| s = s.trim(); |
| if (i == STA) { |
| String color = null; |
| if (s.startsWith("(internal)")) { |
| color = "red"; |
| } else if (s.startsWith("(draft)")) { |
| color = "orange"; |
| } else if (s.startsWith("(stable)")) { |
| color = "green"; |
| } else if (s.startsWith("(deprecated)")) { |
| color = "gray"; |
| } |
| if (color != null) { |
| s = "<span style='color:" + color + "'>" + prepText(s, html) + "</span>"; |
| } |
| } |
| } |
| buf.append(s); |
| buf.append(' '); |
| } |
| } |
| |
| int val = getVal(CAT); |
| switch (val) { |
| case CAT_CLASS: |
| if (sig.indexOf("extends") == -1) { |
| buf.append("interface "); |
| } else { |
| buf.append("class "); |
| } |
| if (html) { |
| buf.append("<i>"); |
| } |
| if (cls.length() > 0) { |
| buf.append(prepText(cls, html)); |
| buf.append('.'); |
| } |
| buf.append(prepText(name, html)); |
| if (html) { |
| buf.append("</i>"); |
| } |
| if (detail) { |
| buf.append(' '); |
| buf.append(prepText(sig, html)); |
| } |
| break; |
| |
| case CAT_ENUM: |
| buf.append("enum "); |
| if (html) { |
| buf.append("<i>"); |
| } |
| if (cls.length() > 0) { |
| buf.append(prepText(cls, html)); |
| buf.append('.'); |
| } |
| buf.append(prepText(name, html)); |
| if (html) { |
| buf.append("</i>"); |
| } |
| if (detail) { |
| buf.append(' '); |
| buf.append(prepText(sig, html)); |
| } |
| break; |
| |
| case CAT_FIELD: |
| case CAT_ENUM_CONSTANT: |
| buf.append(prepText(xsig, html)); |
| buf.append(' '); |
| buf.append(prepText(name, html)); |
| break; |
| |
| case CAT_METHOD: |
| case CAT_CONSTRUCTOR: |
| int n = xsig.indexOf('('); |
| if (n > 0) { |
| buf.append(prepText(xsig.substring(0, n), html)); |
| buf.append(' '); |
| } else { |
| n = 0; |
| } |
| if (html) { |
| buf.append("<i>" + prepText(name, html) + "</i>"); |
| } else { |
| buf.append(name); |
| } |
| buf.append(prepText(xsig.substring(n), html)); |
| break; |
| } |
| } |
| |
| private static String prepText(String text, boolean html) { |
| if (html && (text.indexOf('<') >= 0 || text.indexOf('>') >= 0)) { |
| StringBuilder buf = new StringBuilder(); |
| for (int i = 0; i < text.length(); i++) { |
| char c = text.charAt(i); |
| if (c == '<') { |
| buf.append("<"); |
| } else if (c == '>') { |
| buf.append(">"); |
| } else { |
| buf.append(c); |
| } |
| } |
| text = buf.toString(); |
| } |
| return text; |
| } |
| |
| public void println(PrintWriter pw, boolean detail, boolean html) { |
| print(pw, detail, html); |
| pw.println(); |
| } |
| |
| private static final String[] typeNames = { |
| "status", "visibility", "static", "final", "synchronized", |
| "abstract", "category", "package", "class", "name", "signature" |
| }; |
| |
| public static final String getTypeValName(int typ, int val) { |
| try { |
| return names[typ][val]; |
| } |
| catch (Exception e) { |
| return ""; |
| } |
| } |
| |
| private static final String[][] names = { |
| { "(draft) ", "(stable) ", "(deprecated)", "(obsolete) ", "*internal* " }, |
| { "package", "public", "protected", "private" }, |
| { "", "static" }, |
| { "", "final" }, |
| { "", "synchronized" }, |
| { "", "abstract" }, |
| { "class", "field", "constructor", "method", "enum", "enum constant" }, |
| null, |
| null, |
| null, |
| null, |
| null |
| }; |
| |
| private static final String[][] shortNames = { |
| { "DR", "ST", "DP", "OB", "IN" }, |
| { "PK", "PB", "PT", "PR" }, |
| { "NS", "ST" }, |
| { "NF", "FN" }, |
| { "NS", "SY" }, |
| { "NA", "AB" }, |
| { "L", "F", "C", "M", "E", "K" }, |
| null, |
| null, |
| null, |
| null, |
| null |
| }; |
| |
| private static void validateType(int typ) { |
| if (typ < 0 || typ > NUM_TYPES) { |
| throw new IllegalArgumentException("bad type index: " + typ); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return get(NAM, true); |
| } |
| } |