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