AnnotationParser.java revision 72e93344b4d1ffc71e9c832ec23de0657e5b04a5
1/* 2 * Copyright (C) 2007 The Android Open Source Project 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.android.dx.cf.direct; 18 19import com.android.dx.cf.iface.ParseException; 20import com.android.dx.cf.iface.ParseObserver; 21import com.android.dx.rop.annotation.Annotation; 22import com.android.dx.rop.annotation.AnnotationVisibility; 23import com.android.dx.rop.annotation.Annotations; 24import com.android.dx.rop.annotation.AnnotationsList; 25import com.android.dx.rop.annotation.NameValuePair; 26import com.android.dx.rop.cst.Constant; 27import com.android.dx.rop.cst.ConstantPool; 28import com.android.dx.rop.cst.CstAnnotation; 29import com.android.dx.rop.cst.CstArray; 30import com.android.dx.rop.cst.CstBoolean; 31import com.android.dx.rop.cst.CstByte; 32import com.android.dx.rop.cst.CstChar; 33import com.android.dx.rop.cst.CstDouble; 34import com.android.dx.rop.cst.CstEnumRef; 35import com.android.dx.rop.cst.CstFieldRef; 36import com.android.dx.rop.cst.CstFloat; 37import com.android.dx.rop.cst.CstInteger; 38import com.android.dx.rop.cst.CstLong; 39import com.android.dx.rop.cst.CstNat; 40import com.android.dx.rop.cst.CstShort; 41import com.android.dx.rop.cst.CstString; 42import com.android.dx.rop.cst.CstType; 43import com.android.dx.rop.cst.CstUtf8; 44import com.android.dx.rop.type.Type; 45import com.android.dx.util.ByteArray; 46import com.android.dx.util.Hex; 47 48import java.io.IOException; 49 50/** 51 * Parser for annotations. 52 */ 53public final class AnnotationParser { 54 /** {@code non-null;} class file being parsed */ 55 private final DirectClassFile cf; 56 57 /** {@code non-null;} constant pool to use */ 58 private final ConstantPool pool; 59 60 /** {@code non-null;} bytes of the attribute data */ 61 private final ByteArray bytes; 62 63 /** {@code null-ok;} parse observer, if any */ 64 private final ParseObserver observer; 65 66 /** {@code non-null;} input stream to parse from */ 67 private final ByteArray.MyDataInputStream input; 68 69 /** 70 * {@code non-null;} cursor for use when informing the observer of what 71 * was parsed 72 */ 73 private int parseCursor; 74 75 /** 76 * Constructs an instance. 77 * 78 * @param cf {@code non-null;} class file to parse from 79 * @param offset {@code >= 0;} offset into the class file data to parse at 80 * @param length {@code >= 0;} number of bytes left in the attribute data 81 * @param observer {@code null-ok;} parse observer to notify, if any 82 */ 83 public AnnotationParser(DirectClassFile cf, int offset, int length, 84 ParseObserver observer) { 85 if (cf == null) { 86 throw new NullPointerException("cf == null"); 87 } 88 89 this.cf = cf; 90 this.pool = cf.getConstantPool(); 91 this.observer = observer; 92 this.bytes = cf.getBytes().slice(offset, offset + length); 93 this.input = bytes.makeDataInputStream(); 94 this.parseCursor = 0; 95 } 96 97 /** 98 * Parses an annotation value ({@code element_value}) attribute. 99 * 100 * @return {@code non-null;} the parsed constant value 101 */ 102 public Constant parseValueAttribute() { 103 Constant result; 104 105 try { 106 result = parseValue(); 107 108 if (input.available() != 0) { 109 throw new ParseException("extra data in attribute"); 110 } 111 } catch (IOException ex) { 112 // ByteArray.MyDataInputStream should never throw. 113 throw new RuntimeException("shouldn't happen", ex); 114 } 115 116 return result; 117 } 118 119 /** 120 * Parses a parameter annotation attribute. 121 * 122 * @param visibility {@code non-null;} visibility of the parsed annotations 123 * @return {@code non-null;} the parsed list of lists of annotations 124 */ 125 public AnnotationsList parseParameterAttribute( 126 AnnotationVisibility visibility) { 127 AnnotationsList result; 128 129 try { 130 result = parseAnnotationsList(visibility); 131 132 if (input.available() != 0) { 133 throw new ParseException("extra data in attribute"); 134 } 135 } catch (IOException ex) { 136 // ByteArray.MyDataInputStream should never throw. 137 throw new RuntimeException("shouldn't happen", ex); 138 } 139 140 return result; 141 } 142 143 /** 144 * Parses an annotation attribute, per se. 145 * 146 * @param visibility {@code non-null;} visibility of the parsed annotations 147 * @return {@code non-null;} the list of annotations read from the attribute 148 * data 149 */ 150 public Annotations parseAnnotationAttribute( 151 AnnotationVisibility visibility) { 152 Annotations result; 153 154 try { 155 result = parseAnnotations(visibility); 156 157 if (input.available() != 0) { 158 throw new ParseException("extra data in attribute"); 159 } 160 } catch (IOException ex) { 161 // ByteArray.MyDataInputStream should never throw. 162 throw new RuntimeException("shouldn't happen", ex); 163 } 164 165 return result; 166 } 167 168 /** 169 * Parses a list of annotation lists. 170 * 171 * @param visibility {@code non-null;} visibility of the parsed annotations 172 * @return {@code non-null;} the list of annotation lists read from the attribute 173 * data 174 */ 175 private AnnotationsList parseAnnotationsList( 176 AnnotationVisibility visibility) throws IOException { 177 int count = input.readUnsignedByte(); 178 179 if (observer != null) { 180 parsed(1, "num_parameters: " + Hex.u1(count)); 181 } 182 183 AnnotationsList outerList = new AnnotationsList(count); 184 185 for (int i = 0; i < count; i++) { 186 if (observer != null) { 187 parsed(0, "parameter_annotations[" + i + "]:"); 188 changeIndent(1); 189 } 190 191 Annotations annotations = parseAnnotations(visibility); 192 outerList.set(i, annotations); 193 194 if (observer != null) { 195 observer.changeIndent(-1); 196 } 197 } 198 199 outerList.setImmutable(); 200 return outerList; 201 } 202 203 /** 204 * Parses an annotation list. 205 * 206 * @param visibility {@code non-null;} visibility of the parsed annotations 207 * @return {@code non-null;} the list of annotations read from the attribute 208 * data 209 */ 210 private Annotations parseAnnotations(AnnotationVisibility visibility) 211 throws IOException { 212 int count = input.readUnsignedShort(); 213 214 if (observer != null) { 215 parsed(2, "num_annotations: " + Hex.u2(count)); 216 } 217 218 Annotations annotations = new Annotations(); 219 220 for (int i = 0; i < count; i++) { 221 if (observer != null) { 222 parsed(0, "annotations[" + i + "]:"); 223 changeIndent(1); 224 } 225 226 Annotation annotation = parseAnnotation(visibility); 227 annotations.add(annotation); 228 229 if (observer != null) { 230 observer.changeIndent(-1); 231 } 232 } 233 234 annotations.setImmutable(); 235 return annotations; 236 } 237 238 /** 239 * Parses a single annotation. 240 * 241 * @param visibility {@code non-null;} visibility of the parsed annotation 242 * @return {@code non-null;} the parsed annotation 243 */ 244 private Annotation parseAnnotation(AnnotationVisibility visibility) 245 throws IOException { 246 requireLength(4); 247 248 int typeIndex = input.readUnsignedShort(); 249 int numElements = input.readUnsignedShort(); 250 CstUtf8 typeUtf8 = (CstUtf8) pool.get(typeIndex); 251 CstType type = new CstType(Type.intern(typeUtf8.getString())); 252 253 if (observer != null) { 254 parsed(2, "type: " + type.toHuman()); 255 parsed(2, "num_elements: " + numElements); 256 } 257 258 Annotation annotation = new Annotation(type, visibility); 259 260 for (int i = 0; i < numElements; i++) { 261 if (observer != null) { 262 parsed(0, "elements[" + i + "]:"); 263 changeIndent(1); 264 } 265 266 NameValuePair element = parseElement(); 267 annotation.add(element); 268 269 if (observer != null) { 270 changeIndent(-1); 271 } 272 } 273 274 annotation.setImmutable(); 275 return annotation; 276 } 277 278 /** 279 * Parses a {@link NameValuePair}. 280 * 281 * @return {@code non-null;} the parsed element 282 */ 283 private NameValuePair parseElement() throws IOException { 284 requireLength(5); 285 286 int elementNameIndex = input.readUnsignedShort(); 287 CstUtf8 elementName = (CstUtf8) pool.get(elementNameIndex); 288 289 if (observer != null) { 290 parsed(2, "element_name: " + elementName.toHuman()); 291 parsed(0, "value: "); 292 changeIndent(1); 293 } 294 295 Constant value = parseValue(); 296 297 if (observer != null) { 298 changeIndent(-1); 299 } 300 301 return new NameValuePair(elementName, value); 302 } 303 304 /** 305 * Parses an annotation value. 306 * 307 * @return {@code non-null;} the parsed value 308 */ 309 private Constant parseValue() throws IOException { 310 int tag = input.readUnsignedByte(); 311 312 if (observer != null) { 313 CstUtf8 humanTag = new CstUtf8(Character.toString((char) tag)); 314 parsed(1, "tag: " + humanTag.toQuoted()); 315 } 316 317 switch (tag) { 318 case 'B': { 319 CstInteger value = (CstInteger) parseConstant(); 320 return CstByte.make(value.getValue()); 321 } 322 case 'C': { 323 CstInteger value = (CstInteger) parseConstant(); 324 int intValue = value.getValue(); 325 return CstChar.make(value.getValue()); 326 } 327 case 'D': { 328 CstDouble value = (CstDouble) parseConstant(); 329 return value; 330 } 331 case 'F': { 332 CstFloat value = (CstFloat) parseConstant(); 333 return value; 334 } 335 case 'I': { 336 CstInteger value = (CstInteger) parseConstant(); 337 return value; 338 } 339 case 'J': { 340 CstLong value = (CstLong) parseConstant(); 341 return value; 342 } 343 case 'S': { 344 CstInteger value = (CstInteger) parseConstant(); 345 return CstShort.make(value.getValue()); 346 } 347 case 'Z': { 348 CstInteger value = (CstInteger) parseConstant(); 349 return CstBoolean.make(value.getValue()); 350 } 351 case 'c': { 352 int classInfoIndex = input.readUnsignedShort(); 353 CstUtf8 value = (CstUtf8) pool.get(classInfoIndex); 354 Type type = Type.internReturnType(value.getString()); 355 356 if (observer != null) { 357 parsed(2, "class_info: " + type.toHuman()); 358 } 359 360 return new CstType(type); 361 } 362 case 's': { 363 CstString value = new CstString((CstUtf8) parseConstant()); 364 return value; 365 } 366 case 'e': { 367 requireLength(4); 368 369 int typeNameIndex = input.readUnsignedShort(); 370 int constNameIndex = input.readUnsignedShort(); 371 CstUtf8 typeName = (CstUtf8) pool.get(typeNameIndex); 372 CstUtf8 constName = (CstUtf8) pool.get(constNameIndex); 373 374 if (observer != null) { 375 parsed(2, "type_name: " + typeName.toHuman()); 376 parsed(2, "const_name: " + constName.toHuman()); 377 } 378 379 return new CstEnumRef(new CstNat(constName, typeName)); 380 } 381 case '@': { 382 Annotation annotation = 383 parseAnnotation(AnnotationVisibility.EMBEDDED); 384 return new CstAnnotation(annotation); 385 } 386 case '[': { 387 requireLength(2); 388 389 int numValues = input.readUnsignedShort(); 390 CstArray.List list = new CstArray.List(numValues); 391 392 if (observer != null) { 393 parsed(2, "num_values: " + numValues); 394 changeIndent(1); 395 } 396 397 for (int i = 0; i < numValues; i++) { 398 if (observer != null) { 399 changeIndent(-1); 400 parsed(0, "element_value[" + i + "]:"); 401 changeIndent(1); 402 } 403 list.set(i, parseValue()); 404 } 405 406 if (observer != null) { 407 changeIndent(-1); 408 } 409 410 list.setImmutable(); 411 return new CstArray(list); 412 } 413 default: { 414 throw new ParseException("unknown annotation tag: " + 415 Hex.u1(tag)); 416 } 417 } 418 } 419 420 /** 421 * Helper for {@link #parseValue}, which parses a constant reference 422 * and returns the referred-to constant value. 423 * 424 * @return {@code non-null;} the parsed value 425 */ 426 private Constant parseConstant() throws IOException { 427 int constValueIndex = input.readUnsignedShort(); 428 Constant value = (Constant) pool.get(constValueIndex); 429 430 if (observer != null) { 431 String human = (value instanceof CstUtf8) 432 ? ((CstUtf8) value).toQuoted() 433 : value.toHuman(); 434 parsed(2, "constant_value: " + human); 435 } 436 437 return value; 438 } 439 440 /** 441 * Helper which will throw an exception if the given number of bytes 442 * is not available to be read. 443 * 444 * @param requiredLength the number of required bytes 445 */ 446 private void requireLength(int requiredLength) throws IOException { 447 if (input.available() < requiredLength) { 448 throw new ParseException("truncated annotation attribute"); 449 } 450 } 451 452 /** 453 * Helper which indicates that some bytes were just parsed. This should 454 * only be used (for efficiency sake) if the parse is known to be 455 * observed. 456 * 457 * @param length {@code >= 0;} number of bytes parsed 458 * @param message {@code non-null;} associated message 459 */ 460 private void parsed(int length, String message) { 461 observer.parsed(bytes, parseCursor, length, message); 462 parseCursor += length; 463 } 464 465 /** 466 * Convenience wrapper that simply calls through to 467 * {@code observer.changeIndent()}. 468 * 469 * @param indent the amount to change the indent by 470 */ 471 private void changeIndent(int indent) { 472 observer.changeIndent(indent); 473 } 474} 475