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