| /* |
| * Copyright (C) 2011 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.merge; |
| |
| import com.android.dx.dex.TableOfContents; |
| import com.android.dx.io.Annotation; |
| import com.android.dx.io.ClassDef; |
| import com.android.dx.io.DexBuffer; |
| import com.android.dx.io.EncodedValue; |
| import com.android.dx.io.EncodedValueReader; |
| import com.android.dx.io.FieldId; |
| import com.android.dx.io.MethodId; |
| import com.android.dx.io.ProtoId; |
| import com.android.dx.util.ByteArrayAnnotatedOutput; |
| import com.android.dx.util.ByteInput; |
| import com.android.dx.util.ByteOutput; |
| import com.android.dx.util.Leb128Utils; |
| import com.android.dx.util.Unsigned; |
| import java.util.HashMap; |
| |
| /** |
| * Maps the index offsets from one dex file to those in another. For example, if |
| * you have string #5 in the old dex file, its position in the new dex file is |
| * {@code strings[5]}. |
| */ |
| public final class IndexMap { |
| private final DexBuffer target; |
| public final int[] stringIds; |
| public final short[] typeIds; |
| public final short[] protoIds; |
| public final short[] fieldIds; |
| public final short[] methodIds; |
| private final HashMap<Integer, Integer> typeListOffsets; |
| private final HashMap<Integer, Integer> annotationOffsets; |
| private final HashMap<Integer, Integer> annotationSetOffsets; |
| private final HashMap<Integer, Integer> annotationDirectoryOffsets; |
| private final HashMap<Integer, Integer> staticValuesOffsets; |
| |
| public IndexMap(DexBuffer target, TableOfContents tableOfContents) { |
| this.target = target; |
| this.stringIds = new int[tableOfContents.stringIds.size]; |
| this.typeIds = new short[tableOfContents.typeIds.size]; |
| this.protoIds = new short[tableOfContents.protoIds.size]; |
| this.fieldIds = new short[tableOfContents.fieldIds.size]; |
| this.methodIds = new short[tableOfContents.methodIds.size]; |
| this.typeListOffsets = new HashMap<Integer, Integer>(); |
| this.annotationOffsets = new HashMap<Integer, Integer>(); |
| this.annotationSetOffsets = new HashMap<Integer, Integer>(); |
| this.annotationDirectoryOffsets = new HashMap<Integer, Integer>(); |
| this.staticValuesOffsets = new HashMap<Integer, Integer>(); |
| |
| /* |
| * A type list, annotation set, annotation directory, or static value at |
| * offset 0 is always empty. Always map offset 0 to 0. |
| */ |
| this.typeListOffsets.put(0, 0); |
| this.annotationSetOffsets.put(0, 0); |
| this.annotationDirectoryOffsets.put(0, 0); |
| this.staticValuesOffsets.put(0, 0); |
| } |
| |
| public void putTypeListOffset(int oldOffset, int newOffset) { |
| if (oldOffset <= 0 || newOffset <= 0) { |
| throw new IllegalArgumentException(); |
| } |
| typeListOffsets.put(oldOffset, newOffset); |
| } |
| |
| public void putAnnotationOffset(int oldOffset, int newOffset) { |
| if (oldOffset <= 0 || newOffset <= 0) { |
| throw new IllegalArgumentException(); |
| } |
| annotationOffsets.put(oldOffset, newOffset); |
| } |
| |
| public void putAnnotationSetOffset(int oldOffset, int newOffset) { |
| if (oldOffset <= 0 || newOffset <= 0) { |
| throw new IllegalArgumentException(); |
| } |
| annotationSetOffsets.put(oldOffset, newOffset); |
| } |
| |
| public void putAnnotationDirectoryOffset(int oldOffset, int newOffset) { |
| if (oldOffset <= 0 || newOffset <= 0) { |
| throw new IllegalArgumentException(); |
| } |
| annotationDirectoryOffsets.put(oldOffset, newOffset); |
| } |
| |
| public void putStaticValuesOffset(int oldOffset, int newOffset) { |
| if (oldOffset <= 0 || newOffset <= 0) { |
| throw new IllegalArgumentException(); |
| } |
| staticValuesOffsets.put(oldOffset, newOffset); |
| } |
| |
| public int adjustString(int stringIndex) { |
| return stringIndex == ClassDef.NO_INDEX ? ClassDef.NO_INDEX : stringIds[stringIndex]; |
| } |
| |
| public int adjustType(int typeIndex) { |
| return (typeIndex == ClassDef.NO_INDEX) ? ClassDef.NO_INDEX : (typeIds[typeIndex] & 0xffff); |
| } |
| |
| public TypeList adjustTypeList(TypeList typeList) { |
| if (typeList == TypeList.EMPTY) { |
| return typeList; |
| } |
| short[] types = typeList.getTypes().clone(); |
| for (int i = 0; i < types.length; i++) { |
| types[i] = (short) adjustType(types[i]); |
| } |
| return new TypeList(target, types); |
| } |
| |
| public int adjustProto(int protoIndex) { |
| return protoIds[protoIndex] & 0xffff; |
| } |
| |
| public int adjustField(int fieldIndex) { |
| return fieldIds[fieldIndex] & 0xffff; |
| } |
| |
| public int adjustMethod(int methodIndex) { |
| return methodIds[methodIndex] & 0xffff; |
| } |
| |
| public int adjustTypeListOffset(int typeListOffset) { |
| return typeListOffsets.get(typeListOffset); |
| } |
| |
| public int adjustAnnotation(int annotationOffset) { |
| return annotationOffsets.get(annotationOffset); |
| } |
| |
| public int adjustAnnotationSet(int annotationSetOffset) { |
| return annotationSetOffsets.get(annotationSetOffset); |
| } |
| |
| public int adjustAnnotationDirectory(int annotationDirectoryOffset) { |
| return annotationDirectoryOffsets.get(annotationDirectoryOffset); |
| } |
| |
| public int adjustStaticValues(int staticValuesOffset) { |
| return staticValuesOffsets.get(staticValuesOffset); |
| } |
| |
| public MethodId adjust(MethodId methodId) { |
| return new MethodId(target, |
| adjustType(methodId.getDeclaringClassIndex()), |
| adjustProto(methodId.getProtoIndex()), |
| adjustString(methodId.getNameIndex())); |
| } |
| |
| public FieldId adjust(FieldId fieldId) { |
| return new FieldId(target, |
| adjustType(fieldId.getDeclaringClassIndex()), |
| adjustType(fieldId.getTypeIndex()), |
| adjustString(fieldId.getNameIndex())); |
| |
| } |
| |
| public ProtoId adjust(ProtoId protoId) { |
| return new ProtoId(target, |
| adjustString(protoId.getShortyIndex()), |
| adjustType(protoId.getReturnTypeIndex()), |
| adjustTypeListOffset(protoId.getParametersOffset())); |
| } |
| |
| public ClassDef adjust(ClassDef classDef) { |
| return new ClassDef(target, classDef.getOffset(), adjustType(classDef.getTypeIndex()), |
| classDef.getAccessFlags(), adjustType(classDef.getSupertypeIndex()), |
| adjustTypeListOffset(classDef.getInterfacesOffset()), classDef.getSourceFileIndex(), |
| classDef.getAnnotationsOffset(), classDef.getClassDataOffset(), |
| classDef.getStaticValuesOffset()); |
| } |
| |
| public SortableType adjust(SortableType sortableType) { |
| return new SortableType(sortableType.getBuffer(), adjust(sortableType.getClassDef())); |
| } |
| |
| public EncodedValue adjustEncodedValue(EncodedValue encodedValue) { |
| ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); |
| new EncodedValueTransformer(encodedValue, out).readValue(); |
| return new EncodedValue(out.toByteArray()); |
| } |
| |
| public EncodedValue adjustEncodedArray(EncodedValue encodedArray) { |
| ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); |
| new EncodedValueTransformer(encodedArray, out).readArray(); |
| return new EncodedValue(out.toByteArray()); |
| } |
| |
| public Annotation adjust(Annotation annotation) { |
| int[] names = annotation.getNames().clone(); |
| EncodedValue[] values = annotation.getValues().clone(); |
| for (int i = 0; i < names.length; i++) { |
| names[i] = adjustString(names[i]); |
| values[i] = adjustEncodedValue(values[i]); |
| } |
| return new Annotation(target, annotation.getVisibility(), |
| adjustType(annotation.getTypeIndex()), names, values); |
| } |
| |
| /** |
| * Adjust an encoded value or array. |
| */ |
| private final class EncodedValueTransformer extends EncodedValueReader { |
| private final ByteOutput out; |
| |
| public EncodedValueTransformer(EncodedValue encodedValue, ByteOutput out) { |
| super(encodedValue); |
| this.out = out; |
| } |
| |
| protected void visitArray(int size) { |
| Leb128Utils.writeUnsignedLeb128(out, size); |
| } |
| |
| protected void visitAnnotation(int typeIndex, int size) { |
| Leb128Utils.writeUnsignedLeb128(out, adjustType(typeIndex)); |
| Leb128Utils.writeUnsignedLeb128(out, size); |
| } |
| |
| protected void visitAnnotationName(int index) { |
| Leb128Utils.writeUnsignedLeb128(out, adjustString(index)); |
| } |
| |
| protected void visitPrimitive(int argAndType, int type, int arg, int size) { |
| out.writeByte(argAndType); |
| copyBytes(in, out, size); |
| } |
| |
| protected void visitString(int type, int index) { |
| writeTypeAndSizeAndIndex(type, adjustString(index)); |
| } |
| |
| protected void visitType(int type, int index) { |
| writeTypeAndSizeAndIndex(type, adjustType(index)); |
| } |
| |
| protected void visitField(int type, int index) { |
| writeTypeAndSizeAndIndex(type, adjustField(index)); |
| } |
| |
| protected void visitMethod(int type, int index) { |
| writeTypeAndSizeAndIndex(type, adjustMethod(index)); |
| } |
| |
| protected void visitArrayValue(int argAndType) { |
| out.writeByte(argAndType); |
| } |
| |
| protected void visitAnnotationValue(int argAndType) { |
| out.writeByte(argAndType); |
| } |
| |
| protected void visitEncodedBoolean(int argAndType) { |
| out.writeByte(argAndType); |
| } |
| |
| protected void visitEncodedNull(int argAndType) { |
| out.writeByte(argAndType); |
| } |
| |
| private void writeTypeAndSizeAndIndex(int type, int index) { |
| int byteCount; |
| if (Unsigned.compare(index, 0xff) <= 0) { |
| byteCount = 1; |
| } else if (Unsigned.compare(index, 0xffff) <= 0) { |
| byteCount = 2; |
| } else if (Unsigned.compare(index, 0xffffff) <= 0) { |
| byteCount = 3; |
| } else { |
| byteCount = 4; |
| } |
| int argAndType = ((byteCount - 1) << 5) | type; |
| out.writeByte(argAndType); |
| |
| for (int i = 0; i < byteCount; i++) { |
| out.writeByte(index & 0xff); |
| index >>>= 8; |
| } |
| } |
| |
| private void copyBytes(ByteInput in, ByteOutput out, int size) { |
| for (int i = 0; i < size; i++) { |
| out.writeByte(in.readByte()); |
| } |
| } |
| } |
| } |