Home | History | Annotate | Download | only in doclava
      1 /*
      2  * Copyright (C) 2010 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.doclava;
     18 
     19 import com.google.doclava.apicheck.ApiParseException;
     20 import com.google.clearsilver.jsilver.data.Data;
     21 import java.util.Comparator;
     22 import java.util.ArrayList;
     23 
     24 public class FieldInfo extends MemberInfo {
     25   public static final Comparator<FieldInfo> comparator = new Comparator<FieldInfo>() {
     26     public int compare(FieldInfo a, FieldInfo b) {
     27       return a.name().compareTo(b.name());
     28     }
     29   };
     30 
     31   public FieldInfo(String name, ClassInfo containingClass, ClassInfo realContainingClass,
     32       boolean isPublic, boolean isProtected, boolean isPackagePrivate, boolean isPrivate,
     33       boolean isFinal, boolean isStatic, boolean isTransient, boolean isVolatile,
     34       boolean isSynthetic, TypeInfo type, String rawCommentText, Object constantValue,
     35       SourcePositionInfo position, ArrayList<AnnotationInstanceInfo> annotations) {
     36     super(rawCommentText, name, null, containingClass, realContainingClass, isPublic, isProtected,
     37           isPackagePrivate, isPrivate, isFinal, isStatic, isSynthetic, chooseKind(isFinal, isStatic, constantValue),
     38         position, annotations);
     39     mIsTransient = isTransient;
     40     mIsVolatile = isVolatile;
     41     mType = type;
     42     mConstantValue = constantValue;
     43   }
     44 
     45   public FieldInfo cloneForClass(ClassInfo newContainingClass) {
     46     return new FieldInfo(name(), newContainingClass, realContainingClass(), isPublic(),
     47         isProtected(), isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isTransient(),
     48         isVolatile(), isSynthetic(), mType, getRawCommentText(), mConstantValue, position(),
     49         annotations());
     50   }
     51 
     52   static String chooseKind(boolean isFinal, boolean isStatic, Object constantValue)
     53   {
     54     return isConstant(isFinal, isStatic, constantValue) ? "constant" : "field";
     55   }
     56 
     57   public String qualifiedName() {
     58     String parentQName
     59         = (containingClass() != null) ? (containingClass().qualifiedName() + ".") : "";
     60     return parentQName + name();
     61   }
     62 
     63   public TypeInfo type() {
     64     return mType;
     65   }
     66 
     67   static boolean isConstant(boolean isFinal, boolean isStatic, Object constantValue)
     68   {
     69     /*
     70      * Note: There is an ambiguity in the doc API that prevents us
     71      * from distinguishing a constant-null from the lack of a
     72      * constant at all. We err on the side of treating all null
     73      * constantValues as meaning that the field is not a constant,
     74      * since having a static final field assigned to null is both
     75      * unusual and generally pretty useless.
     76      */
     77     return isFinal && isStatic && (constantValue != null);
     78   }
     79 
     80   public boolean isConstant() {
     81     return isConstant(isFinal(), isStatic(), mConstantValue);
     82   }
     83 
     84   public TagInfo[] firstSentenceTags() {
     85     return comment().briefTags();
     86   }
     87 
     88   public TagInfo[] inlineTags() {
     89     return comment().tags();
     90   }
     91 
     92   public Object constantValue() {
     93     return mConstantValue;
     94   }
     95 
     96   public String constantLiteralValue() {
     97     return constantLiteralValue(mConstantValue);
     98   }
     99 
    100   public void setDeprecated(boolean deprecated) {
    101     mDeprecatedKnown = true;
    102     mIsDeprecated = deprecated;
    103   }
    104 
    105   public boolean isDeprecated() {
    106     if (!mDeprecatedKnown) {
    107       boolean commentDeprecated = comment().isDeprecated();
    108       boolean annotationDeprecated = false;
    109       for (AnnotationInstanceInfo annotation : annotations()) {
    110         if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) {
    111           annotationDeprecated = true;
    112           break;
    113         }
    114       }
    115 
    116       if (commentDeprecated != annotationDeprecated) {
    117         Errors.error(Errors.DEPRECATION_MISMATCH, position(), "Field "
    118             + mContainingClass.qualifiedName() + "." + name()
    119             + ": @Deprecated annotation and @deprecated comment do not match");
    120       }
    121 
    122       mIsDeprecated = commentDeprecated | annotationDeprecated;
    123       mDeprecatedKnown = true;
    124     }
    125     return mIsDeprecated;
    126   }
    127 
    128   public static String constantLiteralValue(Object val) {
    129     String str = null;
    130     if (val != null) {
    131       if (val instanceof Boolean || val instanceof Byte || val instanceof Short
    132           || val instanceof Integer) {
    133         str = val.toString();
    134       }
    135       // catch all special values
    136       else if (val instanceof Double) {
    137         str = canonicalizeFloatingPoint(val.toString(), "");
    138       } else if (val instanceof Float) {
    139         str = canonicalizeFloatingPoint(val.toString(), "f");
    140       } else if (val instanceof Long) {
    141         str = val.toString() + "L";
    142       } else if (val instanceof Character) {
    143         str = String.format("\'\\u%04x\'", val);
    144         System.out.println("str=" + str);
    145       } else if (val instanceof String) {
    146         str = "\"" + javaEscapeString((String) val) + "\"";
    147       } else {
    148         str = "<<<<" + val.toString() + ">>>>";
    149       }
    150     }
    151     if (str == null) {
    152       str = "null";
    153     }
    154     return str;
    155   }
    156 
    157   /**
    158    * Returns a canonical string representation of a floating point
    159    * number. The representation is suitable for use as Java source
    160    * code. This method also addresses bug #4428022 in the Sun JDK.
    161    */
    162   private static String canonicalizeFloatingPoint(String val, String suffix) {
    163     if (val.equals("Infinity")) {
    164       return "(1.0" + suffix + "/0.0" + suffix + ")";
    165     } else if (val.equals("-Infinity")) {
    166       return "(-1.0" + suffix + "/0.0" + suffix + ")";
    167     } else if (val.equals("NaN")) {
    168       return "(0.0" + suffix + "/0.0" + suffix + ")";
    169     }
    170 
    171     String str = val.toString();
    172     if (str.indexOf('E') != -1) {
    173       return str + suffix;
    174     }
    175 
    176     // 1.0 is the only case where a trailing "0" is allowed.
    177     // 1.00 is canonicalized as 1.0.
    178     int i = str.length() - 1;
    179     int d = str.indexOf('.');
    180     while (i >= d + 2 && str.charAt(i) == '0') {
    181       str = str.substring(0, i--);
    182     }
    183     return str + suffix;
    184   }
    185 
    186   public static String javaEscapeString(String str) {
    187     String result = "";
    188     final int N = str.length();
    189     for (int i = 0; i < N; i++) {
    190       char c = str.charAt(i);
    191       if (c == '\\') {
    192         result += "\\\\";
    193       } else if (c == '\t') {
    194         result += "\\t";
    195       } else if (c == '\b') {
    196         result += "\\b";
    197       } else if (c == '\r') {
    198         result += "\\r";
    199       } else if (c == '\n') {
    200         result += "\\n";
    201       } else if (c == '\f') {
    202         result += "\\f";
    203       } else if (c == '\'') {
    204         result += "\\'";
    205       } else if (c == '\"') {
    206         result += "\\\"";
    207       } else if (c >= ' ' && c <= '~') {
    208         result += c;
    209       } else {
    210         result += String.format("\\u%04x", new Integer((int) c));
    211       }
    212     }
    213     return result;
    214   }
    215 
    216   public static String javaUnescapeString(String str) throws ApiParseException {
    217     final int N = str.length();
    218     check: {
    219       for (int i=0; i<N; i++) {
    220         final char c = str.charAt(i);
    221         if (c == '\\') {
    222           break check;
    223         }
    224       }
    225       return str;
    226     }
    227 
    228     final StringBuilder buf = new StringBuilder(str.length());
    229     char escaped = 0;
    230     final int START = 0;
    231     final int CHAR1 = 1;
    232     final int CHAR2 = 2;
    233     final int CHAR3 = 3;
    234     final int CHAR4 = 4;
    235     final int ESCAPE = 5;
    236     int state = START;
    237 
    238     for (int i=0; i<N; i++) {
    239       final char c = str.charAt(i);
    240       switch (state) {
    241         case START:
    242           if (c == '\\') {
    243             state = ESCAPE;
    244           } else {
    245             buf.append(c);
    246           }
    247           break;
    248         case ESCAPE:
    249           switch (c) {
    250             case '\\':
    251               buf.append('\\');
    252               state = START;
    253               break;
    254             case 't':
    255               buf.append('\t');
    256               state = START;
    257               break;
    258             case 'b':
    259               buf.append('\b');
    260               state = START;
    261               break;
    262             case 'r':
    263               buf.append('\r');
    264               state = START;
    265               break;
    266             case 'n':
    267               buf.append('\n');
    268               state = START;
    269               break;
    270             case 'f':
    271               buf.append('\f');
    272               state = START;
    273               break;
    274             case '\'':
    275               buf.append('\'');
    276               state = START;
    277               break;
    278             case '\"':
    279               buf.append('\"');
    280               state = START;
    281               break;
    282             case 'u':
    283               state = CHAR1;
    284               escaped = 0;
    285               break;
    286           }
    287           break;
    288         case CHAR1:
    289         case CHAR2:
    290         case CHAR3:
    291         case CHAR4:
    292           escaped <<= 4;
    293           if (c >= '0' && c <= '9') {
    294             escaped |= c - '0';
    295           } else if (c >= 'a' && c <= 'f') {
    296             escaped |= 10 + (c - 'a');
    297           } else if (c >= 'A' && c <= 'F') {
    298             escaped |= 10 + (c - 'A');
    299           } else {
    300             throw new ApiParseException("bad escape sequence: '" + c + "' at pos " + i + " in: \""
    301                 + str + "\"");
    302           }
    303           if (state == CHAR4) {
    304             buf.append(escaped);
    305             state = START;
    306           } else {
    307             state++;
    308           }
    309           break;
    310       }
    311     }
    312     if (state != START) {
    313       throw new ApiParseException("unfinished escape sequence: " + str);
    314     }
    315     return buf.toString();
    316   }
    317 
    318   public void makeHDF(Data data, String base) {
    319     data.setValue(base + ".kind", kind());
    320     type().makeHDF(data, base + ".type");
    321     data.setValue(base + ".name", name());
    322     data.setValue(base + ".href", htmlPage());
    323     data.setValue(base + ".anchor", anchor());
    324     TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
    325     TagInfo.makeHDF(data, base + ".descr", inlineTags());
    326     TagInfo.makeHDF(data, base + ".deprecated", comment().deprecatedTags());
    327     TagInfo.makeHDF(data, base + ".seeAlso", comment().seeTags());
    328     data.setValue(base + ".since", getSince());
    329     data.setValue(base + ".final", isFinal() ? "final" : "");
    330     data.setValue(base + ".static", isStatic() ? "static" : "");
    331     if (isPublic()) {
    332       data.setValue(base + ".scope", "public");
    333     } else if (isProtected()) {
    334       data.setValue(base + ".scope", "protected");
    335     } else if (isPackagePrivate()) {
    336       data.setValue(base + ".scope", "");
    337     } else if (isPrivate()) {
    338       data.setValue(base + ".scope", "private");
    339     }
    340     Object val = mConstantValue;
    341     if (val != null) {
    342       String dec = null;
    343       String hex = null;
    344       String str = null;
    345 
    346       if (val instanceof Boolean) {
    347         str = ((Boolean) val).toString();
    348       } else if (val instanceof Byte) {
    349         dec = String.format("%d", val);
    350         hex = String.format("0x%02x", val);
    351       } else if (val instanceof Character) {
    352         dec = String.format("\'%c\'", val);
    353         hex = String.format("0x%04x", val);
    354       } else if (val instanceof Double) {
    355         str = ((Double) val).toString();
    356       } else if (val instanceof Float) {
    357         str = ((Float) val).toString();
    358       } else if (val instanceof Integer) {
    359         dec = String.format("%d", val);
    360         hex = String.format("0x%08x", val);
    361       } else if (val instanceof Long) {
    362         dec = String.format("%d", val);
    363         hex = String.format("0x%016x", val);
    364       } else if (val instanceof Short) {
    365         dec = String.format("%d", val);
    366         hex = String.format("0x%04x", val);
    367       } else if (val instanceof String) {
    368         str = "\"" + ((String) val) + "\"";
    369       } else {
    370         str = "";
    371       }
    372 
    373       if (dec != null && hex != null) {
    374         data.setValue(base + ".constantValue.dec", Doclava.escape(dec));
    375         data.setValue(base + ".constantValue.hex", Doclava.escape(hex));
    376       } else {
    377         data.setValue(base + ".constantValue.str", Doclava.escape(str));
    378         data.setValue(base + ".constantValue.isString", "1");
    379       }
    380     }
    381 
    382     setFederatedReferences(data, base);
    383   }
    384 
    385   @Override
    386   public boolean isExecutable() {
    387     return false;
    388   }
    389 
    390   public boolean isTransient() {
    391     return mIsTransient;
    392   }
    393 
    394   public boolean isVolatile() {
    395     return mIsVolatile;
    396   }
    397 
    398   // Check the declared value with a typed comparison, not a string comparison,
    399   // to accommodate toolchains with different fp -> string conversions.
    400   private boolean valueEquals(FieldInfo other) {
    401     if ((mConstantValue == null) != (other.mConstantValue == null)) {
    402       return false;
    403     }
    404 
    405     // Null values are considered equal
    406     if (mConstantValue == null) {
    407       return true;
    408     }
    409 
    410     return mType.equals(other.mType)
    411         && mConstantValue.equals(other.mConstantValue);
    412   }
    413 
    414   public boolean isConsistent(FieldInfo fInfo) {
    415     boolean consistent = true;
    416     if (!mType.equals(fInfo.mType)) {
    417       Errors.error(Errors.CHANGED_TYPE, fInfo.position(), "Field " + fInfo.qualifiedName()
    418           + " has changed type");
    419       consistent = false;
    420     } else if (!this.valueEquals(fInfo)) {
    421       Errors.error(Errors.CHANGED_VALUE, fInfo.position(), "Field " + fInfo.qualifiedName()
    422           + " has changed value from " + mConstantValue + " to " + fInfo.mConstantValue);
    423       consistent = false;
    424     }
    425 
    426     if (!scope().equals(fInfo.scope())) {
    427       Errors.error(Errors.CHANGED_SCOPE, fInfo.position(), "Method " + fInfo.qualifiedName()
    428           + " changed scope from " + this.scope() + " to " + fInfo.scope());
    429       consistent = false;
    430     }
    431 
    432     if (mIsStatic != fInfo.mIsStatic) {
    433       Errors.error(Errors.CHANGED_STATIC, fInfo.position(), "Field " + fInfo.qualifiedName()
    434           + " has changed 'static' qualifier");
    435       consistent = false;
    436     }
    437 
    438     if (mIsFinal != fInfo.mIsFinal) {
    439       Errors.error(Errors.CHANGED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName()
    440           + " has changed 'final' qualifier");
    441       consistent = false;
    442     }
    443 
    444     if (mIsTransient != fInfo.mIsTransient) {
    445       Errors.error(Errors.CHANGED_TRANSIENT, fInfo.position(), "Field " + fInfo.qualifiedName()
    446           + " has changed 'transient' qualifier");
    447       consistent = false;
    448     }
    449 
    450     if (mIsVolatile != fInfo.mIsVolatile) {
    451       Errors.error(Errors.CHANGED_VOLATILE, fInfo.position(), "Field " + fInfo.qualifiedName()
    452           + " has changed 'volatile' qualifier");
    453       consistent = false;
    454     }
    455 
    456     if (isDeprecated() != fInfo.isDeprecated()) {
    457       Errors.error(Errors.CHANGED_DEPRECATED, fInfo.position(), "Field " + fInfo.qualifiedName()
    458           + " has changed deprecation state");
    459       consistent = false;
    460     }
    461 
    462     return consistent;
    463   }
    464 
    465   public boolean hasValue() {
    466       return mHasValue;
    467   }
    468 
    469   public void setHasValue(boolean hasValue) {
    470       mHasValue = hasValue;
    471   }
    472 
    473   boolean mIsTransient;
    474   boolean mIsVolatile;
    475   boolean mDeprecatedKnown;
    476   boolean mIsDeprecated;
    477   boolean mHasValue;
    478   TypeInfo mType;
    479   Object mConstantValue;
    480 }
    481