• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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("&lt;");
559                 } else if (c == '>') {
560                     buf.append("&gt;");
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