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