blob: e8a6eef9db0702f92812ff62305fac68252daefd [file] [log] [blame]
/**
*******************************************************************************
* 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;
public int hashCode() {
return (((pack.hashCode() << 3) ^ cls.hashCode()) << 3) ^ name.hashCode();
}
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() {
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() {
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() {
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
// fortunately, all the packages have 4 chars (lang, math, text, util).
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() + 5; // trailing 'xxxx.'
}
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("&lt;");
} else if (c == '>') {
buf.append("&gt;");
} 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);
}
}
public String toString() {
return get(NAM, true);
}
}