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.doclava.apicheck.ApiParseException; 20 import com.google.clearsilver.jsilver.data.Data; 21 import java.util.Comparator; 22 import java.util.ArrayList; 23 24 public 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 data.setValue(base + ".final", isFinal() ? "final" : ""); 330 data.setValue(base + ".static", isStatic() ? "static" : ""); 331 if (isPublic()) { 332 data.setValue(base + ".scope", "public"); 333 } else if (isProtected()) { 334 data.setValue(base + ".scope", "protected"); 335 } else if (isPackagePrivate()) { 336 data.setValue(base + ".scope", ""); 337 } else if (isPrivate()) { 338 data.setValue(base + ".scope", "private"); 339 } 340 Object val = mConstantValue; 341 if (val != null) { 342 String dec = null; 343 String hex = null; 344 String str = null; 345 346 if (val instanceof Boolean) { 347 str = ((Boolean) val).toString(); 348 } else if (val instanceof Byte) { 349 dec = String.format("%d", val); 350 hex = String.format("0x%02x", val); 351 } else if (val instanceof Character) { 352 dec = String.format("\'%c\'", val); 353 hex = String.format("0x%04x", val); 354 } else if (val instanceof Double) { 355 str = ((Double) val).toString(); 356 } else if (val instanceof Float) { 357 str = ((Float) val).toString(); 358 } else if (val instanceof Integer) { 359 dec = String.format("%d", val); 360 hex = String.format("0x%08x", val); 361 } else if (val instanceof Long) { 362 dec = String.format("%d", val); 363 hex = String.format("0x%016x", val); 364 } else if (val instanceof Short) { 365 dec = String.format("%d", val); 366 hex = String.format("0x%04x", val); 367 } else if (val instanceof String) { 368 str = "\"" + ((String) val) + "\""; 369 } else { 370 str = ""; 371 } 372 373 if (dec != null && hex != null) { 374 data.setValue(base + ".constantValue.dec", Doclava.escape(dec)); 375 data.setValue(base + ".constantValue.hex", Doclava.escape(hex)); 376 } else { 377 data.setValue(base + ".constantValue.str", Doclava.escape(str)); 378 data.setValue(base + ".constantValue.isString", "1"); 379 } 380 } 381 382 setFederatedReferences(data, base); 383 } 384 385 @Override 386 public boolean isExecutable() { 387 return false; 388 } 389 390 public boolean isTransient() { 391 return mIsTransient; 392 } 393 394 public boolean isVolatile() { 395 return mIsVolatile; 396 } 397 398 // Check the declared value with a typed comparison, not a string comparison, 399 // to accommodate toolchains with different fp -> string conversions. 400 private boolean valueEquals(FieldInfo other) { 401 if ((mConstantValue == null) != (other.mConstantValue == null)) { 402 return false; 403 } 404 405 // Null values are considered equal 406 if (mConstantValue == null) { 407 return true; 408 } 409 410 return mType.equals(other.mType) 411 && mConstantValue.equals(other.mConstantValue); 412 } 413 414 public boolean isConsistent(FieldInfo fInfo) { 415 boolean consistent = true; 416 if (!mType.equals(fInfo.mType)) { 417 Errors.error(Errors.CHANGED_TYPE, fInfo.position(), "Field " + fInfo.qualifiedName() 418 + " has changed type"); 419 consistent = false; 420 } else if (!this.valueEquals(fInfo)) { 421 Errors.error(Errors.CHANGED_VALUE, fInfo.position(), "Field " + fInfo.qualifiedName() 422 + " has changed value from " + mConstantValue + " to " + fInfo.mConstantValue); 423 consistent = false; 424 } 425 426 if (!scope().equals(fInfo.scope())) { 427 Errors.error(Errors.CHANGED_SCOPE, fInfo.position(), "Method " + fInfo.qualifiedName() 428 + " changed scope from " + this.scope() + " to " + fInfo.scope()); 429 consistent = false; 430 } 431 432 if (mIsStatic != fInfo.mIsStatic) { 433 Errors.error(Errors.CHANGED_STATIC, fInfo.position(), "Field " + fInfo.qualifiedName() 434 + " has changed 'static' qualifier"); 435 consistent = false; 436 } 437 438 if (mIsFinal != fInfo.mIsFinal) { 439 Errors.error(Errors.CHANGED_FINAL, fInfo.position(), "Field " + fInfo.qualifiedName() 440 + " has changed 'final' qualifier"); 441 consistent = false; 442 } 443 444 if (mIsTransient != fInfo.mIsTransient) { 445 Errors.error(Errors.CHANGED_TRANSIENT, fInfo.position(), "Field " + fInfo.qualifiedName() 446 + " has changed 'transient' qualifier"); 447 consistent = false; 448 } 449 450 if (mIsVolatile != fInfo.mIsVolatile) { 451 Errors.error(Errors.CHANGED_VOLATILE, fInfo.position(), "Field " + fInfo.qualifiedName() 452 + " has changed 'volatile' qualifier"); 453 consistent = false; 454 } 455 456 if (isDeprecated() != fInfo.isDeprecated()) { 457 Errors.error(Errors.CHANGED_DEPRECATED, fInfo.position(), "Field " + fInfo.qualifiedName() 458 + " has changed deprecation state"); 459 consistent = false; 460 } 461 462 return consistent; 463 } 464 465 public boolean hasValue() { 466 return mHasValue; 467 } 468 469 public void setHasValue(boolean hasValue) { 470 mHasValue = hasValue; 471 } 472 473 boolean mIsTransient; 474 boolean mIsVolatile; 475 boolean mDeprecatedKnown; 476 boolean mIsDeprecated; 477 boolean mHasValue; 478 TypeInfo mType; 479 Object mConstantValue; 480 } 481