blob: ca38fc51d8fc0f616e511cfa19c1c23c60b3eb1a [file] [log] [blame]
/*
* 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);
}
}