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      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