blob: 0b0b6c4ec462716ff858542816d2e986989a7bd5 [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
Joe Onorato04099252011-03-09 13:34:18 -080019import com.google.doclava.apicheck.ApiParseException;
Ben Dodson920dbbb2010-08-04 15:21:06 -070020import com.google.clearsilver.jsilver.data.Data;
21import java.util.Comparator;
Andrew Sappersteind6eaacb2011-05-20 13:14:56 -070022import java.util.ArrayList;
Ben Dodson920dbbb2010-08-04 15:21:06 -070023
24public 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
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,
Andrew Sappersteind6eaacb2011-05-20 13:14:56 -070035 SourcePositionInfo position, ArrayList<AnnotationInstanceInfo> annotations) {
Ben Dodson920dbbb2010-08-04 15:21:06 -070036 super(rawCommentText, name, null, containingClass, realContainingClass, isPublic, isProtected,
Dan Bornstein62aaca82010-11-29 13:35:43 -080037 isPackagePrivate, isPrivate, isFinal, isStatic, isSynthetic, chooseKind(isFinal, isStatic, constantValue),
Ben Dodson920dbbb2010-08-04 15:21:06 -070038 position, annotations);
39 mIsTransient = isTransient;
40 mIsVolatile = isVolatile;
41 mType = type;
42 mConstantValue = constantValue;
43 }
44
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
Dan Bornstein62aaca82010-11-29 13:35:43 -080052 static String chooseKind(boolean isFinal, boolean isStatic, Object constantValue)
53 {
54 return isConstant(isFinal, isStatic, constantValue) ? "constant" : "field";
Ben Dodson920dbbb2010-08-04 15:21:06 -070055 }
56
57 public String qualifiedName() {
58 String parentQName
59 = (containingClass() != null) ? (containingClass().qualifiedName() + ".") : "";
60 return parentQName + name();
61 }
62
63 public TypeInfo type() {
64 return mType;
65 }
66
Dan Bornstein62aaca82010-11-29 13:35:43 -080067 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
Ben Dodson920dbbb2010-08-04 15:21:06 -070080 public boolean isConstant() {
Dan Bornstein62aaca82010-11-29 13:35:43 -080081 return isConstant(isFinal(), isStatic(), mConstantValue);
Ben Dodson920dbbb2010-08-04 15:21:06 -070082 }
83
84 public TagInfo[] firstSentenceTags() {
85 return comment().briefTags();
86 }
87
88 public TagInfo[] inlineTags() {
89 return comment().tags();
90 }
91
92 public Object constantValue() {
93 return mConstantValue;
94 }
95
96 public String constantLiteralValue() {
97 return constantLiteralValue(mConstantValue);
98 }
99
100 public void setDeprecated(boolean deprecated) {
101 mDeprecatedKnown = true;
102 mIsDeprecated = deprecated;
103 }
104
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
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) {
Ben Dodsonf22ba052010-08-12 11:47:06 -0700137 str = canonicalizeFloatingPoint(val.toString(), "");
138 } else if (val instanceof Float) {
139 str = canonicalizeFloatingPoint(val.toString(), "f");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700140 } else if (val instanceof Long) {
141 str = val.toString() + "L";
Ben Dodson920dbbb2010-08-04 15:21:06 -0700142 } else if (val instanceof Character) {
143 str = String.format("\'\\u%04x\'", val);
Joe Onorato04099252011-03-09 13:34:18 -0800144 System.out.println("str=" + str);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700145 } 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
Ben Dodsonf22ba052010-08-12 11:47:06 -0700157 /**
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 */
162 private static String canonicalizeFloatingPoint(String val, String suffix) {
163 if (val.equals("Infinity")) {
Joe Onorato04099252011-03-09 13:34:18 -0800164 return "(1.0" + suffix + "/0.0" + suffix + ")";
Ben Dodsonf22ba052010-08-12 11:47:06 -0700165 } else if (val.equals("-Infinity")) {
Joe Onorato04099252011-03-09 13:34:18 -0800166 return "(-1.0" + suffix + "/0.0" + suffix + ")";
Ben Dodsonf22ba052010-08-12 11:47:06 -0700167 } else if (val.equals("NaN")) {
Joe Onorato04099252011-03-09 13:34:18 -0800168 return "(0.0" + suffix + "/0.0" + suffix + ")";
Ben Dodsonf22ba052010-08-12 11:47:06 -0700169 }
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
Ben Dodson920dbbb2010-08-04 15:21:06 -0700186 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
Joe Onorato04099252011-03-09 13:34:18 -0800216 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 }
Ben Dodson920dbbb2010-08-04 15:21:06 -0700317
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 data.setValue(base + ".final", isFinal() ? "final" : "");
330 data.setValue(base + ".static", isStatic() ? "static" : "");
331 if (isPublic()) {
332 data.setValue(base + ".scope", "public");
333 } else if (isProtected()) {
334 data.setValue(base + ".scope", "protected");
335 } else if (isPackagePrivate()) {
336 data.setValue(base + ".scope", "");
337 } else if (isPrivate()) {
338 data.setValue(base + ".scope", "private");
339 }
340 Object val = mConstantValue;
341 if (val != null) {
342 String dec = null;
343 String hex = null;
344 String str = null;
345
346 if (val instanceof Boolean) {
347 str = ((Boolean) val).toString();
348 } else if (val instanceof Byte) {
349 dec = String.format("%d", val);
350 hex = String.format("0x%02x", val);
351 } else if (val instanceof Character) {
352 dec = String.format("\'%c\'", val);
353 hex = String.format("0x%04x", val);
354 } else if (val instanceof Double) {
355 str = ((Double) val).toString();
356 } else if (val instanceof Float) {
357 str = ((Float) val).toString();
358 } else if (val instanceof Integer) {
359 dec = String.format("%d", val);
360 hex = String.format("0x%08x", val);
361 } else if (val instanceof Long) {
362 dec = String.format("%d", val);
363 hex = String.format("0x%016x", val);
364 } else if (val instanceof Short) {
365 dec = String.format("%d", val);
366 hex = String.format("0x%04x", val);
367 } else if (val instanceof String) {
368 str = "\"" + ((String) val) + "\"";
369 } else {
370 str = "";
371 }
372
373 if (dec != null && hex != null) {
374 data.setValue(base + ".constantValue.dec", Doclava.escape(dec));
375 data.setValue(base + ".constantValue.hex", Doclava.escape(hex));
376 } else {
377 data.setValue(base + ".constantValue.str", Doclava.escape(str));
378 data.setValue(base + ".constantValue.isString", "1");
379 }
380 }
381
382 setFederatedReferences(data, base);
383 }
384
385 @Override
386 public boolean isExecutable() {
387 return false;
388 }
389
390 public boolean isTransient() {
391 return mIsTransient;
392 }
393
394 public boolean isVolatile() {
395 return mIsVolatile;
396 }
397
398 // Check the declared value with a typed comparison, not a string comparison,
399 // to accommodate toolchains with different fp -> string conversions.
400 private boolean valueEquals(FieldInfo other) {
401 if ((mConstantValue == null) != (other.mConstantValue == null)) {
402 return false;
403 }
404
405 // Null values are considered equal
406 if (mConstantValue == null) {
407 return true;
408 }
Joe Onorato04099252011-03-09 13:34:18 -0800409
Ben Dodsonf22ba052010-08-12 11:47:06 -0700410 return mType.equals(other.mType)
411 && mConstantValue.equals(other.mConstantValue);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700412 }
413
414 public boolean isConsistent(FieldInfo fInfo) {
415 boolean consistent = true;
416 if (!mType.equals(fInfo.mType)) {
417 Errors.error(Errors.CHANGED_TYPE, fInfo.position(), "Field " + fInfo.qualifiedName()
418 + " has changed type");
419 consistent = false;
420 } else if (!this.valueEquals(fInfo)) {
421 Errors.error(Errors.CHANGED_VALUE, fInfo.position(), "Field " + fInfo.qualifiedName()
422 + " has changed value from " + mConstantValue + " to " + fInfo.mConstantValue);
423 consistent = false;
424 }
425
426 if (!scope().equals(fInfo.scope())) {
427 Errors.error(Errors.CHANGED_SCOPE, fInfo.position(), "Method " + fInfo.qualifiedName()
428 + " changed scope from " + this.scope() + " to " + fInfo.scope());
429 consistent = false;
430 }
431
432 if (mIsStatic != fInfo.mIsStatic) {
433 Errors.error(Errors.CHANGED_STATIC, fInfo.position(), "Field " + fInfo.qualifiedName()
434 + " has changed 'static' qualifier");
435 consistent = false;
436 }
437
438 if (mIsFinal != fInfo.mIsFinal) {
439 Errors.error(Errors.CHANGED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName()
440 + " has changed 'final' qualifier");
441 consistent = false;
442 }
443
444 if (mIsTransient != fInfo.mIsTransient) {
445 Errors.error(Errors.CHANGED_TRANSIENT, fInfo.position(), "Field " + fInfo.qualifiedName()
446 + " has changed 'transient' qualifier");
447 consistent = false;
448 }
449
450 if (mIsVolatile != fInfo.mIsVolatile) {
451 Errors.error(Errors.CHANGED_VOLATILE, fInfo.position(), "Field " + fInfo.qualifiedName()
452 + " has changed 'volatile' qualifier");
453 consistent = false;
454 }
455
456 if (isDeprecated() != fInfo.isDeprecated()) {
457 Errors.error(Errors.CHANGED_DEPRECATED, fInfo.position(), "Field " + fInfo.qualifiedName()
458 + " has changed deprecation state");
459 consistent = false;
460 }
461
462 return consistent;
463 }
464
Andrew Sappersteind6eaacb2011-05-20 13:14:56 -0700465 public boolean hasValue() {
466 return mHasValue;
467 }
468
469 public void setHasValue(boolean hasValue) {
470 mHasValue = hasValue;
471 }
472
Ben Dodson920dbbb2010-08-04 15:21:06 -0700473 boolean mIsTransient;
474 boolean mIsVolatile;
475 boolean mDeprecatedKnown;
476 boolean mIsDeprecated;
Andrew Sappersteind6eaacb2011-05-20 13:14:56 -0700477 boolean mHasValue;
Ben Dodson920dbbb2010-08-04 15:21:06 -0700478 TypeInfo mType;
479 Object mConstantValue;
480}