FieldInfo.java revision 62aaca829e5b443a3c1345c54c44293587929dd4
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.clearsilver.jsilver.data.Data;
20import java.util.Comparator;
21
22public class FieldInfo extends MemberInfo {
23  public static final Comparator<FieldInfo> comparator = new Comparator<FieldInfo>() {
24    public int compare(FieldInfo a, FieldInfo b) {
25      return a.name().compareTo(b.name());
26    }
27  };
28
29  public FieldInfo(String name, ClassInfo containingClass, ClassInfo realContainingClass,
30      boolean isPublic, boolean isProtected, boolean isPackagePrivate, boolean isPrivate,
31      boolean isFinal, boolean isStatic, boolean isTransient, boolean isVolatile,
32      boolean isSynthetic, TypeInfo type, String rawCommentText, Object constantValue,
33      SourcePositionInfo position, AnnotationInstanceInfo[] annotations) {
34    super(rawCommentText, name, null, containingClass, realContainingClass, isPublic, isProtected,
35          isPackagePrivate, isPrivate, isFinal, isStatic, isSynthetic, chooseKind(isFinal, isStatic, constantValue),
36        position, annotations);
37    mIsTransient = isTransient;
38    mIsVolatile = isVolatile;
39    mType = type;
40    mConstantValue = constantValue;
41  }
42
43  public FieldInfo cloneForClass(ClassInfo newContainingClass) {
44    return new FieldInfo(name(), newContainingClass, realContainingClass(), isPublic(),
45        isProtected(), isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isTransient(),
46        isVolatile(), isSynthetic(), mType, getRawCommentText(), mConstantValue, position(),
47        annotations());
48  }
49
50  static String chooseKind(boolean isFinal, boolean isStatic, Object constantValue)
51  {
52    return isConstant(isFinal, isStatic, constantValue) ? "constant" : "field";
53  }
54
55  public String qualifiedName() {
56    String parentQName
57        = (containingClass() != null) ? (containingClass().qualifiedName() + ".") : "";
58    return parentQName + name();
59  }
60
61  public TypeInfo type() {
62    return mType;
63  }
64
65  static boolean isConstant(boolean isFinal, boolean isStatic, Object constantValue)
66  {
67    /*
68     * Note: There is an ambiguity in the doc API that prevents us
69     * from distinguishing a constant-null from the lack of a
70     * constant at all. We err on the side of treating all null
71     * constantValues as meaning that the field is not a constant,
72     * since having a static final field assigned to null is both
73     * unusual and generally pretty useless.
74     */
75    return isFinal && isStatic && (constantValue != null);
76  }
77
78  public boolean isConstant() {
79    return isConstant(isFinal(), isStatic(), mConstantValue);
80  }
81
82  public TagInfo[] firstSentenceTags() {
83    return comment().briefTags();
84  }
85
86  public TagInfo[] inlineTags() {
87    return comment().tags();
88  }
89
90  public Object constantValue() {
91    return mConstantValue;
92  }
93
94  public String constantLiteralValue() {
95    return constantLiteralValue(mConstantValue);
96  }
97
98  public void setDeprecated(boolean deprecated) {
99    mDeprecatedKnown = true;
100    mIsDeprecated = deprecated;
101  }
102
103  public boolean isDeprecated() {
104    if (!mDeprecatedKnown) {
105      boolean commentDeprecated = comment().isDeprecated();
106      boolean annotationDeprecated = false;
107      for (AnnotationInstanceInfo annotation : annotations()) {
108        if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) {
109          annotationDeprecated = true;
110          break;
111        }
112      }
113
114      if (commentDeprecated != annotationDeprecated) {
115        Errors.error(Errors.DEPRECATION_MISMATCH, position(), "Field "
116            + mContainingClass.qualifiedName() + "." + name()
117            + ": @Deprecated annotation and @deprecated comment do not match");
118      }
119
120      mIsDeprecated = commentDeprecated | annotationDeprecated;
121      mDeprecatedKnown = true;
122    }
123    return mIsDeprecated;
124  }
125
126  public static String constantLiteralValue(Object val) {
127    String str = null;
128    if (val != null) {
129      if (val instanceof Boolean || val instanceof Byte || val instanceof Short
130          || val instanceof Integer) {
131        str = val.toString();
132      }
133      // catch all special values
134      else if (val instanceof Double) {
135        str = canonicalizeFloatingPoint(val.toString(), "");
136      } else if (val instanceof Float) {
137        str = canonicalizeFloatingPoint(val.toString(), "f");
138      } else if (val instanceof Long) {
139        str = val.toString() + "L";
140      } else if (val instanceof Character) {
141        str = String.format("\'\\u%04x\'", val);
142      } else if (val instanceof String) {
143        str = "\"" + javaEscapeString((String) val) + "\"";
144      } else {
145        str = "<<<<" + val.toString() + ">>>>";
146      }
147    }
148    if (str == null) {
149      str = "null";
150    }
151    return str;
152  }
153
154  /**
155   * Returns a canonical string representation of a floating point
156   * number. The representation is suitable for use as Java source
157   * code. This method also addresses bug #4428022 in the Sun JDK.
158   */
159  private static String canonicalizeFloatingPoint(String val, String suffix) {
160    if (val.equals("Infinity")) {
161      return "(1.0" + suffix + " / 0.0" + suffix + ")";
162    } else if (val.equals("-Infinity")) {
163      return "(-1.0" + suffix + " / 0.0" + suffix + ")";
164    } else if (val.equals("NaN")) {
165      return "(0.0" + suffix + " / 0.0" + suffix + ")";
166    }
167
168    String str = val.toString();
169    if (str.indexOf('E') != -1) {
170      return str + suffix;
171    }
172
173    // 1.0 is the only case where a trailing "0" is allowed.
174    // 1.00 is canonicalized as 1.0.
175    int i = str.length() - 1;
176    int d = str.indexOf('.');
177    while (i >= d + 2 && str.charAt(i) == '0') {
178      str = str.substring(0, i--);
179    }
180    return str + suffix;
181  }
182
183  public static String javaEscapeString(String str) {
184    String result = "";
185    final int N = str.length();
186    for (int i = 0; i < N; i++) {
187      char c = str.charAt(i);
188      if (c == '\\') {
189        result += "\\\\";
190      } else if (c == '\t') {
191        result += "\\t";
192      } else if (c == '\b') {
193        result += "\\b";
194      } else if (c == '\r') {
195        result += "\\r";
196      } else if (c == '\n') {
197        result += "\\n";
198      } else if (c == '\f') {
199        result += "\\f";
200      } else if (c == '\'') {
201        result += "\\'";
202      } else if (c == '\"') {
203        result += "\\\"";
204      } else if (c >= ' ' && c <= '~') {
205        result += c;
206      } else {
207        result += String.format("\\u%04x", new Integer((int) c));
208      }
209    }
210    return result;
211  }
212
213
214  public void makeHDF(Data data, String base) {
215    data.setValue(base + ".kind", kind());
216    type().makeHDF(data, base + ".type");
217    data.setValue(base + ".name", name());
218    data.setValue(base + ".href", htmlPage());
219    data.setValue(base + ".anchor", anchor());
220    TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
221    TagInfo.makeHDF(data, base + ".descr", inlineTags());
222    TagInfo.makeHDF(data, base + ".deprecated", comment().deprecatedTags());
223    TagInfo.makeHDF(data, base + ".seeAlso", comment().seeTags());
224    data.setValue(base + ".since", getSince());
225    data.setValue(base + ".final", isFinal() ? "final" : "");
226    data.setValue(base + ".static", isStatic() ? "static" : "");
227    if (isPublic()) {
228      data.setValue(base + ".scope", "public");
229    } else if (isProtected()) {
230      data.setValue(base + ".scope", "protected");
231    } else if (isPackagePrivate()) {
232      data.setValue(base + ".scope", "");
233    } else if (isPrivate()) {
234      data.setValue(base + ".scope", "private");
235    }
236    Object val = mConstantValue;
237    if (val != null) {
238      String dec = null;
239      String hex = null;
240      String str = null;
241
242      if (val instanceof Boolean) {
243        str = ((Boolean) val).toString();
244      } else if (val instanceof Byte) {
245        dec = String.format("%d", val);
246        hex = String.format("0x%02x", val);
247      } else if (val instanceof Character) {
248        dec = String.format("\'%c\'", val);
249        hex = String.format("0x%04x", val);
250      } else if (val instanceof Double) {
251        str = ((Double) val).toString();
252      } else if (val instanceof Float) {
253        str = ((Float) val).toString();
254      } else if (val instanceof Integer) {
255        dec = String.format("%d", val);
256        hex = String.format("0x%08x", val);
257      } else if (val instanceof Long) {
258        dec = String.format("%d", val);
259        hex = String.format("0x%016x", val);
260      } else if (val instanceof Short) {
261        dec = String.format("%d", val);
262        hex = String.format("0x%04x", val);
263      } else if (val instanceof String) {
264        str = "\"" + ((String) val) + "\"";
265      } else {
266        str = "";
267      }
268
269      if (dec != null && hex != null) {
270        data.setValue(base + ".constantValue.dec", Doclava.escape(dec));
271        data.setValue(base + ".constantValue.hex", Doclava.escape(hex));
272      } else {
273        data.setValue(base + ".constantValue.str", Doclava.escape(str));
274        data.setValue(base + ".constantValue.isString", "1");
275      }
276    }
277
278    setFederatedReferences(data, base);
279  }
280
281  @Override
282  public boolean isExecutable() {
283    return false;
284  }
285
286  public boolean isTransient() {
287    return mIsTransient;
288  }
289
290  public boolean isVolatile() {
291    return mIsVolatile;
292  }
293
294  // Check the declared value with a typed comparison, not a string comparison,
295  // to accommodate toolchains with different fp -> string conversions.
296  private boolean valueEquals(FieldInfo other) {
297    if ((mConstantValue == null) != (other.mConstantValue == null)) {
298      return false;
299    }
300
301    // Null values are considered equal
302    if (mConstantValue == null) {
303      return true;
304    }
305
306    // TODO: This method is called through from an XML comparison only right now,
307    // and mConstantValue is always a String. Get rid of this assertion.
308    if (!(mConstantValue instanceof String && other.mConstantValue instanceof String)) {
309      throw new AssertionError("Bad type for field value");
310    }
311
312    return mType.equals(other.mType)
313        && mConstantValue.equals(other.mConstantValue);
314  }
315
316  public boolean isConsistent(FieldInfo fInfo) {
317    boolean consistent = true;
318    if (!mType.equals(fInfo.mType)) {
319      Errors.error(Errors.CHANGED_TYPE, fInfo.position(), "Field " + fInfo.qualifiedName()
320          + " has changed type");
321      consistent = false;
322    } else if (!this.valueEquals(fInfo)) {
323      Errors.error(Errors.CHANGED_VALUE, fInfo.position(), "Field " + fInfo.qualifiedName()
324          + " has changed value from " + mConstantValue + " to " + fInfo.mConstantValue);
325      consistent = false;
326    }
327
328    if (!scope().equals(fInfo.scope())) {
329      Errors.error(Errors.CHANGED_SCOPE, fInfo.position(), "Method " + fInfo.qualifiedName()
330          + " changed scope from " + this.scope() + " to " + fInfo.scope());
331      consistent = false;
332    }
333
334    if (mIsStatic != fInfo.mIsStatic) {
335      Errors.error(Errors.CHANGED_STATIC, fInfo.position(), "Field " + fInfo.qualifiedName()
336          + " has changed 'static' qualifier");
337      consistent = false;
338    }
339
340    if (mIsFinal != fInfo.mIsFinal) {
341      Errors.error(Errors.CHANGED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName()
342          + " has changed 'final' qualifier");
343      consistent = false;
344    }
345
346    if (mIsTransient != fInfo.mIsTransient) {
347      Errors.error(Errors.CHANGED_TRANSIENT, fInfo.position(), "Field " + fInfo.qualifiedName()
348          + " has changed 'transient' qualifier");
349      consistent = false;
350    }
351
352    if (mIsVolatile != fInfo.mIsVolatile) {
353      Errors.error(Errors.CHANGED_VOLATILE, fInfo.position(), "Field " + fInfo.qualifiedName()
354          + " has changed 'volatile' qualifier");
355      consistent = false;
356    }
357
358    if (isDeprecated() != fInfo.isDeprecated()) {
359      Errors.error(Errors.CHANGED_DEPRECATED, fInfo.position(), "Field " + fInfo.qualifiedName()
360          + " has changed deprecation state");
361      consistent = false;
362    }
363
364    return consistent;
365  }
366
367  boolean mIsTransient;
368  boolean mIsVolatile;
369  boolean mDeprecatedKnown;
370  boolean mIsDeprecated;
371  TypeInfo mType;
372  Object mConstantValue;
373}
374