blob: a34bfb8e6c8d89fddab22503285574fc30a7106f [file] [log] [blame]
Ben Dodson920dbbb2010-08-04 15:21:06 -07001/*
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.clearsilver.jsilver.data.Data;
20import java.util.Comparator;
21
22public class FieldInfo extends MemberInfo {
23 public static final Comparator<FieldInfo> comparator = new Comparator<FieldInfo>() {
24 public int compare(FieldInfo a, FieldInfo b) {
25 return a.name().compareTo(b.name());
26 }
27 };
28
29 public FieldInfo(String name, ClassInfo containingClass, ClassInfo realContainingClass,
30 boolean isPublic, boolean isProtected, boolean isPackagePrivate, boolean isPrivate,
31 boolean isFinal, boolean isStatic, boolean isTransient, boolean isVolatile,
32 boolean isSynthetic, TypeInfo type, String rawCommentText, Object constantValue,
33 SourcePositionInfo position, AnnotationInstanceInfo[] annotations) {
34 super(rawCommentText, name, null, containingClass, realContainingClass, isPublic, isProtected,
Dan Bornstein62aaca82010-11-29 13:35:43 -080035 isPackagePrivate, isPrivate, isFinal, isStatic, isSynthetic, chooseKind(isFinal, isStatic, constantValue),
Ben Dodson920dbbb2010-08-04 15:21:06 -070036 position, annotations);
37 mIsTransient = isTransient;
38 mIsVolatile = isVolatile;
39 mType = type;
40 mConstantValue = constantValue;
41 }
42
43 public FieldInfo cloneForClass(ClassInfo newContainingClass) {
44 return new FieldInfo(name(), newContainingClass, realContainingClass(), isPublic(),
45 isProtected(), isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isTransient(),
46 isVolatile(), isSynthetic(), mType, getRawCommentText(), mConstantValue, position(),
47 annotations());
48 }
49
Dan Bornstein62aaca82010-11-29 13:35:43 -080050 static String chooseKind(boolean isFinal, boolean isStatic, Object constantValue)
51 {
52 return isConstant(isFinal, isStatic, constantValue) ? "constant" : "field";
Ben Dodson920dbbb2010-08-04 15:21:06 -070053 }
54
55 public String qualifiedName() {
56 String parentQName
57 = (containingClass() != null) ? (containingClass().qualifiedName() + ".") : "";
58 return parentQName + name();
59 }
60
61 public TypeInfo type() {
62 return mType;
63 }
64
Dan Bornstein62aaca82010-11-29 13:35:43 -080065 static boolean isConstant(boolean isFinal, boolean isStatic, Object constantValue)
66 {
67 /*
68 * Note: There is an ambiguity in the doc API that prevents us
69 * from distinguishing a constant-null from the lack of a
70 * constant at all. We err on the side of treating all null
71 * constantValues as meaning that the field is not a constant,
72 * since having a static final field assigned to null is both
73 * unusual and generally pretty useless.
74 */
75 return isFinal && isStatic && (constantValue != null);
76 }
77
Ben Dodson920dbbb2010-08-04 15:21:06 -070078 public boolean isConstant() {
Dan Bornstein62aaca82010-11-29 13:35:43 -080079 return isConstant(isFinal(), isStatic(), mConstantValue);
Ben Dodson920dbbb2010-08-04 15:21:06 -070080 }
81
82 public TagInfo[] firstSentenceTags() {
83 return comment().briefTags();
84 }
85
86 public TagInfo[] inlineTags() {
87 return comment().tags();
88 }
89
90 public Object constantValue() {
91 return mConstantValue;
92 }
93
94 public String constantLiteralValue() {
95 return constantLiteralValue(mConstantValue);
96 }
97
98 public void setDeprecated(boolean deprecated) {
99 mDeprecatedKnown = true;
100 mIsDeprecated = deprecated;
101 }
102
103 public boolean isDeprecated() {
104 if (!mDeprecatedKnown) {
105 boolean commentDeprecated = comment().isDeprecated();
106 boolean annotationDeprecated = false;
107 for (AnnotationInstanceInfo annotation : annotations()) {
108 if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) {
109 annotationDeprecated = true;
110 break;
111 }
112 }
113
114 if (commentDeprecated != annotationDeprecated) {
115 Errors.error(Errors.DEPRECATION_MISMATCH, position(), "Field "
116 + mContainingClass.qualifiedName() + "." + name()
117 + ": @Deprecated annotation and @deprecated comment do not match");
118 }
119
120 mIsDeprecated = commentDeprecated | annotationDeprecated;
121 mDeprecatedKnown = true;
122 }
123 return mIsDeprecated;
124 }
125
126 public static String constantLiteralValue(Object val) {
127 String str = null;
128 if (val != null) {
129 if (val instanceof Boolean || val instanceof Byte || val instanceof Short
130 || val instanceof Integer) {
131 str = val.toString();
132 }
133 // catch all special values
134 else if (val instanceof Double) {
Ben Dodsonf22ba052010-08-12 11:47:06 -0700135 str = canonicalizeFloatingPoint(val.toString(), "");
136 } else if (val instanceof Float) {
137 str = canonicalizeFloatingPoint(val.toString(), "f");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700138 } else if (val instanceof Long) {
139 str = val.toString() + "L";
Ben Dodson920dbbb2010-08-04 15:21:06 -0700140 } else if (val instanceof Character) {
141 str = String.format("\'\\u%04x\'", val);
142 } else if (val instanceof String) {
143 str = "\"" + javaEscapeString((String) val) + "\"";
144 } else {
145 str = "<<<<" + val.toString() + ">>>>";
146 }
147 }
148 if (str == null) {
149 str = "null";
150 }
151 return str;
152 }
153
Ben Dodsonf22ba052010-08-12 11:47:06 -0700154 /**
155 * Returns a canonical string representation of a floating point
156 * number. The representation is suitable for use as Java source
157 * code. This method also addresses bug #4428022 in the Sun JDK.
158 */
159 private static String canonicalizeFloatingPoint(String val, String suffix) {
160 if (val.equals("Infinity")) {
161 return "(1.0" + suffix + " / 0.0" + suffix + ")";
162 } else if (val.equals("-Infinity")) {
163 return "(-1.0" + suffix + " / 0.0" + suffix + ")";
164 } else if (val.equals("NaN")) {
165 return "(0.0" + suffix + " / 0.0" + suffix + ")";
166 }
167
168 String str = val.toString();
169 if (str.indexOf('E') != -1) {
170 return str + suffix;
171 }
172
173 // 1.0 is the only case where a trailing "0" is allowed.
174 // 1.00 is canonicalized as 1.0.
175 int i = str.length() - 1;
176 int d = str.indexOf('.');
177 while (i >= d + 2 && str.charAt(i) == '0') {
178 str = str.substring(0, i--);
179 }
180 return str + suffix;
181 }
182
Ben Dodson920dbbb2010-08-04 15:21:06 -0700183 public static String javaEscapeString(String str) {
184 String result = "";
185 final int N = str.length();
186 for (int i = 0; i < N; i++) {
187 char c = str.charAt(i);
188 if (c == '\\') {
189 result += "\\\\";
190 } else if (c == '\t') {
191 result += "\\t";
192 } else if (c == '\b') {
193 result += "\\b";
194 } else if (c == '\r') {
195 result += "\\r";
196 } else if (c == '\n') {
197 result += "\\n";
198 } else if (c == '\f') {
199 result += "\\f";
200 } else if (c == '\'') {
201 result += "\\'";
202 } else if (c == '\"') {
203 result += "\\\"";
204 } else if (c >= ' ' && c <= '~') {
205 result += c;
206 } else {
207 result += String.format("\\u%04x", new Integer((int) c));
208 }
209 }
210 return result;
211 }
212
213
214 public void makeHDF(Data data, String base) {
215 data.setValue(base + ".kind", kind());
216 type().makeHDF(data, base + ".type");
217 data.setValue(base + ".name", name());
218 data.setValue(base + ".href", htmlPage());
219 data.setValue(base + ".anchor", anchor());
220 TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
221 TagInfo.makeHDF(data, base + ".descr", inlineTags());
222 TagInfo.makeHDF(data, base + ".deprecated", comment().deprecatedTags());
223 TagInfo.makeHDF(data, base + ".seeAlso", comment().seeTags());
224 data.setValue(base + ".since", getSince());
225 data.setValue(base + ".final", isFinal() ? "final" : "");
226 data.setValue(base + ".static", isStatic() ? "static" : "");
227 if (isPublic()) {
228 data.setValue(base + ".scope", "public");
229 } else if (isProtected()) {
230 data.setValue(base + ".scope", "protected");
231 } else if (isPackagePrivate()) {
232 data.setValue(base + ".scope", "");
233 } else if (isPrivate()) {
234 data.setValue(base + ".scope", "private");
235 }
236 Object val = mConstantValue;
237 if (val != null) {
238 String dec = null;
239 String hex = null;
240 String str = null;
241
242 if (val instanceof Boolean) {
243 str = ((Boolean) val).toString();
244 } else if (val instanceof Byte) {
245 dec = String.format("%d", val);
246 hex = String.format("0x%02x", val);
247 } else if (val instanceof Character) {
248 dec = String.format("\'%c\'", val);
249 hex = String.format("0x%04x", val);
250 } else if (val instanceof Double) {
251 str = ((Double) val).toString();
252 } else if (val instanceof Float) {
253 str = ((Float) val).toString();
254 } else if (val instanceof Integer) {
255 dec = String.format("%d", val);
256 hex = String.format("0x%08x", val);
257 } else if (val instanceof Long) {
258 dec = String.format("%d", val);
259 hex = String.format("0x%016x", val);
260 } else if (val instanceof Short) {
261 dec = String.format("%d", val);
262 hex = String.format("0x%04x", val);
263 } else if (val instanceof String) {
264 str = "\"" + ((String) val) + "\"";
265 } else {
266 str = "";
267 }
268
269 if (dec != null && hex != null) {
270 data.setValue(base + ".constantValue.dec", Doclava.escape(dec));
271 data.setValue(base + ".constantValue.hex", Doclava.escape(hex));
272 } else {
273 data.setValue(base + ".constantValue.str", Doclava.escape(str));
274 data.setValue(base + ".constantValue.isString", "1");
275 }
276 }
277
278 setFederatedReferences(data, base);
279 }
280
281 @Override
282 public boolean isExecutable() {
283 return false;
284 }
285
286 public boolean isTransient() {
287 return mIsTransient;
288 }
289
290 public boolean isVolatile() {
291 return mIsVolatile;
292 }
293
294 // Check the declared value with a typed comparison, not a string comparison,
295 // to accommodate toolchains with different fp -> string conversions.
296 private boolean valueEquals(FieldInfo other) {
297 if ((mConstantValue == null) != (other.mConstantValue == null)) {
298 return false;
299 }
300
301 // Null values are considered equal
302 if (mConstantValue == null) {
303 return true;
304 }
305
306 // TODO: This method is called through from an XML comparison only right now,
307 // and mConstantValue is always a String. Get rid of this assertion.
308 if (!(mConstantValue instanceof String && other.mConstantValue instanceof String)) {
309 throw new AssertionError("Bad type for field value");
310 }
311
Ben Dodsonf22ba052010-08-12 11:47:06 -0700312 return mType.equals(other.mType)
313 && mConstantValue.equals(other.mConstantValue);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700314 }
315
316 public boolean isConsistent(FieldInfo fInfo) {
317 boolean consistent = true;
318 if (!mType.equals(fInfo.mType)) {
319 Errors.error(Errors.CHANGED_TYPE, fInfo.position(), "Field " + fInfo.qualifiedName()
320 + " has changed type");
321 consistent = false;
322 } else if (!this.valueEquals(fInfo)) {
323 Errors.error(Errors.CHANGED_VALUE, fInfo.position(), "Field " + fInfo.qualifiedName()
324 + " has changed value from " + mConstantValue + " to " + fInfo.mConstantValue);
325 consistent = false;
326 }
327
328 if (!scope().equals(fInfo.scope())) {
329 Errors.error(Errors.CHANGED_SCOPE, fInfo.position(), "Method " + fInfo.qualifiedName()
330 + " changed scope from " + this.scope() + " to " + fInfo.scope());
331 consistent = false;
332 }
333
334 if (mIsStatic != fInfo.mIsStatic) {
335 Errors.error(Errors.CHANGED_STATIC, fInfo.position(), "Field " + fInfo.qualifiedName()
336 + " has changed 'static' qualifier");
337 consistent = false;
338 }
339
340 if (mIsFinal != fInfo.mIsFinal) {
341 Errors.error(Errors.CHANGED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName()
342 + " has changed 'final' qualifier");
343 consistent = false;
344 }
345
346 if (mIsTransient != fInfo.mIsTransient) {
347 Errors.error(Errors.CHANGED_TRANSIENT, fInfo.position(), "Field " + fInfo.qualifiedName()
348 + " has changed 'transient' qualifier");
349 consistent = false;
350 }
351
352 if (mIsVolatile != fInfo.mIsVolatile) {
353 Errors.error(Errors.CHANGED_VOLATILE, fInfo.position(), "Field " + fInfo.qualifiedName()
354 + " has changed 'volatile' qualifier");
355 consistent = false;
356 }
357
358 if (isDeprecated() != fInfo.isDeprecated()) {
359 Errors.error(Errors.CHANGED_DEPRECATED, fInfo.position(), "Field " + fInfo.qualifiedName()
360 + " has changed deprecation state");
361 consistent = false;
362 }
363
364 return consistent;
365 }
366
367 boolean mIsTransient;
368 boolean mIsVolatile;
369 boolean mDeprecatedKnown;
370 boolean mIsDeprecated;
371 TypeInfo mType;
372 Object mConstantValue;
373}