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