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 
FieldInfo(String name, ClassInfo containingClass, ClassInfo realContainingClass, boolean isPublic, boolean isProtected, boolean isPackagePrivate, boolean isPrivate, boolean isFinal, boolean isStatic, boolean isTransient, boolean isVolatile, boolean isSynthetic, TypeInfo type, String rawCommentText, Object constantValue, SourcePositionInfo position, ArrayList<AnnotationInstanceInfo> annotations)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 
cloneForClass(ClassInfo newContainingClass)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 
chooseKind(boolean isFinal, boolean isStatic, Object constantValue)52   static String chooseKind(boolean isFinal, boolean isStatic, Object constantValue)
53   {
54     return isConstant(isFinal, isStatic, constantValue) ? "constant" : "field";
55   }
56 
qualifiedName()57   public String qualifiedName() {
58     String parentQName
59         = (containingClass() != null) ? (containingClass().qualifiedName() + ".") : "";
60     return parentQName + name();
61   }
62 
type()63   public TypeInfo type() {
64     return mType;
65   }
66 
isConstant(boolean isFinal, boolean isStatic, Object constantValue)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 
isConstant()80   public boolean isConstant() {
81     return isConstant(isFinal(), isStatic(), mConstantValue);
82   }
83 
firstSentenceTags()84   public TagInfo[] firstSentenceTags() {
85     return comment().briefTags();
86   }
87 
inlineTags()88   public TagInfo[] inlineTags() {
89     return comment().tags();
90   }
91 
constantValue()92   public Object constantValue() {
93     return mConstantValue;
94   }
95 
constantLiteralValue()96   public String constantLiteralValue() {
97     return constantLiteralValue(mConstantValue);
98   }
99 
setDeprecated(boolean deprecated)100   public void setDeprecated(boolean deprecated) {
101     mDeprecatedKnown = true;
102     mIsDeprecated = deprecated;
103   }
104 
isDeprecated()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 
constantLiteralValue(Object val)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    */
canonicalizeFloatingPoint(String val, String suffix)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 
javaEscapeString(String str)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 
javaUnescapeString(String str)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 
makeHDF(Data data, String base)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     if (isDeprecated()) {
330       data.setValue(base + ".deprecatedsince", getDeprecatedSince());
331     }
332     data.setValue(base + ".final", isFinal() ? "final" : "");
333     data.setValue(base + ".static", isStatic() ? "static" : "");
334     if (isPublic()) {
335       data.setValue(base + ".scope", "public");
336     } else if (isProtected()) {
337       data.setValue(base + ".scope", "protected");
338     } else if (isPackagePrivate()) {
339       data.setValue(base + ".scope", "");
340     } else if (isPrivate()) {
341       data.setValue(base + ".scope", "private");
342     }
343     Object val = mConstantValue;
344     if (val != null) {
345       String dec = null;
346       String hex = null;
347       String str = null;
348 
349       if (val instanceof Boolean) {
350         str = ((Boolean) val).toString();
351       } else if (val instanceof Byte) {
352         dec = String.format("%d", val);
353         hex = String.format("0x%02x", val);
354       } else if (val instanceof Character) {
355         dec = String.format("\'%c\'", val);
356         hex = String.format("0x%04x", val);
357       } else if (val instanceof Double) {
358         str = ((Double) val).toString();
359       } else if (val instanceof Float) {
360         str = ((Float) val).toString();
361       } else if (val instanceof Integer) {
362         dec = String.format("%d", val);
363         hex = String.format("0x%08x", val);
364       } else if (val instanceof Long) {
365         dec = String.format("%d", val);
366         hex = String.format("0x%016x", val);
367       } else if (val instanceof Short) {
368         dec = String.format("%d", val);
369         hex = String.format("0x%04x", val);
370       } else if (val instanceof String) {
371         str = "\"" + ((String) val) + "\"";
372       } else {
373         str = "";
374       }
375 
376       if (dec != null && hex != null) {
377         data.setValue(base + ".constantValue.dec", Doclava.escape(dec));
378         data.setValue(base + ".constantValue.hex", Doclava.escape(hex));
379       } else {
380         data.setValue(base + ".constantValue.str", Doclava.escape(str));
381         data.setValue(base + ".constantValue.isString", "1");
382       }
383     }
384 
385     AnnotationInstanceInfo.makeLinkListHDF(
386       data,
387       base + ".showAnnotations",
388       showAnnotations().toArray(new AnnotationInstanceInfo[showAnnotations().size()]));
389 
390     setFederatedReferences(data, base);
391   }
392 
393   @Override
isExecutable()394   public boolean isExecutable() {
395     return false;
396   }
397 
isTransient()398   public boolean isTransient() {
399     return mIsTransient;
400   }
401 
isVolatile()402   public boolean isVolatile() {
403     return mIsVolatile;
404   }
405 
406   // Check the declared value with a typed comparison, not a string comparison,
407   // to accommodate toolchains with different fp -> string conversions.
valueEquals(FieldInfo other)408   private boolean valueEquals(FieldInfo other) {
409     if ((mConstantValue == null) != (other.mConstantValue == null)) {
410       return false;
411     }
412 
413     // Null values are considered equal
414     if (mConstantValue == null) {
415       return true;
416     }
417 
418     return mType.equals(other.mType)
419         && mConstantValue.equals(other.mConstantValue);
420   }
421 
isConsistent(FieldInfo fInfo)422   public boolean isConsistent(FieldInfo fInfo) {
423     boolean consistent = true;
424     if (!mType.equals(fInfo.mType)) {
425       Errors.error(Errors.CHANGED_TYPE, fInfo.position(), "Field " + fInfo.qualifiedName()
426           + " has changed type from " + mType + " to " + fInfo.mType);
427       consistent = false;
428     } else if (!this.valueEquals(fInfo)) {
429       Errors.error(Errors.CHANGED_VALUE, fInfo.position(), "Field " + fInfo.qualifiedName()
430           + " has changed value from " + mConstantValue + " to " + fInfo.mConstantValue);
431       consistent = false;
432     }
433 
434     if (!scope().equals(fInfo.scope())) {
435       Errors.error(Errors.CHANGED_SCOPE, fInfo.position(), "Method " + fInfo.qualifiedName()
436           + " changed scope from " + this.scope() + " to " + fInfo.scope());
437       consistent = false;
438     }
439 
440     if (mIsStatic != fInfo.mIsStatic) {
441       Errors.error(Errors.CHANGED_STATIC, fInfo.position(), "Field " + fInfo.qualifiedName()
442           + " has changed 'static' qualifier");
443       consistent = false;
444     }
445 
446     if (!mIsFinal && fInfo.mIsFinal) {
447       Errors.error(Errors.ADDED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName()
448           + " has added 'final' qualifier");
449       consistent = false;
450     } else if (mIsFinal && !fInfo.mIsFinal) {
451       Errors.error(Errors.REMOVED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName()
452           + " has removed 'final' qualifier");
453       consistent = false;
454     }
455 
456     if (mIsTransient != fInfo.mIsTransient) {
457       Errors.error(Errors.CHANGED_TRANSIENT, fInfo.position(), "Field " + fInfo.qualifiedName()
458           + " has changed 'transient' qualifier");
459       consistent = false;
460     }
461 
462     if (mIsVolatile != fInfo.mIsVolatile) {
463       Errors.error(Errors.CHANGED_VOLATILE, fInfo.position(), "Field " + fInfo.qualifiedName()
464           + " has changed 'volatile' qualifier");
465       consistent = false;
466     }
467 
468     if (isDeprecated() != fInfo.isDeprecated()) {
469       Errors.error(Errors.CHANGED_DEPRECATED, fInfo.position(), "Field " + fInfo.qualifiedName()
470           + " has changed deprecation state " + isDeprecated() + " --> " + fInfo.isDeprecated());
471       consistent = false;
472     }
473 
474     return consistent;
475   }
476 
hasValue()477   public boolean hasValue() {
478       return mHasValue;
479   }
480 
setHasValue(boolean hasValue)481   public void setHasValue(boolean hasValue) {
482       mHasValue = hasValue;
483   }
484 
485   boolean mIsTransient;
486   boolean mIsVolatile;
487   boolean mDeprecatedKnown;
488   boolean mIsDeprecated;
489   boolean mHasValue;
490   TypeInfo mType;
491   Object mConstantValue;
492 }
493