• 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", new Integer((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     if (isDeprecated()) {
380       data.setValue(base + ".deprecatedsince", getDeprecatedSince());
381     }
382     data.setValue(base + ".final", isFinal() ? "final" : "");
383     data.setValue(base + ".static", isStatic() ? "static" : "");
384     if (isPublic()) {
385       data.setValue(base + ".scope", "public");
386     } else if (isProtected()) {
387       data.setValue(base + ".scope", "protected");
388     } else if (isPackagePrivate()) {
389       data.setValue(base + ".scope", "");
390     } else if (isPrivate()) {
391       data.setValue(base + ".scope", "private");
392     }
393     Object val = mConstantValue;
394     if (val != null) {
395       String dec = null;
396       String hex = null;
397       String str = null;
398 
399       if (val instanceof Boolean) {
400         str = ((Boolean) val).toString();
401       } else if (val instanceof Byte) {
402         dec = String.format("%d", val);
403         hex = String.format("0x%02x", val);
404       } else if (val instanceof Character) {
405         dec = String.format("\'%c\'", val);
406         hex = String.format("0x%04x", val);
407       } else if (val instanceof Double) {
408         str = ((Double) val).toString();
409       } else if (val instanceof Float) {
410         str = ((Float) val).toString();
411       } else if (val instanceof Integer) {
412         dec = String.format("%d", val);
413         hex = String.format("0x%08x", val);
414       } else if (val instanceof Long) {
415         dec = String.format("%d", val);
416         hex = String.format("0x%016x", val);
417       } else if (val instanceof Short) {
418         dec = String.format("%d", val);
419         hex = String.format("0x%04x", val);
420       } else if (val instanceof String) {
421         str = "\"" + ((String) val) + "\"";
422       } else {
423         str = "";
424       }
425 
426       if (dec != null && hex != null) {
427         data.setValue(base + ".constantValue.dec", Doclava.escape(dec));
428         data.setValue(base + ".constantValue.hex", Doclava.escape(hex));
429       } else {
430         data.setValue(base + ".constantValue.str", Doclava.escape(str));
431         data.setValue(base + ".constantValue.isString", "1");
432       }
433     }
434 
435     AnnotationInstanceInfo.makeLinkListHDF(
436       data,
437       base + ".showAnnotations",
438       showAnnotations().toArray(new AnnotationInstanceInfo[showAnnotations().size()]));
439 
440     setFederatedReferences(data, base);
441 
442     Doclava.linter.lintField(this);
443   }
444 
445   @Override
isExecutable()446   public boolean isExecutable() {
447     return false;
448   }
449 
isTransient()450   public boolean isTransient() {
451     return mIsTransient;
452   }
453 
isVolatile()454   public boolean isVolatile() {
455     return mIsVolatile;
456   }
457 
458   // Check the declared value with a typed comparison, not a string comparison,
459   // to accommodate toolchains with different fp -> string conversions.
valueEquals(FieldInfo other)460   private boolean valueEquals(FieldInfo other) {
461     if ((mConstantValue == null) != (other.mConstantValue == null)) {
462       return false;
463     }
464 
465     // Null values are considered equal
466     if (mConstantValue == null) {
467       return true;
468     }
469 
470     return mType.equals(other.mType)
471         && mConstantValue.equals(other.mConstantValue);
472   }
473 
isConsistent(FieldInfo fInfo)474   public boolean isConsistent(FieldInfo fInfo) {
475     boolean consistent = true;
476     if (!mType.equals(fInfo.mType)) {
477       Errors.error(Errors.CHANGED_TYPE, fInfo.position(), "Field " + fInfo.qualifiedName()
478           + " has changed type from " + mType + " to " + fInfo.mType);
479       consistent = false;
480     } else if (!this.valueEquals(fInfo)) {
481       Errors.error(Errors.CHANGED_VALUE, fInfo.position(), "Field " + fInfo.qualifiedName()
482           + " has changed value from " + mConstantValue + " to " + fInfo.mConstantValue);
483       consistent = false;
484     }
485 
486     if (!scope().equals(fInfo.scope())) {
487       Errors.error(Errors.CHANGED_SCOPE, fInfo.position(), "Method " + fInfo.qualifiedName()
488           + " changed scope from " + this.scope() + " to " + fInfo.scope());
489       consistent = false;
490     }
491 
492     if (mIsStatic != fInfo.mIsStatic) {
493       Errors.error(Errors.CHANGED_STATIC, fInfo.position(), "Field " + fInfo.qualifiedName()
494           + " has changed 'static' qualifier");
495       consistent = false;
496     }
497 
498     if (!mIsFinal && fInfo.mIsFinal) {
499       Errors.error(Errors.ADDED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName()
500           + " has added 'final' qualifier");
501       consistent = false;
502     } else if (mIsFinal && !fInfo.mIsFinal) {
503       Errors.error(Errors.REMOVED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName()
504           + " has removed 'final' qualifier");
505       consistent = false;
506     }
507 
508     if (mIsTransient != fInfo.mIsTransient) {
509       Errors.error(Errors.CHANGED_TRANSIENT, fInfo.position(), "Field " + fInfo.qualifiedName()
510           + " has changed 'transient' qualifier");
511       consistent = false;
512     }
513 
514     if (mIsVolatile != fInfo.mIsVolatile) {
515       Errors.error(Errors.CHANGED_VOLATILE, fInfo.position(), "Field " + fInfo.qualifiedName()
516           + " has changed 'volatile' qualifier");
517       consistent = false;
518     }
519 
520     if (isDeprecated() != fInfo.isDeprecated()) {
521       Errors.error(Errors.CHANGED_DEPRECATED, fInfo.position(), "Field " + fInfo.qualifiedName()
522           + " has changed deprecation state " + isDeprecated() + " --> " + fInfo.isDeprecated());
523       consistent = false;
524     }
525 
526     return consistent;
527   }
528 
hasValue()529   public boolean hasValue() {
530       return mHasValue;
531   }
532 
setHasValue(boolean hasValue)533   public void setHasValue(boolean hasValue) {
534       mHasValue = hasValue;
535   }
536 
537   boolean mIsTransient;
538   boolean mIsVolatile;
539   boolean mDeprecatedKnown;
540   boolean mIsDeprecated;
541   boolean mHasValue;
542   TypeInfo mType;
543   Object mConstantValue;
544 }
545