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