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