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