diff --git a/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java b/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java index d3906d65..1de9e4c8 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java @@ -98,5 +98,9 @@ public final class DexFileFactory { return new DexBackedDexFile(dexBuf); } + public static void writeDexFile(String path, DexFile dexFile) throws IOException { + org.jf.dexlib2.writer.DexFile.writeTo(path, dexFile); + } + private DexFileFactory() {} } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/base/BaseAnnotation.java b/dexlib2/src/main/java/org/jf/dexlib2/base/BaseAnnotation.java index 2c098830..b03d70a5 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/base/BaseAnnotation.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/base/BaseAnnotation.java @@ -65,7 +65,7 @@ public abstract class BaseAnnotation implements Annotation { return CollectionUtils.compareAsSet(getElements(), o.getElements()); } - public static final Comparator COMPARE_BY_TYPE = new Comparator() { + public static final Comparator BY_TYPE = new Comparator() { @Override public int compare(Annotation annotation1, Annotation annotation2) { return annotation1.getType().compareTo(annotation2.getType()); diff --git a/dexlib2/src/main/java/org/jf/dexlib2/base/BaseAnnotationElement.java b/dexlib2/src/main/java/org/jf/dexlib2/base/BaseAnnotationElement.java index edfd451b..92566cbc 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/base/BaseAnnotationElement.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/base/BaseAnnotationElement.java @@ -60,7 +60,7 @@ public abstract class BaseAnnotationElement implements AnnotationElement { return getValue().compareTo(o.getValue()); } - public static final Comparator COMPARE_BY_NAME = new Comparator() { + public static final Comparator BY_NAME = new Comparator() { @Override public int compare(@Nonnull AnnotationElement element1, @Nonnull AnnotationElement element2) { return element1.getName().compareTo(element2.getName()); diff --git a/dexlib2/src/main/java/org/jf/dexlib2/immutable/value/ImmutableEncodedValueFactory.java b/dexlib2/src/main/java/org/jf/dexlib2/immutable/value/ImmutableEncodedValueFactory.java index e0cfd5a2..34734297 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/immutable/value/ImmutableEncodedValueFactory.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/immutable/value/ImmutableEncodedValueFactory.java @@ -35,6 +35,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import org.jf.dexlib2.ValueType; import org.jf.dexlib2.iface.value.*; +import org.jf.util.ExceptionWithContext; import org.jf.util.ImmutableConverter; import javax.annotation.Nonnull; @@ -82,6 +83,33 @@ public class ImmutableEncodedValueFactory { } } + @Nonnull + public static EncodedValue defaultValueForType(String type) { + switch (type.charAt(0)) { + case 'Z': + return new ImmutableBooleanEncodedValue(false); + case 'B': + return new ImmutableByteEncodedValue((byte)0); + case 'S': + return new ImmutableShortEncodedValue((short)0); + case 'C': + return new ImmutableCharEncodedValue((char)0); + case 'I': + return new ImmutableIntEncodedValue(0); + case 'J': + return new ImmutableLongEncodedValue(0); + case 'F': + return new ImmutableFloatEncodedValue(0); + case 'D': + return new ImmutableDoubleEncodedValue(0); + case 'L': + case '[': + return ImmutableNullEncodedValue.INSTANCE; + default: + throw new ExceptionWithContext("Unrecognized type: %s", type); + } + } + @Nullable public static ImmutableEncodedValue ofNullable(@Nullable EncodedValue encodedValue) { if (encodedValue == null) { diff --git a/dexlib2/src/main/java/org/jf/dexlib2/util/FieldUtil.java b/dexlib2/src/main/java/org/jf/dexlib2/util/FieldUtil.java new file mode 100644 index 00000000..e00da0e8 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/util/FieldUtil.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.util; + +import org.jf.dexlib2.AccessFlags; +import org.jf.dexlib2.iface.Field; + +import javax.annotation.Nonnull; + +public final class FieldUtil { + public static boolean isStatic(@Nonnull Field field) { + return AccessFlags.STATIC.isSet(field.getAccessFlags()); + } + + private FieldUtil() {} +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationDirectoryPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationDirectoryPool.java new file mode 100644 index 00000000..b24ca3db --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationDirectoryPool.java @@ -0,0 +1,257 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.base.Predicate; +import com.google.common.collect.*; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.Field; +import org.jf.dexlib2.iface.Method; +import org.jf.dexlib2.iface.MethodParameter; +import org.jf.util.CollectionUtils; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.*; + +public class AnnotationDirectoryPool { + @Nonnull private final Map internedAnnotationDirectoryItems = Maps.newHashMap(); + @Nonnull private final Map nonInternedAnnotationDirectoryOffsetMap = Maps.newHashMap(); + @Nonnull private final List nonInternedAnnotationDirectoryItems = Lists.newArrayList(); + @Nonnull private final DexFile dexFile; + + public AnnotationDirectoryPool(@Nonnull DexFile dexFile) { + this.dexFile = dexFile; + } + + public void intern(@Nonnull ClassDef classDef) { + Key key = new Key(classDef); + + if (key.hasNonClassAnnotations()) { + nonInternedAnnotationDirectoryItems.add(key); + } else if (key.hasClassAnnotations()) { + Integer prev = internedAnnotationDirectoryItems.put(key, 0); + if (prev != null) { + // we don't need to re-intern the contents + return; + } + } else { + // it's empty. nothing to do. + return; + } + + dexFile.annotationSetPool.intern(classDef.getAnnotations()); + for (Field field: key.getFieldsWithAnnotations()) { + dexFile.annotationSetPool.intern(field.getAnnotations()); + } + + for (Method method: key.getMethodsWithAnnotations()) { + dexFile.annotationSetPool.intern(method.getAnnotations()); + } + + for (Method method: key.getMethodsWithParameterAnnotations()) { + dexFile.annotationSetRefPool.intern(method); + } + } + + public int getOffset(@Nonnull ClassDef classDef) { + Integer offset = nonInternedAnnotationDirectoryOffsetMap.get(classDef.getType()); + if (offset == null) { + Key key = new Key(classDef); + if (!key.hasNonClassAnnotations()) { + offset = internedAnnotationDirectoryItems.get(key); + } + } + if (offset == null) { + throw new ExceptionWithContext("Annotation directory not found for class %s.", classDef.getType()); + } + return offset; + } + + public void write(@Nonnull DexWriter writer) throws IOException { + // we'll write out the interned items first + List directoryItems = Lists.newArrayList(internedAnnotationDirectoryItems.keySet()); + Collections.sort(directoryItems); + for (Key key: directoryItems) { + writer.align(); + internedAnnotationDirectoryItems.put(key, writer.getPosition()); + writer.writeInt(dexFile.annotationSetPool.getOffset(key.classDef.getAnnotations())); + writer.writeInt(0); + writer.writeInt(0); + writer.writeInt(0); + } + + // now, write out the non-internable items + directoryItems = nonInternedAnnotationDirectoryItems; + Collections.sort(directoryItems); + for (Key key: directoryItems) { + writer.align(); + nonInternedAnnotationDirectoryOffsetMap.put(key.classDef.getType(), writer.getPosition()); + writer.writeInt(dexFile.annotationSetPool.getOffset(key.classDef.getAnnotations())); + writer.writeInt(key.fieldAnnotationCount); + writer.writeInt(key.methodAnnotationCount); + writer.writeInt(key.parameterAnnotationCount); + + Iterable fieldsWithAnnotations = null; + if (CollectionUtils.isNaturalSortedSet(key.classDef.getFields())) { + fieldsWithAnnotations = key.getFieldsWithAnnotations(); + } else { + fieldsWithAnnotations = Lists.newArrayList(key.getFieldsWithAnnotations()); + Collections.sort((List)fieldsWithAnnotations); + } + for (Field field: fieldsWithAnnotations) { + writer.writeInt(dexFile.fieldPool.getIndex(field)); + writer.writeInt(dexFile.annotationSetPool.getOffset(field.getAnnotations())); + } + + boolean sortMethods = CollectionUtils.isNaturalSortedSet(key.classDef.getMethods()); + Iterable methodsWithAnnotations = null; + if (sortMethods) { + methodsWithAnnotations = Lists.newArrayList(key.getMethodsWithAnnotations()); + Collections.sort((List)methodsWithAnnotations); + } else { + methodsWithAnnotations = key.getMethodsWithAnnotations(); + } + for (Method method: methodsWithAnnotations) { + writer.writeInt(dexFile.methodPool.getIndex(method)); + writer.writeInt(dexFile.annotationSetPool.getOffset(method.getAnnotations())); + } + + Iterable methodsWithParameterAnnotations = null; + if (sortMethods) { + methodsWithParameterAnnotations = Lists.newArrayList(key.getMethodsWithParameterAnnotations()); + Collections.sort((List)methodsWithParameterAnnotations); + } else { + methodsWithParameterAnnotations = key.getMethodsWithParameterAnnotations(); + } + for (Method method: methodsWithParameterAnnotations) { + writer.writeInt(dexFile.methodPool.getIndex(method)); + writer.writeInt(dexFile.annotationSetRefPool.getOffset(method)); + } + } + } + + private static final Predicate FIELD_HAS_ANNOTATION = new Predicate() { + @Override + public boolean apply(Field input) { return input.getAnnotations().size() > 0; } + }; + + private static final Predicate METHOD_HAS_ANNOTATION = new Predicate() { + @Override + public boolean apply(Method input) { return input.getAnnotations().size() > 0; } + }; + + private static final Predicate METHOD_HAS_PARAMETER_ANNOTATION = new Predicate() { + @Override + public boolean apply(Method input) { + for (MethodParameter parameter: input.getParameters()) { + if (parameter.getAnnotations().size() > 0) { + return true; + } + } + return false; + } + }; + + private static class Key implements Comparable { + @Nonnull private final ClassDef classDef; + private final int fieldAnnotationCount; + private final int methodAnnotationCount; + private final int parameterAnnotationCount; + + public Key(@Nonnull ClassDef classDef) { + this.classDef = classDef; + this.fieldAnnotationCount = Iterables.size(getFieldsWithAnnotations()); + this.methodAnnotationCount = Iterables.size(getMethodsWithAnnotations()); + this.parameterAnnotationCount = Iterables.size(getMethodsWithParameterAnnotations()); + } + + public int getFieldAnnotationCount() { return fieldAnnotationCount; } + public int getMethodAnnotationCount() { return methodAnnotationCount; } + public int getParameterAnnotationCount() { return parameterAnnotationCount; } + + @Nonnull + public Iterable getFieldsWithAnnotations() { + return FluentIterable.from(classDef.getFields()).filter(FIELD_HAS_ANNOTATION); + } + + @Nonnull + public Iterable getMethodsWithAnnotations() { + return FluentIterable.from(classDef.getMethods()).filter(METHOD_HAS_ANNOTATION); + } + + @Nonnull + public Iterable getMethodsWithParameterAnnotations() { + return FluentIterable.from(classDef.getMethods()).filter(METHOD_HAS_PARAMETER_ANNOTATION); + } + + public boolean hasClassAnnotations() { + return classDef.getAnnotations().size() > 0; + } + + public boolean hasNonClassAnnotations() { + return fieldAnnotationCount > 0 || + methodAnnotationCount > 0 || + parameterAnnotationCount > 0; + } + + @Override + public int hashCode() { + // hashCode is only used for internable items - those that only have class annotations. + return classDef.getAnnotations().hashCode(); + } + + @Override + public boolean equals(Object o) { + // equals is only used for internable items - those that only have class annotations + if (o instanceof Key) { + Key other = (Key)o; + if (classDef.getAnnotations().size() != other.classDef.getAnnotations().size()) { + return false; + } + return Iterables.elementsEqual(classDef.getAnnotations(), other.classDef.getAnnotations()); + } + return false; + } + + @Override + public int compareTo(Key o) { + // compareTo will only be called on keys of the same internability. An internable key will not be compared + // with a non-internable one. + if (hasClassAnnotations()) { + return classDef.getType().compareTo(o.classDef.getType()); + } + return CollectionUtils.compareAsSet(classDef.getAnnotations(), o.classDef.getAnnotations()); + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationPool.java new file mode 100644 index 00000000..f8be27e6 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationPool.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.jf.dexlib2.base.BaseAnnotationElement; +import org.jf.dexlib2.iface.Annotation; +import org.jf.dexlib2.iface.AnnotationElement; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; + +public class AnnotationPool { + @Nonnull private final Map internedAnnotations = Maps.newHashMap(); + @Nonnull private final DexFile dexFile; + + public AnnotationPool(@Nonnull DexFile dexFile) { + this.dexFile = dexFile; + } + + public void intern(@Nonnull Annotation annotation) { + Integer prev = internedAnnotations.put(annotation, 0); + if (prev == null) { + dexFile.typePool.intern(annotation.getType()); + for (AnnotationElement element: annotation.getElements()) { + dexFile.stringPool.intern(element.getName()); + dexFile.internEncodedValue(element.getValue()); + } + } + } + + public int getOffset(@Nonnull Annotation annotation) { + Integer offset = internedAnnotations.get(annotation); + if (offset == null) { + throw new ExceptionWithContext("Annotation not found."); + } + return offset; + } + + public void write(@Nonnull DexWriter writer) throws IOException { + List annotations = Lists.newArrayList(internedAnnotations.keySet()); + Collections.sort(annotations); + + for (Annotation annotation: annotations) { + internedAnnotations.put(annotation, writer.getPosition()); + writer.writeUbyte(annotation.getVisibility()); + writer.writeUleb128(dexFile.typePool.getIndex(annotation.getType())); + writer.writeUleb128(annotation.getElements().size()); + + SortedSet sortedElements = + ImmutableSortedSet.copyOf(BaseAnnotationElement.BY_NAME, annotation.getElements()); + for (AnnotationElement element: sortedElements) { + writer.writeUleb128(dexFile.stringPool.getIndex(element.getName())); + dexFile.writeEncodedValue(writer, element.getValue()); + } + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationSetPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationSetPool.java new file mode 100644 index 00000000..63f70f17 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationSetPool.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; +import org.jf.dexlib2.base.BaseAnnotation; +import org.jf.dexlib2.iface.Annotation; +import org.jf.util.CollectionUtils; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.*; + +public class AnnotationSetPool { + @Nonnull private final Map, Integer> internedAnnotationSetItems = Maps.newHashMap(); + @Nonnull private final DexFile dexFile; + + public AnnotationSetPool(@Nonnull DexFile dexFile) { + this.dexFile = dexFile; + } + + public void intern(@Nonnull Set annotationSet) { + if (annotationSet.size() > 0) { + Integer prev = internedAnnotationSetItems.put(annotationSet, 0); + if (prev == null) { + for (Annotation annotation: annotationSet) { + dexFile.annotationPool.intern(annotation); + } + } + } + } + + public int getOffset(@Nonnull Set annotationSet) { + if (annotationSet.size() == 0) { + return 0; + } + Integer offset = internedAnnotationSetItems.get(annotationSet); + if (offset == null) { + throw new ExceptionWithContext("Annotation set not found."); + } + return offset; + } + + public void write(@Nonnull DexWriter writer) throws IOException { + List> annotationSets = + Lists.newArrayList(internedAnnotationSetItems.keySet()); + Collections.sort(annotationSets, CollectionUtils.listComparator(Ordering.natural())); + + for (Set annotationSet: annotationSets) { + SortedSet sortedAnnotationSet = ImmutableSortedSet.copyOf(BaseAnnotation.BY_TYPE, + annotationSet); + writer.align(); + internedAnnotationSetItems.put(annotationSet, writer.getPosition()); + writer.writeInt(annotationSet.size()); + for (Annotation annotation: sortedAnnotationSet) { + writer.writeInt(dexFile.annotationPool.getOffset(annotation)); + } + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationSetRefPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationSetRefPool.java new file mode 100644 index 00000000..223e0f65 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/AnnotationSetRefPool.java @@ -0,0 +1,162 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; +import com.google.common.primitives.Ints; +import org.jf.dexlib2.iface.Annotation; +import org.jf.dexlib2.iface.Method; +import org.jf.dexlib2.iface.MethodParameter; +import org.jf.util.CollectionUtils; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.*; + +public class AnnotationSetRefPool { + @Nonnull private final Map internedAnnotationSetRefItems = Maps.newHashMap(); + @Nonnull private final DexFile dexFile; + + public AnnotationSetRefPool(@Nonnull DexFile dexFile) { + this.dexFile = dexFile; + } + + public void intern(@Nonnull Method method) { + Key annotationSetRefKey = new Key(method); + Integer prev = internedAnnotationSetRefItems.put(annotationSetRefKey, 0); + if (prev == null) { + for (Set annotationSet: annotationSetRefKey.getAnnotationSets()) { + dexFile.annotationSetPool.intern(annotationSet); + } + } + } + + public int getOffset(@Nonnull Method method) { + Key annotationSetRefKey = new Key(method); + Integer offset = internedAnnotationSetRefItems.put(annotationSetRefKey, 0); + if (offset == null) { + throw new ExceptionWithContext("Annotation set ref not found."); + } + return offset; + } + + public void write(@Nonnull DexWriter writer) throws IOException { + List annotationSetRefs = + Lists.newArrayList(internedAnnotationSetRefItems.keySet()); + Collections.sort(annotationSetRefs); + + for (Key key: annotationSetRefs) { + writer.align(); + internedAnnotationSetRefItems.put(key, writer.getPosition()); + writer.writeInt(key.getAnnotationSetCount()); + for (Set annotationSet: key.getAnnotationSets()) { + writer.writeInt(dexFile.annotationSetPool.getOffset(annotationSet)); + } + } + } + + private static class Key implements Comparable { + @Nonnull private final Method method; + private final int size; + + public Key(@Nonnull Method method) { + this.method = method; + this.size = CollectionUtils.lastIndexOf(method.getParameters(), HAS_ANNOTATIONS) + 1; + } + + public int getAnnotationSetCount() { + return size; + } + + public Iterable> getAnnotationSets() { + return FluentIterable.from(method.getParameters()) + .limit(size) + .transform(PARAMETER_ANNOTATIONS); + } + + @Override + public int hashCode() { + int hashCode = 1; + for (Set annotationSet: getAnnotationSets()) { + hashCode = hashCode*31 + annotationSet.hashCode(); + } + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (o instanceof Key) { + Key other = (Key)o; + if (size != other.size) { + return false; + } + Iterator> otherAnnotationSets = getAnnotationSets().iterator(); + for (Set annotationSet: getAnnotationSets()) { + if (!annotationSet.equals(otherAnnotationSets)) { + return false; + } + } + return true; + } + return false; + } + + private static final Predicate HAS_ANNOTATIONS = new Predicate() { + @Override + public boolean apply(MethodParameter input) { + return input.getAnnotations().size() > 0; + } + }; + + private static final Function> PARAMETER_ANNOTATIONS = + new Function>() { + @Override + public Set apply(MethodParameter input) { + return input.getAnnotations(); + } + }; + + @Override + public int compareTo(Key o) { + int res = Ints.compare(size, o.size); + if (res != 0) return res; + return CollectionUtils.compareAsIterable(CollectionUtils.setComparator(Ordering.natural()), + getAnnotationSets(), o.getAnnotationSets()); + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/DexFile.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexFile.java new file mode 100644 index 00000000..e06dea20 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexFile.java @@ -0,0 +1,314 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.collect.ImmutableSortedSet; +import org.jf.dexlib2.DebugItemType; +import org.jf.dexlib2.ReferenceType; +import org.jf.dexlib2.ValueType; +import org.jf.dexlib2.iface.*; +import org.jf.dexlib2.iface.debug.DebugItem; +import org.jf.dexlib2.iface.debug.SetSourceFile; +import org.jf.dexlib2.iface.debug.StartLocal; +import org.jf.dexlib2.iface.instruction.Instruction; +import org.jf.dexlib2.iface.instruction.ReferenceInstruction; +import org.jf.dexlib2.iface.reference.*; +import org.jf.dexlib2.iface.value.*; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public class DexFile { + // package-private access for these + @Nonnull final StringPool stringPool = new StringPool(); + @Nonnull final TypePool typePool = new TypePool(this); + @Nonnull final FieldPool fieldPool = new FieldPool(this); + @Nonnull final ProtoPool protoPool = new ProtoPool(this); + @Nonnull final MethodPool methodPool = new MethodPool(this); + + @Nonnull final TypeListPool typeListPool = new TypeListPool(this); + @Nonnull final EncodedArrayPool encodedArrayPool = new EncodedArrayPool(this); + @Nonnull final AnnotationPool annotationPool = new AnnotationPool(this); + @Nonnull final AnnotationSetPool annotationSetPool = new AnnotationSetPool(this); + @Nonnull final AnnotationSetRefPool annotationSetRefPool = new AnnotationSetRefPool(this); + @Nonnull final AnnotationDirectoryPool annotationDirectoryPool = new AnnotationDirectoryPool(this); + + @Nonnull private final Set classes; + + private DexFile(Set classes) { + this.classes = classes; + + for (ClassDef classDef: classes) { + internClass(classDef); + } + } + + public void writeEncodedValue(@Nonnull DexWriter writer, @Nonnull EncodedValue encodedValue) throws IOException { + int valueType = encodedValue.getValueType(); + switch (valueType) { + case ValueType.ANNOTATION: + AnnotationEncodedValue annotationEncodedValue = (AnnotationEncodedValue)encodedValue; + Collection annotationElements = annotationEncodedValue.getElements(); + writer.writeUleb128(typePool.getIndex(annotationEncodedValue.getType())); + writer.writeUleb128(annotationElements.size()); + for (AnnotationElement element: annotationElements) { + writer.writeUleb128(stringPool.getIndex(element.getName())); + writeEncodedValue(writer, element.getValue()); + } + break; + case ValueType.ARRAY: + ArrayEncodedValue arrayEncodedValue = (ArrayEncodedValue)encodedValue; + Collection elements = arrayEncodedValue.getValue(); + writer.writeUleb128(elements.size()); + for (EncodedValue element: elements) { + writeEncodedValue(writer, element); + } + break; + case ValueType.BOOLEAN: + writer.writeEncodedValueHeader(valueType, (((BooleanEncodedValue)encodedValue).getValue()?1:0)); + break; + case ValueType.BYTE: + writer.writeEncodedInt(valueType, ((ByteEncodedValue)encodedValue).getValue()); + break; + case ValueType.CHAR: + writer.writeEncodedInt(valueType, ((CharEncodedValue)encodedValue).getValue()); + break; + case ValueType.DOUBLE: + writer.writeEncodedDouble(valueType, ((DoubleEncodedValue)encodedValue).getValue()); + break; + case ValueType.ENUM: + writer.writeEncodedUint(valueType, fieldPool.getIndex(((EnumEncodedValue)encodedValue).getValue())); + break; + case ValueType.FIELD: + writer.writeEncodedUint(valueType, fieldPool.getIndex(((FieldEncodedValue)encodedValue).getValue())); + break; + case ValueType.FLOAT: + writer.writeEncodedFloat(valueType, ((FloatEncodedValue)encodedValue).getValue()); + break; + case ValueType.INT: + writer.writeEncodedInt(valueType, ((IntEncodedValue)encodedValue).getValue()); + break; + case ValueType.LONG: + writer.writeEncodedLong(valueType, ((LongEncodedValue)encodedValue).getValue()); + break; + case ValueType.METHOD: + writer.writeEncodedUint(valueType, methodPool.getIndex(((MethodEncodedValue)encodedValue).getValue())); + break; + case ValueType.NULL: + writer.write(valueType); + break; + case ValueType.SHORT: + writer.writeEncodedInt(valueType, ((ShortEncodedValue)encodedValue).getValue()); + break; + case ValueType.STRING: + writer.writeEncodedUint(valueType, stringPool.getIndex(((StringEncodedValue)encodedValue).getValue())); + break; + case ValueType.TYPE: + writer.writeEncodedUint(valueType, typePool.getIndex(((TypeEncodedValue)encodedValue).getValue())); + break; + default: + throw new ExceptionWithContext("Unrecognized value type: %d", encodedValue.getValueType()); + } + } + + public void internEncodedValue(@Nonnull EncodedValue encodedValue) { + switch (encodedValue.getValueType()) { + case ValueType.ARRAY: + ArrayEncodedValue arrayEncodedValue = (ArrayEncodedValue)encodedValue; + for (EncodedValue value: arrayEncodedValue.getValue()) { + internEncodedValue(value); + } + return; + case ValueType.ANNOTATION: + AnnotationEncodedValue annotationEncodedValue = (AnnotationEncodedValue)encodedValue; + typePool.intern(annotationEncodedValue.getType()); + for(AnnotationElement annotationElement: annotationEncodedValue.getElements()) { + stringPool.intern(annotationElement.getName()); + internEncodedValue(annotationElement.getValue()); + } + return; + case ValueType.STRING: + StringEncodedValue stringEncodedValue = (StringEncodedValue)encodedValue; + stringPool.intern(stringEncodedValue.getValue()); + return; + case ValueType.TYPE: + TypeEncodedValue typeEncodedValue = (TypeEncodedValue)encodedValue; + typePool.intern(typeEncodedValue.getValue()); + return; + case ValueType.ENUM: + EnumEncodedValue enumEncodedValue = (EnumEncodedValue)encodedValue; + fieldPool.intern(enumEncodedValue.getValue()); + return; + case ValueType.FIELD: + FieldEncodedValue fieldEncodedValue = (FieldEncodedValue)encodedValue; + fieldPool.intern(fieldEncodedValue.getValue()); + return; + case ValueType.METHOD: + MethodEncodedValue methodEncodedValue = (MethodEncodedValue)encodedValue; + methodPool.intern(methodEncodedValue.getValue()); + return; + default: + // nothing to do + break; + } + } + + public void internFields(@Nonnull ClassDef classDef) { + for (Field field: classDef.getFields()) { + fieldPool.intern(field); + } + } + + public void internMethods(@Nonnull ClassDef classDef) { + for (Method method: classDef.getMethods()) { + methodPool.intern(method); + for (MethodParameter param: method.getParameters()) { + stringPool.internNullable(param.getName()); + } + internMethodImplementation(method.getImplementation()); + } + } + + public void internMethodImplementation(@Nullable MethodImplementation methodImplementation) { + if (methodImplementation != null) { + internDebugItems(methodImplementation.getDebugItems()); + internTryBlocks(methodImplementation.getTryBlocks()); + internInstructions(methodImplementation.getInstructions()); + } + } + + public void internDebugItems(Iterable debugItems) { + // TODO: debug_info_items should technically be internable... + for (DebugItem debugItem: debugItems) { + switch (debugItem.getDebugItemType()) { + case DebugItemType.START_LOCAL: + StartLocal startLocal = (StartLocal)debugItem; + stringPool.internNullable(startLocal.getName()); + typePool.internNullable(startLocal.getType()); + stringPool.internNullable(startLocal.getSignature()); + break; + case DebugItemType.SET_SOURCE_FILE: + stringPool.internNullable(((SetSourceFile) debugItem).getSourceFile()); + break; + } + } + } + + public void internTryBlocks(List tryBlocks) { + for (TryBlock tryBlock: tryBlocks) { + for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) { + typePool.internNullable(handler.getExceptionType()); + } + } + } + + public void internInstructions(Iterable instructions) { + for (Instruction instruction: instructions) { + if (instruction instanceof ReferenceInstruction) { + Reference reference = ((ReferenceInstruction)instruction).getReference(); + switch (instruction.getOpcode().referenceType) { + case ReferenceType.STRING: + stringPool.intern((StringReference) reference); + break; + case ReferenceType.TYPE: + typePool.intern((TypeReference)reference); + break; + case ReferenceType.FIELD: + fieldPool.intern((FieldReference) reference); + break; + case ReferenceType.METHOD: + methodPool.intern((MethodReference)reference); + break; + default: + throw new ExceptionWithContext("Unrecognized reference type: %d", + instruction.getOpcode().referenceType); + } + } + } + } + + public void internClass(@Nonnull ClassDef classDef) { + typePool.intern(classDef.getType()); + typePool.internNullable(classDef.getSuperclass()); + typeListPool.intern(ImmutableSortedSet.copyOf(classDef.getInterfaces())); + stringPool.internNullable(classDef.getSourceFile()); + encodedArrayPool.intern(classDef); + annotationDirectoryPool.intern(classDef); + internFields(classDef); + internMethods(classDef); + } + + private void writeTo(@Nonnull String path) throws IOException { + RandomAccessFile raf = new RandomAccessFile(path, "rw"); + try { + int dataOffset = stringPool.getIndexedSectionSize() + typePool.getIndexedSectionSize() + + fieldPool.getIndexedSectionSize() + protoPool.getIndexedSectionSize() + + methodPool.getIndexedSectionSize(); + + DexWriter indexWriter = outputAt(raf, 0); + DexWriter offsetWriter = outputAt(raf, dataOffset); + try { + stringPool.write(indexWriter, offsetWriter); + typePool.write(indexWriter); + fieldPool.write(indexWriter); + typeListPool.write(offsetWriter); + protoPool.write(indexWriter); + methodPool.write(indexWriter); + encodedArrayPool.write(offsetWriter); + annotationPool.write(offsetWriter); + annotationSetPool.write(offsetWriter); + annotationSetRefPool.write(offsetWriter); + annotationDirectoryPool.write(offsetWriter); + } finally { + indexWriter.close(); + offsetWriter.close(); + } + } finally { + raf.close(); + } + } + + private static DexWriter outputAt(RandomAccessFile raf, int filePosition) throws IOException { + return new DexWriter(raf.getChannel(), filePosition); + } + + public static void writeTo(@Nonnull String path, @Nonnull org.jf.dexlib2.iface.DexFile input) throws IOException { + DexFile dexFile = new DexFile(input.getClasses()); + dexFile.writeTo(path); + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/DexItemType.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexItemType.java new file mode 100644 index 00000000..853ff3bd --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexItemType.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +public class DexItemType { + public static final int HEADER_ITEM = 0x0000; + public static final int STRING_ID_ITEM = 0x0001; + public static final int TYPE_ID_ITEM = 0x0002; + public static final int PROTO_ID_ITEM = 0x0003; + public static final int FIELD_ID_ITEM = 0x0004; + public static final int METHOD_ID_ITEM = 0x0005; + public static final int CLASS_DEF_ITEM = 0x0006; + public static final int MAP_LIST = 0x1000; + public static final int TYPE_LIST = 0x1001; + public static final int ANNOTATION_SET_REF_LIST = 0x1002; + public static final int ANNOTATION_SET_ITEM = 0x1003; + public static final int CLASS_DATA_ITEM = 0x2000; + public static final int CODE_ITEM = 0x2001; + public static final int STRING_DATA_ITEM = 0x2002; + public static final int DEBUG_INFO_ITEM = 0x2003; + public static final int ANNOTATION_ITEM = 0x2004; + public static final int ENCODED_ARRAY_ITEM = 0x2005; + public static final int ANNOTATION_DIRECTORY_ITEM = 0x2006; +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java new file mode 100644 index 00000000..0a62c367 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java @@ -0,0 +1,342 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.base.Preconditions; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; + +public class DexWriter extends OutputStream { + private static final int MAP_SIZE = 1024*1024; + private static final int BUF_SIZE = 256*1024; + + @Nonnull private final FileChannel channel; + private MappedByteBuffer byteBuffer; + + /** The position in the file at which byteBuffer starts. */ + private int mappedFilePosition; + + private byte[] buf = new byte[BUF_SIZE]; + /** The index within buf to write to */ + private int bufPosition; + + /** + * A temporary buffer that can be used for larger writes. Can be replaced with a larger buffer if needed. + * Must be at least 8 bytes + */ + private byte[] tempBuf = new byte[8]; + + /** A buffer of 0s we used for writing alignment values */ + private byte[] zeroBuf = new byte[3]; + + public DexWriter(FileChannel channel, int position) throws IOException { + this.channel = channel; + this.mappedFilePosition = position; + + byteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, position, MAP_SIZE); + } + + @Override + public void write(int b) throws IOException { + if (bufPosition >= BUF_SIZE) { + flushBuffer(); + } + buf[bufPosition++] = (byte)b; + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + int toWrite = len; + + if (bufPosition == BUF_SIZE) { + flushBuffer(); + } + int remainingBuffer = BUF_SIZE - bufPosition; + if (toWrite >= remainingBuffer) { + // fill up and write out the current buffer + System.arraycopy(b, 0, buf, bufPosition, remainingBuffer); + bufPosition += remainingBuffer; + toWrite -= remainingBuffer; + flushBuffer(); + + // skip the intermediate buffer while we have a full buffer's worth + while (toWrite >= BUF_SIZE) { + writeBufferToMap(b, len - toWrite, BUF_SIZE); + toWrite -= BUF_SIZE; + } + } + // write out the final chunk, if any + if (toWrite > 0) { + System.arraycopy(b, len-toWrite, buf, bufPosition, len); + bufPosition += len; + } + } + + public void writeInt(int value) throws IOException { + write(value); + write(value >> 8); + write(value >> 16); + write(value >> 24); + } + + public void writeShort(int value) throws IOException { + if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) { + throw new ExceptionWithContext("Short value out of range: %d", value); + } + write(value); + write(value >> 8); + } + + public void writeUshort(int value) throws IOException { + if (value < 0 || value > 0xFFFF) { + throw new ExceptionWithContext("Unsigned short value out of range: %d", value); + } + write(value); + write(value >> 8); + } + + public void writeUbyte(int value) throws IOException { + if (value < 0 || value > 0xFF) { + throw new ExceptionWithContext("Unsigned byte value out of range: %d", value); + } + write(value); + } + + public void writeUleb128(int value) throws IOException { + while (value > 0x7f) { + write((value & 0x7f) | 0x80); + value >>>= 7; + } + write(value); + } + + /* public static byte[] encodeSignedIntegralValue(long value) { + int requiredBytes = getRequiredBytesForSignedIntegralValue(value); + + byte[] bytes = new byte[requiredBytes]; + + for (int i = 0; i < requiredBytes; i++) { + bytes[i] = (byte) value; + value >>= 8; + } + return bytes; + }*/ + + + /* + public static long decodeUnsignedIntegralValue(byte[] bytes) { + long value = 0; + for (int i = 0; i < bytes.length; i++) { + value |= (((long)(bytes[i] & 0xFF)) << i * 8); + } + return value; + } + */ + + /* + public static byte[] encodeUnsignedIntegralValue(long value) { + int requiredBytes = getRequiredBytesForUnsignedIntegralValue(value); + + byte[] bytes = new byte[requiredBytes]; + + for (int i = 0; i < requiredBytes; i++) { + bytes[i] = (byte) value; + value >>= 8; + } + return bytes; + } + */ + + public void writeEncodedValueHeader(int valueType, int valueArg) throws IOException { + write(valueType | (valueArg << 5)); + } + + public void writeEncodedInt(int valueType, int value) throws IOException { + int index = 0; + if (value >= 0) { + while (value > 0x7f) { + tempBuf[index++] = (byte)value; + value >>= 8; + } + } else { + while (value < -0x80) { + tempBuf[index++] = (byte)value; + value >>= 8; + } + } + tempBuf[index++] = (byte)value; + writeEncodedValueHeader(valueType, index); + write(tempBuf, 0, index); + } + + public void writeEncodedLong(int valueType, long value) throws IOException { + int index = 0; + if (value >= 0) { + while (value > 0x7f) { + tempBuf[index++] = (byte)value; + value >>= 8; + } + } else { + while (value < -0x80) { + tempBuf[index++] = (byte)value; + value >>= 8; + } + } + tempBuf[index++] = (byte)value; + writeEncodedValueHeader(valueType, index); + write(tempBuf, 0, index); + } + + public void writeEncodedUint(int valueType, int value) throws IOException { + int index = 0; + do { + tempBuf[index++] = (byte)value; + value >>= 8; + } while (value != 0); + writeEncodedValueHeader(valueType, index); + write(tempBuf, 0, index); + } + + public void writeEncodedFloat(int valueType, float value) throws IOException { + int intValue = Float.floatToRawIntBits(value); + + int index = 3; + do { + buf[index--] = (byte)((intValue & 0xFF000000) >>> 24); + intValue <<= 8; + } while (intValue != 0); + writeEncodedValueHeader(valueType, 4-index); + write(buf, index+1, 4-index); + } + + public void writeEncodedDouble(int valueType, double value) throws IOException { + long longValue = Double.doubleToRawLongBits(value); + + int index = 7; + do { + buf[index--] = (byte)((longValue & 0xFF00000000000000L) >>> 56); + longValue <<= 8; + } while (longValue != 0); + writeEncodedValueHeader(valueType, 7-index); + write(buf, index+1, 7-index); + } + + public void writeString(String string) throws IOException { + int len = string.length(); + + // make sure we have enough room in the temporary buffer + if (tempBuf.length <= string.length()*3) { + tempBuf = new byte[string.length()*3]; + } + + final byte[] buf = tempBuf; + + int bufPos = 0; + for (int i = 0; i < len; i++) { + char c = string.charAt(i); + if ((c != 0) && (c < 0x80)) { + buf[bufPos++] = (byte)c; + } else if (c < 0x800) { + buf[bufPos++] = (byte)(((c >> 6) & 0x1f) | 0xc0); + buf[bufPos++] = (byte)((c & 0x3f) | 0x80); + } else { + buf[bufPos++] = (byte)(((c >> 12) & 0x0f) | 0xe0); + buf[bufPos++] = (byte)(((c >> 6) & 0x3f) | 0x80); + buf[bufPos++] = (byte)((c & 0x3f) | 0x80); + } + } + write(buf, 0, bufPos); + } + + public void align() throws IOException { + int zeros = (-getPosition()) & 3; + if (zeros > 0) { + write(zeroBuf, 0, zeros); + } + } + + @Override + public void flush() throws IOException { + if (bufPosition > 0) { + writeBufferToMap(buf, 0, bufPosition); + bufPosition = 0; + } + byteBuffer.force(); + mappedFilePosition += byteBuffer.position(); + channel.position(mappedFilePosition + MAP_SIZE); + channel.write(ByteBuffer.wrap(new byte[]{0})); + byteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, mappedFilePosition, MAP_SIZE); + } + + @Override + public void close() throws IOException { + if (bufPosition > 0) { + writeBufferToMap(buf, 0, bufPosition); + bufPosition = 0; + } + byteBuffer.force(); + byteBuffer = null; + buf = null; + tempBuf = null; + } + + private void flushBuffer() throws IOException { + Preconditions.checkState(bufPosition == BUF_SIZE); + writeBufferToMap(buf, 0, BUF_SIZE); + bufPosition = 0; + } + + private void writeBufferToMap(byte[] buf, int bufOffset, int len) throws IOException { + // we always write BUF_SIZE, which evenly divides our mapped size, so we only care if remaining is 0 yet + if (!byteBuffer.hasRemaining()) { + byteBuffer.force(); + mappedFilePosition += MAP_SIZE; + byteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, mappedFilePosition, MAP_SIZE); + } + byteBuffer.put(buf, bufOffset, len); + } + + public int getPosition() { + return mappedFilePosition + byteBuffer.position() + bufPosition; + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedArrayPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedArrayPool.java new file mode 100644 index 00000000..faea4480 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedArrayPool.java @@ -0,0 +1,194 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.primitives.Ints; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.Field; +import org.jf.dexlib2.iface.value.EncodedValue; +import org.jf.dexlib2.immutable.value.ImmutableEncodedValueFactory; +import org.jf.dexlib2.util.EncodedValueUtils; +import org.jf.dexlib2.util.FieldUtil; +import org.jf.util.CollectionUtils; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.*; + +public class EncodedArrayPool { + @Nonnull private final Map internedEncodedArrayItems = Maps.newHashMap(); + @Nonnull private final DexFile dexFile; + + public EncodedArrayPool(DexFile dexFile) { + this.dexFile = dexFile; + } + + public void intern(@Nonnull ClassDef classDef) { + Key key = Key.of(classDef); + if (key != null) { + Integer prev = internedEncodedArrayItems.put(key, 0); + if (prev == null) { + for (EncodedValue encodedValue: key.getElements()) { + dexFile.internEncodedValue(encodedValue); + } + } + } + } + + public int getOffset(@Nonnull ClassDef classDef) { + Key key = Key.of(classDef); + if (key != null) { + Integer offset = internedEncodedArrayItems.get(key); + if (offset == null) { + throw new ExceptionWithContext("Encoded array not found."); + } + return offset; + } + return 0; + } + + public void write(@Nonnull DexWriter writer) throws IOException { + List encodedArrays = Lists.newArrayList(internedEncodedArrayItems.keySet()); + Collections.sort(encodedArrays); + + for (Key encodedArray: encodedArrays) { + internedEncodedArrayItems.put(encodedArray, writer.getPosition()); + writer.writeUleb128(encodedArray.getElementCount()); + for (EncodedValue value: encodedArray.getElements()) { + dexFile.writeEncodedValue(writer, value); + } + } + } + + public static class Key implements Comparable { + private final Set fields; + private final int size; + + private static final Function GET_INITIAL_VALUE = + new Function() { + @Override + public EncodedValue apply(Field input) { + EncodedValue initialValue = input.getInitialValue(); + if (initialValue == null) { + return ImmutableEncodedValueFactory.defaultValueForType(input.getType()); + } + return initialValue; + } + }; + + private Key(@Nonnull Set fields, int size) { + this.fields = fields; + this.size = size; + } + + @Nullable + public static Key of(@Nonnull ClassDef classDef) { + Set fields = classDef.getFields(); + + Iterable staticFields = FluentIterable.from(classDef.getFields()).filter(IS_STATIC_FIELD); + int lastIndex = CollectionUtils.lastIndexOf(staticFields, HAS_INITIALIZER); + + if (lastIndex > -1) { + return new Key(fields, lastIndex+1); + } + return null; + } + + public int getElementCount() { + return size; + } + + @Nonnull + public Iterable getElements() { + return FluentIterable.from(fields) + .filter(IS_STATIC_FIELD) + .limit(size) + .transform(GET_INITIAL_VALUE); + } + + @Override + public int hashCode() { + return CollectionUtils.listHashCode(getElements()); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Key) { + Key other = (Key)o; + if (size != other.size) { + return false; + } + return Iterables.elementsEqual(getElements(), other.getElements()); + } + return false; + } + + private static final Predicate HAS_INITIALIZER = new Predicate() { + @Override + public boolean apply(Field input) { + EncodedValue encodedValue = input.getInitialValue(); + return encodedValue != null && !EncodedValueUtils.isDefaultValue(encodedValue); + } + }; + + private static final Predicate IS_STATIC_FIELD = new Predicate() { + @Override + public boolean apply(Field input) { + return FieldUtil.isStatic(input); + } + }; + + @Override + public int compareTo(Key o) { + int res = Ints.compare(size, o.size); + if (res != 0) { + return res; + } + Iterator otherElements = o.getElements().iterator(); + for (EncodedValue element: getElements()) { + res = element.compareTo(otherElements.next()); + if (res != 0) { + return res; + } + } + return 0; + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/FieldPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/FieldPool.java new file mode 100644 index 00000000..0bc449b1 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/FieldPool.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.jf.dexlib2.iface.reference.FieldReference; +import org.jf.dexlib2.util.ReferenceUtil; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class FieldPool { + private final static int FIELD_ID_ITEM_SIZE = 8; + @Nonnull private final Map internedFieldIdItems = Maps.newHashMap(); + @Nonnull private final DexFile dexFile; + + public FieldPool(@Nonnull DexFile dexFile) { + this.dexFile = dexFile; + } + + public void intern(@Nonnull FieldReference field) { + Integer prev = internedFieldIdItems.put(field, 0); + if (prev == null) { + dexFile.typePool.intern(field.getDefiningClass()); + dexFile.stringPool.intern(field.getName()); + dexFile.typePool.intern(field.getType()); + } + } + + public int getIndex(@Nonnull FieldReference fieldReference) { + Integer index = internedFieldIdItems.get(fieldReference); + if (index == null) { + throw new ExceptionWithContext("Field not found.: %s", ReferenceUtil.getFieldDescriptor(fieldReference)); + } + return index; + } + + public int getIndexedSectionSize() { + return internedFieldIdItems.size() * FIELD_ID_ITEM_SIZE; + } + + public void write(@Nonnull DexWriter writer) throws IOException { + List fields = Lists.newArrayList(internedFieldIdItems.keySet()); + Collections.sort(fields); + + int index = 0; + for (FieldReference field: fields) { + internedFieldIdItems.put(field, index++); + writer.writeUshort(dexFile.typePool.getIndex(field.getDefiningClass())); + writer.writeUshort(dexFile.typePool.getIndex(field.getType())); + writer.writeInt(dexFile.stringPool.getIndex(field.getName())); + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/MethodPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/MethodPool.java new file mode 100644 index 00000000..70ee1f40 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/MethodPool.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.jf.dexlib2.iface.reference.MethodReference; +import org.jf.dexlib2.util.ReferenceUtil; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class MethodPool { + private final static int METHOD_ID_ITEM_SIZE = 8; + @Nonnull private final Map internedMethodIdItems = Maps.newHashMap(); + @Nonnull private final DexFile dexFile; + + public MethodPool(@Nonnull DexFile dexFile) { + this.dexFile = dexFile; + } + + public void intern(@Nonnull MethodReference method) { + Integer prev = internedMethodIdItems.put(method, 0); + if (prev == null) { + dexFile.typePool.intern(method.getDefiningClass()); + dexFile.protoPool.intern(method); + dexFile.stringPool.intern(method.getName()); + } + } + + public int getIndex(@Nonnull MethodReference methodReference) { + Integer index = internedMethodIdItems.get(methodReference); + if (index == null) { + throw new ExceptionWithContext("Method not found.: %s", ReferenceUtil.getMethodDescriptor(methodReference)); + } + return index; + } + + public int getIndexedSectionSize() { + return internedMethodIdItems.size() * METHOD_ID_ITEM_SIZE; + } + + public void write(@Nonnull DexWriter writer) throws IOException { + List methods = Lists.newArrayList(internedMethodIdItems.keySet()); + + int index = 0; + for (MethodReference method: methods) { + internedMethodIdItems.put(method, index++); + writer.writeUshort(dexFile.typePool.getIndex(method.getDefiningClass())); + writer.writeUshort(dexFile.protoPool.getIndex(method)); + writer.writeInt(dexFile.stringPool.getIndex(method.getName())); + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/ProtoPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/ProtoPool.java new file mode 100644 index 00000000..641b0d02 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/ProtoPool.java @@ -0,0 +1,160 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.jf.dexlib2.iface.reference.MethodReference; +import org.jf.dexlib2.iface.reference.TypeReference; +import org.jf.util.CollectionUtils; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class ProtoPool { + private final static int PROTO_ID_ITEM_SIZE = 12; + @Nonnull private final Map internedProtoIdItems = Maps.newHashMap(); + @Nonnull private final DexFile dexFile; + + public ProtoPool(@Nonnull DexFile dexFile) { + this.dexFile = dexFile; + } + + public void intern(@Nonnull MethodReference method) { + // We can't use method directly, because it is likely a full MethodReference. We use a wrapper that computes + // hashCode and equals based only on the prototype fields + Key key = new Key(method); + Integer prev = internedProtoIdItems.put(key, 0); + if (prev == null) { + dexFile.stringPool.intern(key.getShorty()); + dexFile.typePool.intern(method.getReturnType()); + dexFile.typeListPool.intern(method.getParameters()); + } + } + + public int getIndex(@Nonnull MethodReference method) { + Key key = new Key(method); + Integer index = internedProtoIdItems.get(key); + if (index == null) { + throw new ExceptionWithContext("Prototype not found.: %s", key); + } + return index; + } + + public int getIndexedSectionSize() { + return internedProtoIdItems.size() * PROTO_ID_ITEM_SIZE; + } + + public void write(@Nonnull DexWriter writer) throws IOException { + List prototypes = Lists.newArrayList(internedProtoIdItems.keySet()); + Collections.sort(prototypes); + + int index = 0; + for (Key proto: prototypes) { + internedProtoIdItems.put(proto, index++); + + writer.writeInt(dexFile.stringPool.getIndex(proto.getShorty())); + writer.writeInt(dexFile.typePool.getIndex(proto.getReturnType())); + writer.writeInt(dexFile.typeListPool.getOffset(proto.getParameters())); + } + } + + private static class Key implements Comparable { + @Nonnull private final MethodReference method; + + public Key(@Nonnull MethodReference method) { + this.method = method; + } + + @Nonnull public String getReturnType() { return method.getReturnType(); } + @Nonnull public Collection getParameters() { + return method.getParameters(); + } + + public String getShorty() { + Collection params = method.getParameters(); + StringBuilder sb = new StringBuilder(params.size() + 1); + sb.append(getShortyType(method.getReturnType())); + for (TypeReference typeRef: params) { + sb.append(getShortyType(typeRef)); + } + return sb.toString(); + } + + private static char getShortyType(CharSequence type) { + if (type.length() > 1) { + return 'L'; + } + return type.charAt(0); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (TypeReference type: getParameters()) { + sb.append(type); + } + sb.append(')'); + sb.append(getReturnType()); + return sb.toString(); + } + + @Override + public int hashCode() { + int hashCode = getReturnType().hashCode(); + return hashCode*31 + getParameters().hashCode(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof Key) { + Key other = (Key)o; + return getReturnType().equals(other.getReturnType()) && + getParameters().equals(other.getParameters()); + } + return false; + } + + @Override + public int compareTo(@Nonnull Key o) { + int res = getReturnType().compareTo(o.getReturnType()); + if (res != 0) return res; + return CollectionUtils.compareAsList(getParameters(), o.getParameters()); + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/StringPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/StringPool.java new file mode 100644 index 00000000..79c945dc --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/StringPool.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.jf.util.ExceptionWithContext; +import org.jf.util.StringUtils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class StringPool { + private final static int STRING_ID_ITEM_SIZE = 4; + @Nonnull private final Map internedStringIdItems = Maps.newHashMap(); + + public void intern(@Nonnull CharSequence string) { + internedStringIdItems.put(string.toString(), 0); + } + + public void internNullable(@Nullable CharSequence string) { + if (string != null) { + intern(string); + } + } + + public int getIndex(@Nonnull CharSequence string) { + Integer index = internedStringIdItems.get(string.toString()); + if (index == null) { + throw new ExceptionWithContext("String not found.: %s", + StringUtils.escapeString(string.toString())); + } + return index; + } + + public int getIndexNullable(@Nullable CharSequence string) { + if (string == null) { + return -1; + } + return getIndex(string); + } + + public int getIndexedSectionSize() { + return internedStringIdItems.size() * STRING_ID_ITEM_SIZE; + } + + public void write(@Nonnull DexWriter indexWriter, @Nonnull DexWriter offsetWriter) throws IOException { + List strings = Lists.newArrayList(internedStringIdItems.keySet()); + Collections.sort(strings); + + int index = 0; + for (String string: strings) { + internedStringIdItems.put(string, index++); + indexWriter.writeInt(offsetWriter.getPosition()); + offsetWriter.writeUleb128(string.length()); + offsetWriter.writeString(string); + offsetWriter.write(0); + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/TypeListPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/TypeListPool.java new file mode 100644 index 00000000..29faf1bc --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/TypeListPool.java @@ -0,0 +1,150 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.*; + +public class TypeListPool { + @Nonnull private final Map internedTypeListItems = Maps.newHashMap(); + @Nonnull private final DexFile dexFile; + + public TypeListPool(@Nonnull DexFile dexFile) { + this.dexFile = dexFile; + } + + public void intern(@Nonnull Collection types) { + Key key = new Key(types); + Integer prev = internedTypeListItems.put(key, 0); + if (prev == null) { + for (CharSequence type: types) { + dexFile.typePool.intern(type); + } + } + } + + public int getOffset(@Nonnull Collection types) { + Key key = new Key(types); + Integer offset = internedTypeListItems.get(key); + if (offset == null) { + throw new ExceptionWithContext("Type list not found.: %s", key); + } + return offset; + } + + public void write(@Nonnull DexWriter writer) throws IOException { + List typeLists = Lists.newArrayList(internedTypeListItems.keySet()); + Collections.sort(typeLists); + + for (Key typeList: typeLists) { + writer.align(); + internedTypeListItems.put(typeList, writer.getPosition()); + Collection types = typeList.getTypes(); + writer.writeInt(types.size()); + for (CharSequence type: types) { + writer.writeUshort(dexFile.typePool.getIndex(type)); + } + } + } + + public static class Key implements Comparable { + @Nonnull private Collection types; + + public Key(@Nonnull Collection types) { + this.types = types; + } + + @Override + public int hashCode() { + int hashCode = 1; + for (CharSequence type: types) { + hashCode = hashCode*31 + type.toString().hashCode(); + } + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (o instanceof Key) { + Key other = (Key)o; + if (types.size() != other.types.size()) { + return false; + } + Iterator otherTypes = other.types.iterator(); + for (CharSequence type: types) { + if (!type.toString().equals(otherTypes.next().toString())) { + return false; + } + } + return true; + } + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (CharSequence type: types) { + sb.append(type.toString()); + } + return sb.toString(); + } + + @Nonnull + public Collection getTypes() { + return types; + } + + @Override + public int compareTo(Key o) { + Iterator other = o.types.iterator(); + for (CharSequence type: types) { + if (!other.hasNext()) { + return 1; + } + int comparison = type.toString().compareTo(other.next().toString()); + if (comparison != 0) { + return comparison; + } + } + if (other.hasNext()) { + return -1; + } + return 0; + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/TypePool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/TypePool.java new file mode 100644 index 00000000..dc41b303 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/TypePool.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class TypePool { + private final static int TYPE_ID_ITEM_SIZE = 4; + @Nonnull private final Map internedTypeIdItems = Maps.newHashMap(); + @Nonnull private final DexFile dexFile; + + public TypePool(@Nonnull DexFile dexFile) { + this.dexFile = dexFile; + } + + public void intern(@Nonnull CharSequence type) { + Integer prev = internedTypeIdItems.put(type.toString(), 0); + if (prev == null) { + dexFile.stringPool.intern(type); + } + } + + public void internNullable(@Nullable CharSequence type) { + if (type != null) { + intern(type); + } + } + + public int getIndex(@Nonnull CharSequence type) { + Integer index = internedTypeIdItems.get(type.toString()); + if (index == null) { + throw new ExceptionWithContext("Type not found.: %s", type); + } + return index; + } + + public int getIndexNullable(@Nullable CharSequence type) { + if (type == null) { + return -1; + } + return getIndex(type); + } + + public int getIndexedSectionSize() { + return internedTypeIdItems.size() * TYPE_ID_ITEM_SIZE; + } + + public void write(@Nonnull DexWriter writer) throws IOException { + List types = Lists.newArrayList(internedTypeIdItems.keySet()); + Collections.sort(types); + + int index = 0; + for (String type: types) { + internedTypeIdItems.put(type, index++); + int stringIndex = dexFile.stringPool.getIndex(type); + writer.writeInt(stringIndex); + } + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/util/PrototypeUtils.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/util/PrototypeUtils.java new file mode 100644 index 00000000..c239c78c --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/util/PrototypeUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.writer.util; + +import org.jf.dexlib2.iface.reference.MethodReference; +import org.jf.dexlib2.iface.reference.TypeReference; + +import javax.annotation.Nonnull; +import java.util.Collection; + +public final class PrototypeUtils { + + + private PrototypeUtils() {} +} diff --git a/util/src/main/java/org/jf/util/CollectionUtils.java b/util/src/main/java/org/jf/util/CollectionUtils.java index 6ecd6454..84de6953 100644 --- a/util/src/main/java/org/jf/util/CollectionUtils.java +++ b/util/src/main/java/org/jf/util/CollectionUtils.java @@ -31,17 +31,35 @@ package org.jf.util; +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Ordering; import com.google.common.primitives.Ints; import javax.annotation.Nonnull; -import java.util.Collection; -import java.util.Comparator; -import java.util.Iterator; -import java.util.SortedSet; +import java.util.*; public class CollectionUtils { + public static int listHashCode(@Nonnull Iterable iterable) { + int hashCode = 1; + for (T item: iterable) { + hashCode = hashCode*31 + item.hashCode(); + } + return hashCode; + } + + public static int lastIndexOf(@Nonnull Iterable iterable, @Nonnull Predicate predicate) { + int index = 0; + int lastMatchingIndex = -1; + for (T item: iterable) { + if (predicate.apply(item)) { + lastMatchingIndex = index; + } + index++; + } + return lastMatchingIndex; + } + public static > int compareAsList(@Nonnull Collection list1, @Nonnull Collection list2) { int res = Ints.compare(list1.size(), list2.size()); @@ -54,6 +72,27 @@ public class CollectionUtils { return 0; } + public static int compareAsIterable(@Nonnull Comparator comparator, + @Nonnull Iterable it1, + @Nonnull Iterable it2) { + Iterator elements2 = it2.iterator(); + for (T element1: it1) { + int res = comparator.compare(element1, elements2.next()); + if (res != 0) return res; + } + return 0; + } + + public static > int compareAsIterable(@Nonnull Iterable it1, + @Nonnull Iterable it2) { + Iterator elements2 = it2.iterator(); + for (T element1: it1) { + int res = element1.compareTo(elements2.next()); + if (res != 0) return res; + } + return 0; + } + public static int compareAsList(@Nonnull Comparator elementComparator, @Nonnull Collection list1, @Nonnull Collection list2) { @@ -67,6 +106,7 @@ public class CollectionUtils { return 0; } + @Nonnull public static Comparator> listComparator( @Nonnull final Comparator elementComparator) { return new Comparator>() { @@ -77,14 +117,32 @@ public class CollectionUtils { }; } + public static boolean isNaturalSortedSet(@Nonnull Iterable it) { + if (it instanceof SortedSet) { + SortedSet sortedSet = (SortedSet)it; + Comparator comparator = sortedSet.comparator(); + return (comparator == null) || comparator.equals(Ordering.natural()); + } + return false; + } + + public static boolean isSortedSet(@Nonnull Comparator elementComparator, + @Nonnull Iterable it) { + if (it instanceof SortedSet) { + SortedSet sortedSet = (SortedSet)it; + Comparator comparator = sortedSet.comparator(); + if (comparator == null) { + return elementComparator.equals(Ordering.natural()); + } + return elementComparator.equals(comparator); + } + return false; + } + @Nonnull private static SortedSet toNaturalSortedSet(@Nonnull Collection collection) { - if (collection instanceof SortedSet) { - SortedSet sortedSet = (SortedSet)collection; - Comparator comparator = sortedSet.comparator(); - if (comparator == null || comparator.equals(Ordering.natural())) { - return sortedSet; - } + if (isNaturalSortedSet(collection)) { + return (SortedSet)collection; } return ImmutableSortedSet.copyOf(collection); } @@ -102,6 +160,17 @@ public class CollectionUtils { return ImmutableSortedSet.copyOf(elementComparator, collection); } + @Nonnull + public static Comparator> setComparator( + @Nonnull final Comparator elementComparator) { + return new Comparator>() { + @Override + public int compare(Collection list1, Collection list2) { + return compareAsSet(elementComparator, list1, list2); + } + }; + } + public static > int compareAsSet(@Nonnull Collection set1, @Nonnull Collection set2) { int res = Ints.compare(set1.size(), set2.size());