| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dx.cf.direct; |
| |
| import com.android.dx.cf.iface.ParseException; |
| import com.android.dx.cf.iface.ParseObserver; |
| import com.android.dx.rop.annotation.Annotation; |
| import com.android.dx.rop.annotation.AnnotationVisibility; |
| import com.android.dx.rop.annotation.Annotations; |
| import com.android.dx.rop.annotation.AnnotationsList; |
| import com.android.dx.rop.annotation.NameValuePair; |
| import com.android.dx.rop.cst.Constant; |
| import com.android.dx.rop.cst.ConstantPool; |
| import com.android.dx.rop.cst.CstAnnotation; |
| import com.android.dx.rop.cst.CstArray; |
| import com.android.dx.rop.cst.CstBoolean; |
| import com.android.dx.rop.cst.CstByte; |
| import com.android.dx.rop.cst.CstChar; |
| import com.android.dx.rop.cst.CstDouble; |
| import com.android.dx.rop.cst.CstEnumRef; |
| import com.android.dx.rop.cst.CstFieldRef; |
| import com.android.dx.rop.cst.CstFloat; |
| import com.android.dx.rop.cst.CstInteger; |
| import com.android.dx.rop.cst.CstLong; |
| import com.android.dx.rop.cst.CstNat; |
| import com.android.dx.rop.cst.CstShort; |
| import com.android.dx.rop.cst.CstString; |
| import com.android.dx.rop.cst.CstType; |
| import com.android.dx.rop.cst.CstUtf8; |
| import com.android.dx.rop.type.Type; |
| import com.android.dx.util.ByteArray; |
| import com.android.dx.util.Hex; |
| |
| import java.io.IOException; |
| |
| /** |
| * Parser for annotations. |
| */ |
| public final class AnnotationParser { |
| /** {@code non-null;} class file being parsed */ |
| private final DirectClassFile cf; |
| |
| /** {@code non-null;} constant pool to use */ |
| private final ConstantPool pool; |
| |
| /** {@code non-null;} bytes of the attribute data */ |
| private final ByteArray bytes; |
| |
| /** {@code null-ok;} parse observer, if any */ |
| private final ParseObserver observer; |
| |
| /** {@code non-null;} input stream to parse from */ |
| private final ByteArray.MyDataInputStream input; |
| |
| /** |
| * {@code non-null;} cursor for use when informing the observer of what |
| * was parsed |
| */ |
| private int parseCursor; |
| |
| /** |
| * Constructs an instance. |
| * |
| * @param cf {@code non-null;} class file to parse from |
| * @param offset {@code >= 0;} offset into the class file data to parse at |
| * @param length {@code >= 0;} number of bytes left in the attribute data |
| * @param observer {@code null-ok;} parse observer to notify, if any |
| */ |
| public AnnotationParser(DirectClassFile cf, int offset, int length, |
| ParseObserver observer) { |
| if (cf == null) { |
| throw new NullPointerException("cf == null"); |
| } |
| |
| this.cf = cf; |
| this.pool = cf.getConstantPool(); |
| this.observer = observer; |
| this.bytes = cf.getBytes().slice(offset, offset + length); |
| this.input = bytes.makeDataInputStream(); |
| this.parseCursor = 0; |
| } |
| |
| /** |
| * Parses an annotation value ({@code element_value}) attribute. |
| * |
| * @return {@code non-null;} the parsed constant value |
| */ |
| public Constant parseValueAttribute() { |
| Constant result; |
| |
| try { |
| result = parseValue(); |
| |
| if (input.available() != 0) { |
| throw new ParseException("extra data in attribute"); |
| } |
| } catch (IOException ex) { |
| // ByteArray.MyDataInputStream should never throw. |
| throw new RuntimeException("shouldn't happen", ex); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Parses a parameter annotation attribute. |
| * |
| * @param visibility {@code non-null;} visibility of the parsed annotations |
| * @return {@code non-null;} the parsed list of lists of annotations |
| */ |
| public AnnotationsList parseParameterAttribute( |
| AnnotationVisibility visibility) { |
| AnnotationsList result; |
| |
| try { |
| result = parseAnnotationsList(visibility); |
| |
| if (input.available() != 0) { |
| throw new ParseException("extra data in attribute"); |
| } |
| } catch (IOException ex) { |
| // ByteArray.MyDataInputStream should never throw. |
| throw new RuntimeException("shouldn't happen", ex); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Parses an annotation attribute, per se. |
| * |
| * @param visibility {@code non-null;} visibility of the parsed annotations |
| * @return {@code non-null;} the list of annotations read from the attribute |
| * data |
| */ |
| public Annotations parseAnnotationAttribute( |
| AnnotationVisibility visibility) { |
| Annotations result; |
| |
| try { |
| result = parseAnnotations(visibility); |
| |
| if (input.available() != 0) { |
| throw new ParseException("extra data in attribute"); |
| } |
| } catch (IOException ex) { |
| // ByteArray.MyDataInputStream should never throw. |
| throw new RuntimeException("shouldn't happen", ex); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Parses a list of annotation lists. |
| * |
| * @param visibility {@code non-null;} visibility of the parsed annotations |
| * @return {@code non-null;} the list of annotation lists read from the attribute |
| * data |
| */ |
| private AnnotationsList parseAnnotationsList( |
| AnnotationVisibility visibility) throws IOException { |
| int count = input.readUnsignedByte(); |
| |
| if (observer != null) { |
| parsed(1, "num_parameters: " + Hex.u1(count)); |
| } |
| |
| AnnotationsList outerList = new AnnotationsList(count); |
| |
| for (int i = 0; i < count; i++) { |
| if (observer != null) { |
| parsed(0, "parameter_annotations[" + i + "]:"); |
| changeIndent(1); |
| } |
| |
| Annotations annotations = parseAnnotations(visibility); |
| outerList.set(i, annotations); |
| |
| if (observer != null) { |
| observer.changeIndent(-1); |
| } |
| } |
| |
| outerList.setImmutable(); |
| return outerList; |
| } |
| |
| /** |
| * Parses an annotation list. |
| * |
| * @param visibility {@code non-null;} visibility of the parsed annotations |
| * @return {@code non-null;} the list of annotations read from the attribute |
| * data |
| */ |
| private Annotations parseAnnotations(AnnotationVisibility visibility) |
| throws IOException { |
| int count = input.readUnsignedShort(); |
| |
| if (observer != null) { |
| parsed(2, "num_annotations: " + Hex.u2(count)); |
| } |
| |
| Annotations annotations = new Annotations(); |
| |
| for (int i = 0; i < count; i++) { |
| if (observer != null) { |
| parsed(0, "annotations[" + i + "]:"); |
| changeIndent(1); |
| } |
| |
| Annotation annotation = parseAnnotation(visibility); |
| annotations.add(annotation); |
| |
| if (observer != null) { |
| observer.changeIndent(-1); |
| } |
| } |
| |
| annotations.setImmutable(); |
| return annotations; |
| } |
| |
| /** |
| * Parses a single annotation. |
| * |
| * @param visibility {@code non-null;} visibility of the parsed annotation |
| * @return {@code non-null;} the parsed annotation |
| */ |
| private Annotation parseAnnotation(AnnotationVisibility visibility) |
| throws IOException { |
| requireLength(4); |
| |
| int typeIndex = input.readUnsignedShort(); |
| int numElements = input.readUnsignedShort(); |
| CstUtf8 typeUtf8 = (CstUtf8) pool.get(typeIndex); |
| CstType type = new CstType(Type.intern(typeUtf8.getString())); |
| |
| if (observer != null) { |
| parsed(2, "type: " + type.toHuman()); |
| parsed(2, "num_elements: " + numElements); |
| } |
| |
| Annotation annotation = new Annotation(type, visibility); |
| |
| for (int i = 0; i < numElements; i++) { |
| if (observer != null) { |
| parsed(0, "elements[" + i + "]:"); |
| changeIndent(1); |
| } |
| |
| NameValuePair element = parseElement(); |
| annotation.add(element); |
| |
| if (observer != null) { |
| changeIndent(-1); |
| } |
| } |
| |
| annotation.setImmutable(); |
| return annotation; |
| } |
| |
| /** |
| * Parses a {@link NameValuePair}. |
| * |
| * @return {@code non-null;} the parsed element |
| */ |
| private NameValuePair parseElement() throws IOException { |
| requireLength(5); |
| |
| int elementNameIndex = input.readUnsignedShort(); |
| CstUtf8 elementName = (CstUtf8) pool.get(elementNameIndex); |
| |
| if (observer != null) { |
| parsed(2, "element_name: " + elementName.toHuman()); |
| parsed(0, "value: "); |
| changeIndent(1); |
| } |
| |
| Constant value = parseValue(); |
| |
| if (observer != null) { |
| changeIndent(-1); |
| } |
| |
| return new NameValuePair(elementName, value); |
| } |
| |
| /** |
| * Parses an annotation value. |
| * |
| * @return {@code non-null;} the parsed value |
| */ |
| private Constant parseValue() throws IOException { |
| int tag = input.readUnsignedByte(); |
| |
| if (observer != null) { |
| CstUtf8 humanTag = new CstUtf8(Character.toString((char) tag)); |
| parsed(1, "tag: " + humanTag.toQuoted()); |
| } |
| |
| switch (tag) { |
| case 'B': { |
| CstInteger value = (CstInteger) parseConstant(); |
| return CstByte.make(value.getValue()); |
| } |
| case 'C': { |
| CstInteger value = (CstInteger) parseConstant(); |
| int intValue = value.getValue(); |
| return CstChar.make(value.getValue()); |
| } |
| case 'D': { |
| CstDouble value = (CstDouble) parseConstant(); |
| return value; |
| } |
| case 'F': { |
| CstFloat value = (CstFloat) parseConstant(); |
| return value; |
| } |
| case 'I': { |
| CstInteger value = (CstInteger) parseConstant(); |
| return value; |
| } |
| case 'J': { |
| CstLong value = (CstLong) parseConstant(); |
| return value; |
| } |
| case 'S': { |
| CstInteger value = (CstInteger) parseConstant(); |
| return CstShort.make(value.getValue()); |
| } |
| case 'Z': { |
| CstInteger value = (CstInteger) parseConstant(); |
| return CstBoolean.make(value.getValue()); |
| } |
| case 'c': { |
| int classInfoIndex = input.readUnsignedShort(); |
| CstUtf8 value = (CstUtf8) pool.get(classInfoIndex); |
| Type type = Type.internReturnType(value.getString()); |
| |
| if (observer != null) { |
| parsed(2, "class_info: " + type.toHuman()); |
| } |
| |
| return new CstType(type); |
| } |
| case 's': { |
| CstString value = new CstString((CstUtf8) parseConstant()); |
| return value; |
| } |
| case 'e': { |
| requireLength(4); |
| |
| int typeNameIndex = input.readUnsignedShort(); |
| int constNameIndex = input.readUnsignedShort(); |
| CstUtf8 typeName = (CstUtf8) pool.get(typeNameIndex); |
| CstUtf8 constName = (CstUtf8) pool.get(constNameIndex); |
| |
| if (observer != null) { |
| parsed(2, "type_name: " + typeName.toHuman()); |
| parsed(2, "const_name: " + constName.toHuman()); |
| } |
| |
| return new CstEnumRef(new CstNat(constName, typeName)); |
| } |
| case '@': { |
| Annotation annotation = |
| parseAnnotation(AnnotationVisibility.EMBEDDED); |
| return new CstAnnotation(annotation); |
| } |
| case '[': { |
| requireLength(2); |
| |
| int numValues = input.readUnsignedShort(); |
| CstArray.List list = new CstArray.List(numValues); |
| |
| if (observer != null) { |
| parsed(2, "num_values: " + numValues); |
| changeIndent(1); |
| } |
| |
| for (int i = 0; i < numValues; i++) { |
| if (observer != null) { |
| changeIndent(-1); |
| parsed(0, "element_value[" + i + "]:"); |
| changeIndent(1); |
| } |
| list.set(i, parseValue()); |
| } |
| |
| if (observer != null) { |
| changeIndent(-1); |
| } |
| |
| list.setImmutable(); |
| return new CstArray(list); |
| } |
| default: { |
| throw new ParseException("unknown annotation tag: " + |
| Hex.u1(tag)); |
| } |
| } |
| } |
| |
| /** |
| * Helper for {@link #parseValue}, which parses a constant reference |
| * and returns the referred-to constant value. |
| * |
| * @return {@code non-null;} the parsed value |
| */ |
| private Constant parseConstant() throws IOException { |
| int constValueIndex = input.readUnsignedShort(); |
| Constant value = (Constant) pool.get(constValueIndex); |
| |
| if (observer != null) { |
| String human = (value instanceof CstUtf8) |
| ? ((CstUtf8) value).toQuoted() |
| : value.toHuman(); |
| parsed(2, "constant_value: " + human); |
| } |
| |
| return value; |
| } |
| |
| /** |
| * Helper which will throw an exception if the given number of bytes |
| * is not available to be read. |
| * |
| * @param requiredLength the number of required bytes |
| */ |
| private void requireLength(int requiredLength) throws IOException { |
| if (input.available() < requiredLength) { |
| throw new ParseException("truncated annotation attribute"); |
| } |
| } |
| |
| /** |
| * Helper which indicates that some bytes were just parsed. This should |
| * only be used (for efficiency sake) if the parse is known to be |
| * observed. |
| * |
| * @param length {@code >= 0;} number of bytes parsed |
| * @param message {@code non-null;} associated message |
| */ |
| private void parsed(int length, String message) { |
| observer.parsed(bytes, parseCursor, length, message); |
| parseCursor += length; |
| } |
| |
| /** |
| * Convenience wrapper that simply calls through to |
| * {@code observer.changeIndent()}. |
| * |
| * @param indent the amount to change the indent by |
| */ |
| private void changeIndent(int indent) { |
| observer.changeIndent(indent); |
| } |
| } |