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 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) { 137 str = canonicalizeFloatingPoint(val.toString(), ""); 138 } else if (val instanceof Float) { 139 str = canonicalizeFloatingPoint(val.toString(), "f"); 140 } else if (val instanceof Long) { 141 str = val.toString() + "L"; 142 } else if (val instanceof Character) { 143 str = String.format("\'\\u%04x\'", val); 144 System.out.println("str=" + str); 145 } 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 157 /** 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")) { 164 return "(1.0" + suffix + "/0.0" + suffix + ")"; 165 } else if (val.equals("-Infinity")) { 166 return "(-1.0" + suffix + "/0.0" + suffix + ")"; 167 } else if (val.equals("NaN")) { 168 return "(0.0" + suffix + "/0.0" + suffix + ")"; 169 } 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 186 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 216 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 } 317 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 if (isDeprecated()) { 330 data.setValue(base + ".deprecatedsince", getDeprecatedSince()); 331 } 332 data.setValue(base + ".final", isFinal() ? "final" : ""); 333 data.setValue(base + ".static", isStatic() ? "static" : ""); 334 if (isPublic()) { 335 data.setValue(base + ".scope", "public"); 336 } else if (isProtected()) { 337 data.setValue(base + ".scope", "protected"); 338 } else if (isPackagePrivate()) { 339 data.setValue(base + ".scope", ""); 340 } else if (isPrivate()) { 341 data.setValue(base + ".scope", "private"); 342 } 343 Object val = mConstantValue; 344 if (val != null) { 345 String dec = null; 346 String hex = null; 347 String str = null; 348 349 if (val instanceof Boolean) { 350 str = ((Boolean) val).toString(); 351 } else if (val instanceof Byte) { 352 dec = String.format("%d", val); 353 hex = String.format("0x%02x", val); 354 } else if (val instanceof Character) { 355 dec = String.format("\'%c\'", val); 356 hex = String.format("0x%04x", val); 357 } else if (val instanceof Double) { 358 str = ((Double) val).toString(); 359 } else if (val instanceof Float) { 360 str = ((Float) val).toString(); 361 } else if (val instanceof Integer) { 362 dec = String.format("%d", val); 363 hex = String.format("0x%08x", val); 364 } else if (val instanceof Long) { 365 dec = String.format("%d", val); 366 hex = String.format("0x%016x", val); 367 } else if (val instanceof Short) { 368 dec = String.format("%d", val); 369 hex = String.format("0x%04x", val); 370 } else if (val instanceof String) { 371 str = "\"" + ((String) val) + "\""; 372 } else { 373 str = ""; 374 } 375 376 if (dec != null && hex != null) { 377 data.setValue(base + ".constantValue.dec", Doclava.escape(dec)); 378 data.setValue(base + ".constantValue.hex", Doclava.escape(hex)); 379 } else { 380 data.setValue(base + ".constantValue.str", Doclava.escape(str)); 381 data.setValue(base + ".constantValue.isString", "1"); 382 } 383 } 384 385 AnnotationInstanceInfo.makeLinkListHDF( 386 data, 387 base + ".showAnnotations", 388 showAnnotations().toArray(new AnnotationInstanceInfo[showAnnotations().size()])); 389 390 setFederatedReferences(data, base); 391 } 392 393 @Override 394 public boolean isExecutable() { 395 return false; 396 } 397 398 public boolean isTransient() { 399 return mIsTransient; 400 } 401 402 public boolean isVolatile() { 403 return mIsVolatile; 404 } 405 406 // Check the declared value with a typed comparison, not a string comparison, 407 // to accommodate toolchains with different fp -> string conversions. 408 private boolean valueEquals(FieldInfo other) { 409 if ((mConstantValue == null) != (other.mConstantValue == null)) { 410 return false; 411 } 412 413 // Null values are considered equal 414 if (mConstantValue == null) { 415 return true; 416 } 417 418 return mType.equals(other.mType) 419 && mConstantValue.equals(other.mConstantValue); 420 } 421 422 public boolean isConsistent(FieldInfo fInfo) { 423 boolean consistent = true; 424 if (!mType.equals(fInfo.mType)) { 425 Errors.error(Errors.CHANGED_TYPE, fInfo.position(), "Field " + fInfo.qualifiedName() 426 + " has changed type from " + mType + " to " + fInfo.mType); 427 consistent = false; 428 } else if (!this.valueEquals(fInfo)) { 429 Errors.error(Errors.CHANGED_VALUE, fInfo.position(), "Field " + fInfo.qualifiedName() 430 + " has changed value from " + mConstantValue + " to " + fInfo.mConstantValue); 431 consistent = false; 432 } 433 434 if (!scope().equals(fInfo.scope())) { 435 Errors.error(Errors.CHANGED_SCOPE, fInfo.position(), "Method " + fInfo.qualifiedName() 436 + " changed scope from " + this.scope() + " to " + fInfo.scope()); 437 consistent = false; 438 } 439 440 if (mIsStatic != fInfo.mIsStatic) { 441 Errors.error(Errors.CHANGED_STATIC, fInfo.position(), "Field " + fInfo.qualifiedName() 442 + " has changed 'static' qualifier"); 443 consistent = false; 444 } 445 446 if (!mIsFinal && fInfo.mIsFinal) { 447 Errors.error(Errors.ADDED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName() 448 + " has added 'final' qualifier"); 449 consistent = false; 450 } else if (mIsFinal && !fInfo.mIsFinal) { 451 Errors.error(Errors.REMOVED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName() 452 + " has removed 'final' qualifier"); 453 consistent = false; 454 } 455 456 if (mIsTransient != fInfo.mIsTransient) { 457 Errors.error(Errors.CHANGED_TRANSIENT, fInfo.position(), "Field " + fInfo.qualifiedName() 458 + " has changed 'transient' qualifier"); 459 consistent = false; 460 } 461 462 if (mIsVolatile != fInfo.mIsVolatile) { 463 Errors.error(Errors.CHANGED_VOLATILE, fInfo.position(), "Field " + fInfo.qualifiedName() 464 + " has changed 'volatile' qualifier"); 465 consistent = false; 466 } 467 468 if (isDeprecated() != fInfo.isDeprecated()) { 469 Errors.error(Errors.CHANGED_DEPRECATED, fInfo.position(), "Field " + fInfo.qualifiedName() 470 + " has changed deprecation state " + isDeprecated() + " --> " + fInfo.isDeprecated()); 471 consistent = false; 472 } 473 474 return consistent; 475 } 476 477 public boolean hasValue() { 478 return mHasValue; 479 } 480 481 public void setHasValue(boolean hasValue) { 482 mHasValue = hasValue; 483 } 484 485 boolean mIsTransient; 486 boolean mIsVolatile; 487 boolean mDeprecatedKnown; 488 boolean mIsDeprecated; 489 boolean mHasValue; 490 TypeInfo mType; 491 Object mConstantValue; 492} 493