blob: f7e364aa7b9d8e199363c2e8f34d211237f792bb [file] [log] [blame]
/*
* Copyright (C) 2008 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.dex.file;
import com.android.dx.rop.annotation.Annotation;
import com.android.dx.rop.annotation.NameValuePair;
import com.android.dx.rop.cst.Constant;
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.CstKnownNull;
import com.android.dx.rop.cst.CstLiteralBits;
import com.android.dx.rop.cst.CstLong;
import com.android.dx.rop.cst.CstMethodRef;
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.util.AnnotatedOutput;
import com.android.dx.util.Hex;
import java.util.Collection;
/**
* Handler for writing out {@code encoded_values} and parts
* thereof.
*/
public final class ValueEncoder {
/** annotation value type constant: {@code byte} */
private static final int VALUE_BYTE = 0x00;
/** annotation value type constant: {@code short} */
private static final int VALUE_SHORT = 0x02;
/** annotation value type constant: {@code char} */
private static final int VALUE_CHAR = 0x03;
/** annotation value type constant: {@code int} */
private static final int VALUE_INT = 0x04;
/** annotation value type constant: {@code long} */
private static final int VALUE_LONG = 0x06;
/** annotation value type constant: {@code float} */
private static final int VALUE_FLOAT = 0x10;
/** annotation value type constant: {@code double} */
private static final int VALUE_DOUBLE = 0x11;
/** annotation value type constant: {@code string} */
private static final int VALUE_STRING = 0x17;
/** annotation value type constant: {@code type} */
private static final int VALUE_TYPE = 0x18;
/** annotation value type constant: {@code field} */
private static final int VALUE_FIELD = 0x19;
/** annotation value type constant: {@code method} */
private static final int VALUE_METHOD = 0x1a;
/** annotation value type constant: {@code enum} */
private static final int VALUE_ENUM = 0x1b;
/** annotation value type constant: {@code array} */
private static final int VALUE_ARRAY = 0x1c;
/** annotation value type constant: {@code annotation} */
private static final int VALUE_ANNOTATION = 0x1d;
/** annotation value type constant: {@code null} */
private static final int VALUE_NULL = 0x1e;
/** annotation value type constant: {@code boolean} */
private static final int VALUE_BOOLEAN = 0x1f;
/** {@code non-null;} file being written */
private final DexFile file;
/** {@code non-null;} output stream to write to */
private final AnnotatedOutput out;
/**
* Construct an instance.
*
* @param file {@code non-null;} file being written
* @param out {@code non-null;} output stream to write to
*/
public ValueEncoder(DexFile file, AnnotatedOutput out) {
if (file == null) {
throw new NullPointerException("file == null");
}
if (out == null) {
throw new NullPointerException("out == null");
}
this.file = file;
this.out = out;
}
/**
* Writes out the encoded form of the given constant.
*
* @param cst {@code non-null;} the constant to write
*/
public void writeConstant(Constant cst) {
int type = constantToValueType(cst);
int arg;
switch (type) {
case VALUE_BYTE:
case VALUE_SHORT:
case VALUE_INT:
case VALUE_LONG: {
long value = ((CstLiteralBits) cst).getLongBits();
writeSignedIntegralValue(type, value);
break;
}
case VALUE_CHAR: {
long value = ((CstLiteralBits) cst).getLongBits();
writeUnsignedIntegralValue(type, value);
break;
}
case VALUE_FLOAT: {
// Shift value left 32 so that right-zero-extension works.
long value = ((CstFloat) cst).getLongBits() << 32;
writeRightZeroExtendedValue(type, value);
break;
}
case VALUE_DOUBLE: {
long value = ((CstDouble) cst).getLongBits();
writeRightZeroExtendedValue(type, value);
break;
}
case VALUE_STRING: {
int index = file.getStringIds().indexOf((CstString) cst);
writeUnsignedIntegralValue(type, (long) index);
break;
}
case VALUE_TYPE: {
int index = file.getTypeIds().indexOf((CstType) cst);
writeUnsignedIntegralValue(type, (long) index);
break;
}
case VALUE_FIELD: {
int index = file.getFieldIds().indexOf((CstFieldRef) cst);
writeUnsignedIntegralValue(type, (long) index);
break;
}
case VALUE_METHOD: {
int index = file.getMethodIds().indexOf((CstMethodRef) cst);
writeUnsignedIntegralValue(type, (long) index);
break;
}
case VALUE_ENUM: {
CstFieldRef fieldRef = ((CstEnumRef) cst).getFieldRef();
int index = file.getFieldIds().indexOf(fieldRef);
writeUnsignedIntegralValue(type, (long) index);
break;
}
case VALUE_ARRAY: {
out.writeByte(type);
writeArray((CstArray) cst, false);
break;
}
case VALUE_ANNOTATION: {
out.writeByte(type);
writeAnnotation(((CstAnnotation) cst).getAnnotation(),
false);
break;
}
case VALUE_NULL: {
out.writeByte(type);
break;
}
case VALUE_BOOLEAN: {
int value = ((CstBoolean) cst).getIntBits();
out.writeByte(type | (value << 5));
break;
}
default: {
throw new RuntimeException("Shouldn't happen");
}
}
}
/**
* Gets the value type for the given constant.
*
* @param cst {@code non-null;} the constant
* @return the value type; one of the {@code VALUE_*} constants
* defined by this class
*/
private static int constantToValueType(Constant cst) {
/*
* TODO: Constant should probable have an associated enum, so this
* can be a switch().
*/
if (cst instanceof CstByte) {
return VALUE_BYTE;
} else if (cst instanceof CstShort) {
return VALUE_SHORT;
} else if (cst instanceof CstChar) {
return VALUE_CHAR;
} else if (cst instanceof CstInteger) {
return VALUE_INT;
} else if (cst instanceof CstLong) {
return VALUE_LONG;
} else if (cst instanceof CstFloat) {
return VALUE_FLOAT;
} else if (cst instanceof CstDouble) {
return VALUE_DOUBLE;
} else if (cst instanceof CstString) {
return VALUE_STRING;
} else if (cst instanceof CstType) {
return VALUE_TYPE;
} else if (cst instanceof CstFieldRef) {
return VALUE_FIELD;
} else if (cst instanceof CstMethodRef) {
return VALUE_METHOD;
} else if (cst instanceof CstEnumRef) {
return VALUE_ENUM;
} else if (cst instanceof CstArray) {
return VALUE_ARRAY;
} else if (cst instanceof CstAnnotation) {
return VALUE_ANNOTATION;
} else if (cst instanceof CstKnownNull) {
return VALUE_NULL;
} else if (cst instanceof CstBoolean) {
return VALUE_BOOLEAN;
} else {
throw new RuntimeException("Shouldn't happen");
}
}
/**
* Writes out the encoded form of the given array, that is, as
* an {@code encoded_array} and not including a
* {@code value_type} prefix. If the output stream keeps
* (debugging) annotations and {@code topLevel} is
* {@code true}, then this method will write (debugging)
* annotations.
*
* @param array {@code non-null;} array instance to write
* @param topLevel {@code true} iff the given annotation is the
* top-level annotation or {@code false} if it is a sub-annotation
* of some other annotation
*/
public void writeArray(CstArray array, boolean topLevel) {
boolean annotates = topLevel && out.annotates();
CstArray.List list = ((CstArray) array).getList();
int size = list.size();
if (annotates) {
out.annotate(" size: " + Hex.u4(size));
}
out.writeUnsignedLeb128(size);
for (int i = 0; i < size; i++) {
Constant cst = list.get(i);
if (annotates) {
out.annotate(" [" + Integer.toHexString(i) + "] " +
constantToHuman(cst));
}
writeConstant(cst);
}
if (annotates) {
out.endAnnotation();
}
}
/**
* Writes out the encoded form of the given annotation, that is,
* as an {@code encoded_annotation} and not including a
* {@code value_type} prefix. If the output stream keeps
* (debugging) annotations and {@code topLevel} is
* {@code true}, then this method will write (debugging)
* annotations.
*
* @param annotation {@code non-null;} annotation instance to write
* @param topLevel {@code true} iff the given annotation is the
* top-level annotation or {@code false} if it is a sub-annotation
* of some other annotation
*/
public void writeAnnotation(Annotation annotation, boolean topLevel) {
boolean annotates = topLevel && out.annotates();
StringIdsSection stringIds = file.getStringIds();
TypeIdsSection typeIds = file.getTypeIds();
CstType type = annotation.getType();
int typeIdx = typeIds.indexOf(type);
if (annotates) {
out.annotate(" type_idx: " + Hex.u4(typeIdx) + " // " +
type.toHuman());
}
out.writeUnsignedLeb128(typeIds.indexOf(annotation.getType()));
Collection<NameValuePair> pairs = annotation.getNameValuePairs();
int size = pairs.size();
if (annotates) {
out.annotate(" size: " + Hex.u4(size));
}
out.writeUnsignedLeb128(size);
int at = 0;
for (NameValuePair pair : pairs) {
CstUtf8 name = pair.getName();
int nameIdx = stringIds.indexOf(name);
Constant value = pair.getValue();
if (annotates) {
out.annotate(0, " elements[" + at + "]:");
at++;
out.annotate(" name_idx: " + Hex.u4(nameIdx) + " // " +
name.toHuman());
}
out.writeUnsignedLeb128(nameIdx);
if (annotates) {
out.annotate(" value: " + constantToHuman(value));
}
writeConstant(value);
}
if (annotates) {
out.endAnnotation();
}
}
/**
* Gets the colloquial type name and human form of the type of the
* given constant, when used as an encoded value.
*
* @param cst {@code non-null;} the constant
* @return {@code non-null;} its type name and human form
*/
public static String constantToHuman(Constant cst) {
int type = constantToValueType(cst);
if (type == VALUE_NULL) {
return "null";
}
StringBuilder sb = new StringBuilder();
sb.append(cst.typeName());
sb.append(' ');
sb.append(cst.toHuman());
return sb.toString();
}
/**
* Helper for {@link #writeConstant}, which writes out the value
* for any signed integral type.
*
* @param type the type constant
* @param value {@code long} bits of the value
*/
private void writeSignedIntegralValue(int type, long value) {
/*
* Figure out how many bits are needed to represent the value,
* including a sign bit: The bit count is subtracted from 65
* and not 64 to account for the sign bit. The xor operation
* has the effect of leaving non-negative values alone and
* unary complementing negative values (so that a leading zero
* count always returns a useful number for our present
* purpose).
*/
int requiredBits =
65 - Long.numberOfLeadingZeros(value ^ (value >> 63));
// Round up the requiredBits to a number of bytes.
int requiredBytes = (requiredBits + 0x07) >> 3;
/*
* Write the header byte, which includes the type and
* requiredBytes - 1.
*/
out.writeByte(type | ((requiredBytes - 1) << 5));
// Write the value, per se.
while (requiredBytes > 0) {
out.writeByte((byte) value);
value >>= 8;
requiredBytes--;
}
}
/**
* Helper for {@link #writeConstant}, which writes out the value
* for any unsigned integral type.
*
* @param type the type constant
* @param value {@code long} bits of the value
*/
private void writeUnsignedIntegralValue(int type, long value) {
// Figure out how many bits are needed to represent the value.
int requiredBits = 64 - Long.numberOfLeadingZeros(value);
if (requiredBits == 0) {
requiredBits = 1;
}
// Round up the requiredBits to a number of bytes.
int requiredBytes = (requiredBits + 0x07) >> 3;
/*
* Write the header byte, which includes the type and
* requiredBytes - 1.
*/
out.writeByte(type | ((requiredBytes - 1) << 5));
// Write the value, per se.
while (requiredBytes > 0) {
out.writeByte((byte) value);
value >>= 8;
requiredBytes--;
}
}
/**
* Helper for {@link #writeConstant}, which writes out a
* right-zero-extended value.
*
* @param type the type constant
* @param value {@code long} bits of the value
*/
private void writeRightZeroExtendedValue(int type, long value) {
// Figure out how many bits are needed to represent the value.
int requiredBits = 64 - Long.numberOfTrailingZeros(value);
if (requiredBits == 0) {
requiredBits = 1;
}
// Round up the requiredBits to a number of bytes.
int requiredBytes = (requiredBits + 0x07) >> 3;
// Scootch the first bits to be written down to the low-order bits.
value >>= 64 - (requiredBytes * 8);
/*
* Write the header byte, which includes the type and
* requiredBytes - 1.
*/
out.writeByte(type | ((requiredBytes - 1) << 5));
// Write the value, per se.
while (requiredBytes > 0) {
out.writeByte((byte) value);
value >>= 8;
requiredBytes--;
}
}
/**
* Helper for {@code addContents()} methods, which adds
* contents for a particular {@link Annotation}, calling itself
* recursively should it encounter a nested annotation.
*
* @param file {@code non-null;} the file to add to
* @param annotation {@code non-null;} the annotation to add contents for
*/
public static void addContents(DexFile file, Annotation annotation) {
TypeIdsSection typeIds = file.getTypeIds();
StringIdsSection stringIds = file.getStringIds();
typeIds.intern(annotation.getType());
for (NameValuePair pair : annotation.getNameValuePairs()) {
stringIds.intern(pair.getName());
addContents(file, pair.getValue());
}
}
/**
* Helper for {@code addContents()} methods, which adds
* contents for a particular constant, calling itself recursively
* should it encounter a {@link CstArray} and calling {@link
* #addContents(DexFile,Annotation)} recursively should it
* encounter a {@link CstAnnotation}.
*
* @param file {@code non-null;} the file to add to
* @param cst {@code non-null;} the constant to add contents for
*/
public static void addContents(DexFile file, Constant cst) {
if (cst instanceof CstAnnotation) {
addContents(file, ((CstAnnotation) cst).getAnnotation());
} else if (cst instanceof CstArray) {
CstArray.List list = ((CstArray) cst).getList();
int size = list.size();
for (int i = 0; i < size; i++) {
addContents(file, list.get(i));
}
} else {
file.internIfAppropriate(cst);
}
}
}