FieldInfo.java revision d894e7de44cda4d042358f10681d2d170c021ec4
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
17package com.google.doclava;
18
19import com.google.doclava.apicheck.ApiParseException;
20import com.google.clearsilver.jsilver.data.Data;
21import java.util.Comparator;
22import java.util.ArrayList;
23
24public 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      // Check to see that the JavaDoc contains @deprecated AND the method is marked as @Deprecated.
117      // Otherwise, warn.
118      // Note: We only do this for "included" classes (i.e. those we have source code for); we do
119      // not have comments for classes from .class files but we do know whether a field is marked
120      // as @Deprecated.
121      if (mContainingClass.isIncluded() && commentDeprecated != annotationDeprecated) {
122        Errors.error(Errors.DEPRECATION_MISMATCH, position(), "Field "
123            + mContainingClass.qualifiedName() + "." + name()
124            + ": @Deprecated annotation (" + (annotationDeprecated ? "" : "not ")
125            + "present) and @deprecated doc tag (" + (commentDeprecated ? "" : "not ")
126            + "present) do not match");
127      }
128
129      mIsDeprecated = commentDeprecated | annotationDeprecated;
130      mDeprecatedKnown = true;
131    }
132    return mIsDeprecated;
133  }
134
135  public static String constantLiteralValue(Object val) {
136    String str = null;
137    if (val != null) {
138      if (val instanceof Boolean || val instanceof Byte || val instanceof Short
139          || val instanceof Integer) {
140        str = val.toString();
141      }
142      // catch all special values
143      else if (val instanceof Double) {
144        str = canonicalizeFloatingPoint(val.toString(), "");
145      } else if (val instanceof Float) {
146        str = canonicalizeFloatingPoint(val.toString(), "f");
147      } else if (val instanceof Long) {
148        str = val.toString() + "L";
149      } else if (val instanceof Character) {
150        str = String.format("\'\\u%04x\'", val);
151        System.out.println("str=" + str);
152      } else if (val instanceof String) {
153        str = "\"" + javaEscapeString((String) val) + "\"";
154      } else {
155        str = "<<<<" + val.toString() + ">>>>";
156      }
157    }
158    if (str == null) {
159      str = "null";
160    }
161    return str;
162  }
163
164  /**
165   * Returns a canonical string representation of a floating point
166   * number. The representation is suitable for use as Java source
167   * code. This method also addresses bug #4428022 in the Sun JDK.
168   */
169  private static String canonicalizeFloatingPoint(String val, String suffix) {
170    if (val.equals("Infinity")) {
171      return "(1.0" + suffix + "/0.0" + suffix + ")";
172    } else if (val.equals("-Infinity")) {
173      return "(-1.0" + suffix + "/0.0" + suffix + ")";
174    } else if (val.equals("NaN")) {
175      return "(0.0" + suffix + "/0.0" + suffix + ")";
176    }
177
178    String str = val.toString();
179    if (str.indexOf('E') != -1) {
180      return str + suffix;
181    }
182
183    // 1.0 is the only case where a trailing "0" is allowed.
184    // 1.00 is canonicalized as 1.0.
185    int i = str.length() - 1;
186    int d = str.indexOf('.');
187    while (i >= d + 2 && str.charAt(i) == '0') {
188      str = str.substring(0, i--);
189    }
190    return str + suffix;
191  }
192
193  public static String javaEscapeString(String str) {
194    String result = "";
195    final int N = str.length();
196    for (int i = 0; i < N; i++) {
197      char c = str.charAt(i);
198      if (c == '\\') {
199        result += "\\\\";
200      } else if (c == '\t') {
201        result += "\\t";
202      } else if (c == '\b') {
203        result += "\\b";
204      } else if (c == '\r') {
205        result += "\\r";
206      } else if (c == '\n') {
207        result += "\\n";
208      } else if (c == '\f') {
209        result += "\\f";
210      } else if (c == '\'') {
211        result += "\\'";
212      } else if (c == '\"') {
213        result += "\\\"";
214      } else if (c >= ' ' && c <= '~') {
215        result += c;
216      } else {
217        result += String.format("\\u%04x", new Integer((int) c));
218      }
219    }
220    return result;
221  }
222
223  public static String javaUnescapeString(String str) throws ApiParseException {
224    final int N = str.length();
225    check: {
226      for (int i=0; i<N; i++) {
227        final char c = str.charAt(i);
228        if (c == '\\') {
229          break check;
230        }
231      }
232      return str;
233    }
234
235    final StringBuilder buf = new StringBuilder(str.length());
236    char escaped = 0;
237    final int START = 0;
238    final int CHAR1 = 1;
239    final int CHAR2 = 2;
240    final int CHAR3 = 3;
241    final int CHAR4 = 4;
242    final int ESCAPE = 5;
243    int state = START;
244
245    for (int i=0; i<N; i++) {
246      final char c = str.charAt(i);
247      switch (state) {
248        case START:
249          if (c == '\\') {
250            state = ESCAPE;
251          } else {
252            buf.append(c);
253          }
254          break;
255        case ESCAPE:
256          switch (c) {
257            case '\\':
258              buf.append('\\');
259              state = START;
260              break;
261            case 't':
262              buf.append('\t');
263              state = START;
264              break;
265            case 'b':
266              buf.append('\b');
267              state = START;
268              break;
269            case 'r':
270              buf.append('\r');
271              state = START;
272              break;
273            case 'n':
274              buf.append('\n');
275              state = START;
276              break;
277            case 'f':
278              buf.append('\f');
279              state = START;
280              break;
281            case '\'':
282              buf.append('\'');
283              state = START;
284              break;
285            case '\"':
286              buf.append('\"');
287              state = START;
288              break;
289            case 'u':
290              state = CHAR1;
291              escaped = 0;
292              break;
293          }
294          break;
295        case CHAR1:
296        case CHAR2:
297        case CHAR3:
298        case CHAR4:
299          escaped <<= 4;
300          if (c >= '0' && c <= '9') {
301            escaped |= c - '0';
302          } else if (c >= 'a' && c <= 'f') {
303            escaped |= 10 + (c - 'a');
304          } else if (c >= 'A' && c <= 'F') {
305            escaped |= 10 + (c - 'A');
306          } else {
307            throw new ApiParseException("bad escape sequence: '" + c + "' at pos " + i + " in: \""
308                + str + "\"");
309          }
310          if (state == CHAR4) {
311            buf.append(escaped);
312            state = START;
313          } else {
314            state++;
315          }
316          break;
317      }
318    }
319    if (state != START) {
320      throw new ApiParseException("unfinished escape sequence: " + str);
321    }
322    return buf.toString();
323  }
324
325  public void makeHDF(Data data, String base) {
326    data.setValue(base + ".kind", kind());
327    type().makeHDF(data, base + ".type");
328    data.setValue(base + ".name", name());
329    data.setValue(base + ".href", htmlPage());
330    data.setValue(base + ".anchor", anchor());
331    TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
332    TagInfo.makeHDF(data, base + ".descr", inlineTags());
333    TagInfo.makeHDF(data, base + ".deprecated", comment().deprecatedTags());
334    TagInfo.makeHDF(data, base + ".seeAlso", comment().seeTags());
335    data.setValue(base + ".since", getSince());
336    if (isDeprecated()) {
337      data.setValue(base + ".deprecatedsince", getDeprecatedSince());
338    }
339    data.setValue(base + ".final", isFinal() ? "final" : "");
340    data.setValue(base + ".static", isStatic() ? "static" : "");
341    if (isPublic()) {
342      data.setValue(base + ".scope", "public");
343    } else if (isProtected()) {
344      data.setValue(base + ".scope", "protected");
345    } else if (isPackagePrivate()) {
346      data.setValue(base + ".scope", "");
347    } else if (isPrivate()) {
348      data.setValue(base + ".scope", "private");
349    }
350    Object val = mConstantValue;
351    if (val != null) {
352      String dec = null;
353      String hex = null;
354      String str = null;
355
356      if (val instanceof Boolean) {
357        str = ((Boolean) val).toString();
358      } else if (val instanceof Byte) {
359        dec = String.format("%d", val);
360        hex = String.format("0x%02x", val);
361      } else if (val instanceof Character) {
362        dec = String.format("\'%c\'", val);
363        hex = String.format("0x%04x", val);
364      } else if (val instanceof Double) {
365        str = ((Double) val).toString();
366      } else if (val instanceof Float) {
367        str = ((Float) val).toString();
368      } else if (val instanceof Integer) {
369        dec = String.format("%d", val);
370        hex = String.format("0x%08x", val);
371      } else if (val instanceof Long) {
372        dec = String.format("%d", val);
373        hex = String.format("0x%016x", val);
374      } else if (val instanceof Short) {
375        dec = String.format("%d", val);
376        hex = String.format("0x%04x", val);
377      } else if (val instanceof String) {
378        str = "\"" + ((String) val) + "\"";
379      } else {
380        str = "";
381      }
382
383      if (dec != null && hex != null) {
384        data.setValue(base + ".constantValue.dec", Doclava.escape(dec));
385        data.setValue(base + ".constantValue.hex", Doclava.escape(hex));
386      } else {
387        data.setValue(base + ".constantValue.str", Doclava.escape(str));
388        data.setValue(base + ".constantValue.isString", "1");
389      }
390    }
391
392    AnnotationInstanceInfo.makeLinkListHDF(
393      data,
394      base + ".showAnnotations",
395      showAnnotations().toArray(new AnnotationInstanceInfo[showAnnotations().size()]));
396
397    setFederatedReferences(data, base);
398  }
399
400  @Override
401  public boolean isExecutable() {
402    return false;
403  }
404
405  public boolean isTransient() {
406    return mIsTransient;
407  }
408
409  public boolean isVolatile() {
410    return mIsVolatile;
411  }
412
413  // Check the declared value with a typed comparison, not a string comparison,
414  // to accommodate toolchains with different fp -> string conversions.
415  private boolean valueEquals(FieldInfo other) {
416    if ((mConstantValue == null) != (other.mConstantValue == null)) {
417      return false;
418    }
419
420    // Null values are considered equal
421    if (mConstantValue == null) {
422      return true;
423    }
424
425    return mType.equals(other.mType)
426        && mConstantValue.equals(other.mConstantValue);
427  }
428
429  public boolean isConsistent(FieldInfo fInfo) {
430    boolean consistent = true;
431    if (!mType.equals(fInfo.mType)) {
432      Errors.error(Errors.CHANGED_TYPE, fInfo.position(), "Field " + fInfo.qualifiedName()
433          + " has changed type from " + mType + " to " + fInfo.mType);
434      consistent = false;
435    } else if (!this.valueEquals(fInfo)) {
436      Errors.error(Errors.CHANGED_VALUE, fInfo.position(), "Field " + fInfo.qualifiedName()
437          + " has changed value from " + mConstantValue + " to " + fInfo.mConstantValue);
438      consistent = false;
439    }
440
441    if (!scope().equals(fInfo.scope())) {
442      Errors.error(Errors.CHANGED_SCOPE, fInfo.position(), "Method " + fInfo.qualifiedName()
443          + " changed scope from " + this.scope() + " to " + fInfo.scope());
444      consistent = false;
445    }
446
447    if (mIsStatic != fInfo.mIsStatic) {
448      Errors.error(Errors.CHANGED_STATIC, fInfo.position(), "Field " + fInfo.qualifiedName()
449          + " has changed 'static' qualifier");
450      consistent = false;
451    }
452
453    if (!mIsFinal && fInfo.mIsFinal) {
454      Errors.error(Errors.ADDED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName()
455          + " has added 'final' qualifier");
456      consistent = false;
457    } else if (mIsFinal && !fInfo.mIsFinal) {
458      Errors.error(Errors.REMOVED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName()
459          + " has removed 'final' qualifier");
460      consistent = false;
461    }
462
463    if (mIsTransient != fInfo.mIsTransient) {
464      Errors.error(Errors.CHANGED_TRANSIENT, fInfo.position(), "Field " + fInfo.qualifiedName()
465          + " has changed 'transient' qualifier");
466      consistent = false;
467    }
468
469    if (mIsVolatile != fInfo.mIsVolatile) {
470      Errors.error(Errors.CHANGED_VOLATILE, fInfo.position(), "Field " + fInfo.qualifiedName()
471          + " has changed 'volatile' qualifier");
472      consistent = false;
473    }
474
475    if (isDeprecated() != fInfo.isDeprecated()) {
476      Errors.error(Errors.CHANGED_DEPRECATED, fInfo.position(), "Field " + fInfo.qualifiedName()
477          + " has changed deprecation state " + isDeprecated() + " --> " + fInfo.isDeprecated());
478      consistent = false;
479    }
480
481    return consistent;
482  }
483
484  public boolean hasValue() {
485      return mHasValue;
486  }
487
488  public void setHasValue(boolean hasValue) {
489      mHasValue = hasValue;
490  }
491
492  boolean mIsTransient;
493  boolean mIsVolatile;
494  boolean mDeprecatedKnown;
495  boolean mIsDeprecated;
496  boolean mHasValue;
497  TypeInfo mType;
498  Object mConstantValue;
499}
500