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