1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.writer;
33
34import com.google.common.collect.Iterables;
35import com.google.common.collect.Lists;
36import com.google.common.collect.Maps;
37import com.google.common.collect.Ordering;
38import org.jf.dexlib2.AccessFlags;
39import org.jf.dexlib2.Opcode;
40import org.jf.dexlib2.ReferenceType;
41import org.jf.dexlib2.base.BaseAnnotation;
42import org.jf.dexlib2.base.BaseAnnotationElement;
43import org.jf.dexlib2.builder.MutableMethodImplementation;
44import org.jf.dexlib2.builder.instruction.BuilderInstruction31c;
45import org.jf.dexlib2.dexbacked.raw.*;
46import org.jf.dexlib2.iface.Annotation;
47import org.jf.dexlib2.iface.ExceptionHandler;
48import org.jf.dexlib2.iface.TryBlock;
49import org.jf.dexlib2.iface.debug.DebugItem;
50import org.jf.dexlib2.iface.debug.LineNumber;
51import org.jf.dexlib2.iface.instruction.Instruction;
52import org.jf.dexlib2.iface.instruction.OneRegisterInstruction;
53import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
54import org.jf.dexlib2.iface.instruction.formats.*;
55import org.jf.dexlib2.iface.reference.FieldReference;
56import org.jf.dexlib2.iface.reference.MethodReference;
57import org.jf.dexlib2.iface.reference.StringReference;
58import org.jf.dexlib2.iface.reference.TypeReference;
59import org.jf.dexlib2.util.InstructionUtil;
60import org.jf.dexlib2.util.MethodUtil;
61import org.jf.dexlib2.writer.io.DeferredOutputStream;
62import org.jf.dexlib2.writer.io.DeferredOutputStreamFactory;
63import org.jf.dexlib2.writer.io.DexDataStore;
64import org.jf.dexlib2.writer.io.MemoryDeferredOutputStream;
65import org.jf.dexlib2.writer.util.TryListBuilder;
66import org.jf.util.CollectionUtils;
67import org.jf.util.ExceptionWithContext;
68
69import javax.annotation.Nonnull;
70import javax.annotation.Nullable;
71import java.io.ByteArrayOutputStream;
72import java.io.IOException;
73import java.io.InputStream;
74import java.io.OutputStream;
75import java.nio.ByteBuffer;
76import java.nio.ByteOrder;
77import java.security.MessageDigest;
78import java.security.NoSuchAlgorithmException;
79import java.util.*;
80import java.util.Map.Entry;
81import java.util.zip.Adler32;
82
83public abstract class DexWriter<
84        StringKey extends CharSequence, StringRef extends StringReference, TypeKey extends CharSequence,
85        TypeRef extends TypeReference, ProtoKey extends Comparable<ProtoKey>,
86        FieldRefKey extends FieldReference, MethodRefKey extends MethodReference,
87        ClassKey extends Comparable<? super ClassKey>,
88        AnnotationKey extends Annotation, AnnotationSetKey,
89        TypeListKey,
90        FieldKey, MethodKey,
91        EncodedValue,
92        AnnotationElement extends org.jf.dexlib2.iface.AnnotationElement> {
93    public static final int NO_INDEX = -1;
94    public static final int NO_OFFSET = 0;
95
96    protected final int api;
97
98    protected int stringIndexSectionOffset = NO_OFFSET;
99    protected int typeSectionOffset = NO_OFFSET;
100    protected int protoSectionOffset = NO_OFFSET;
101    protected int fieldSectionOffset = NO_OFFSET;
102    protected int methodSectionOffset = NO_OFFSET;
103    protected int classIndexSectionOffset = NO_OFFSET;
104
105    protected int stringDataSectionOffset = NO_OFFSET;
106    protected int classDataSectionOffset = NO_OFFSET;
107    protected int typeListSectionOffset = NO_OFFSET;
108    protected int encodedArraySectionOffset = NO_OFFSET;
109    protected int annotationSectionOffset = NO_OFFSET;
110    protected int annotationSetSectionOffset = NO_OFFSET;
111    protected int annotationSetRefSectionOffset = NO_OFFSET;
112    protected int annotationDirectorySectionOffset = NO_OFFSET;
113    protected int debugSectionOffset = NO_OFFSET;
114    protected int codeSectionOffset = NO_OFFSET;
115    protected int mapSectionOffset = NO_OFFSET;
116
117    protected int numEncodedArrayItems = 0;
118    protected int numAnnotationSetRefItems = 0;
119    protected int numAnnotationDirectoryItems = 0;
120    protected int numDebugInfoItems = 0;
121    protected int numCodeItemItems = 0;
122    protected int numClassDataItems = 0;
123
124    protected final StringSection<StringKey, StringRef> stringSection;
125    protected final TypeSection<StringKey, TypeKey, TypeRef> typeSection;
126    protected final ProtoSection<StringKey, TypeKey, ProtoKey, TypeListKey> protoSection;
127    protected final FieldSection<StringKey, TypeKey, FieldRefKey, FieldKey> fieldSection;
128    protected final MethodSection<StringKey, TypeKey, ProtoKey, MethodRefKey, MethodKey> methodSection;
129    protected final ClassSection<StringKey, TypeKey, TypeListKey, ClassKey, FieldKey, MethodKey, AnnotationSetKey,
130            EncodedValue> classSection;
131
132    protected final TypeListSection<TypeKey, TypeListKey> typeListSection;
133    protected final AnnotationSection<StringKey, TypeKey, AnnotationKey, AnnotationElement, EncodedValue> annotationSection;
134    protected final AnnotationSetSection<AnnotationKey, AnnotationSetKey> annotationSetSection;
135
136    protected DexWriter(int api,
137                        StringSection<StringKey, StringRef> stringSection,
138                        TypeSection<StringKey, TypeKey, TypeRef> typeSection,
139                        ProtoSection<StringKey, TypeKey, ProtoKey, TypeListKey> protoSection,
140                        FieldSection<StringKey, TypeKey, FieldRefKey, FieldKey> fieldSection,
141                        MethodSection<StringKey, TypeKey, ProtoKey, MethodRefKey, MethodKey> methodSection,
142                        ClassSection<StringKey, TypeKey, TypeListKey, ClassKey, FieldKey, MethodKey, AnnotationSetKey,
143                                EncodedValue> classSection,
144                        TypeListSection<TypeKey, TypeListKey> typeListSection,
145                        AnnotationSection<StringKey, TypeKey, AnnotationKey, AnnotationElement,
146                                EncodedValue> annotationSection,
147                        AnnotationSetSection<AnnotationKey, AnnotationSetKey> annotationSetSection) {
148        this.api = api;
149        this.stringSection = stringSection;
150        this.typeSection = typeSection;
151        this.protoSection = protoSection;
152        this.fieldSection = fieldSection;
153        this.methodSection = methodSection;
154        this.classSection = classSection;
155        this.typeListSection = typeListSection;
156        this.annotationSection = annotationSection;
157        this.annotationSetSection = annotationSetSection;
158    }
159
160    protected abstract void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer,
161                                              @Nonnull EncodedValue encodedValue) throws IOException;
162
163    private static Comparator<Map.Entry> toStringKeyComparator =
164            new Comparator<Map.Entry>() {
165                @Override public int compare(Entry o1, Entry o2) {
166                    return o1.getKey().toString().compareTo(o2.getKey().toString());
167                }
168            };
169
170    private static <T extends Comparable<? super T>> Comparator<Map.Entry<? extends T, ?>> comparableKeyComparator() {
171        return new Comparator<Entry<? extends T, ?>>() {
172            @Override public int compare(Entry<? extends T, ?> o1, Entry<? extends T, ?> o2) {
173                return o1.getKey().compareTo(o2.getKey());
174            }
175        };
176    }
177
178    protected class InternalEncodedValueWriter extends EncodedValueWriter<StringKey, TypeKey, FieldRefKey, MethodRefKey,
179            AnnotationElement, EncodedValue> {
180        private InternalEncodedValueWriter(@Nonnull DexDataWriter writer) {
181            super(writer, stringSection, typeSection, fieldSection, methodSection, annotationSection);
182        }
183
184        @Override protected void writeEncodedValue(@Nonnull EncodedValue encodedValue) throws IOException {
185            DexWriter.this.writeEncodedValue(this, encodedValue);
186        }
187    }
188
189    private int getDataSectionOffset() {
190        return HeaderItem.ITEM_SIZE +
191                stringSection.getItems().size() * StringIdItem.ITEM_SIZE +
192                typeSection.getItems().size() * TypeIdItem.ITEM_SIZE +
193                protoSection.getItems().size() * ProtoIdItem.ITEM_SIZE +
194                fieldSection.getItems().size() * FieldIdItem.ITEM_SIZE +
195                methodSection.getItems().size() * MethodIdItem.ITEM_SIZE +
196                classSection.getItems().size() * ClassDefItem.ITEM_SIZE;
197    }
198
199    public void writeTo(@Nonnull DexDataStore dest) throws IOException {
200        this.writeTo(dest, MemoryDeferredOutputStream.getFactory());
201    }
202
203    public void writeTo(@Nonnull DexDataStore dest,
204                        @Nonnull DeferredOutputStreamFactory tempFactory) throws IOException {
205        try {
206            int dataSectionOffset = getDataSectionOffset();
207            DexDataWriter headerWriter = outputAt(dest, 0);
208            DexDataWriter indexWriter = outputAt(dest, HeaderItem.ITEM_SIZE);
209            DexDataWriter offsetWriter = outputAt(dest, dataSectionOffset);
210            try {
211                writeStrings(indexWriter, offsetWriter);
212                writeTypes(indexWriter);
213                writeTypeLists(offsetWriter);
214                writeProtos(indexWriter);
215                writeFields(indexWriter);
216                writeMethods(indexWriter);
217                writeEncodedArrays(offsetWriter);
218                writeAnnotations(offsetWriter);
219                writeAnnotationSets(offsetWriter);
220                writeAnnotationSetRefs(offsetWriter);
221                writeAnnotationDirectories(offsetWriter);
222                writeDebugAndCodeItems(offsetWriter, tempFactory.makeDeferredOutputStream());
223                writeClasses(indexWriter, offsetWriter);
224                writeMapItem(offsetWriter);
225                writeHeader(headerWriter, dataSectionOffset, offsetWriter.getPosition());
226            } finally {
227                headerWriter.close();
228                indexWriter.close();
229                offsetWriter.close();
230            }
231            updateSignature(dest);
232            updateChecksum(dest);
233        } finally {
234            dest.close();
235        }
236    }
237
238    private void updateSignature(@Nonnull DexDataStore dataStore) throws IOException {
239        MessageDigest md;
240        try {
241            md = MessageDigest.getInstance("SHA-1");
242        } catch (NoSuchAlgorithmException ex) {
243            throw new RuntimeException(ex);
244        }
245
246        byte[] buffer = new byte[4 * 1024];
247        InputStream input = dataStore.readAt(HeaderItem.SIGNATURE_DATA_START_OFFSET);
248        int bytesRead = input.read(buffer);
249        while (bytesRead >= 0) {
250            md.update(buffer, 0, bytesRead);
251            bytesRead = input.read(buffer);
252        }
253
254        byte[] signature = md.digest();
255        if (signature.length != HeaderItem.SIGNATURE_SIZE) {
256            throw new RuntimeException("unexpected digest write: " + signature.length + " bytes");
257        }
258
259        // write signature
260        OutputStream output = dataStore.outputAt(HeaderItem.SIGNATURE_OFFSET);
261        output.write(signature);
262        output.close();
263    }
264
265    private void updateChecksum(@Nonnull DexDataStore dataStore) throws IOException {
266        Adler32 a32 = new Adler32();
267
268        byte[] buffer = new byte[4 * 1024];
269        InputStream input = dataStore.readAt(HeaderItem.CHECKSUM_DATA_START_OFFSET);
270        int bytesRead = input.read(buffer);
271        while (bytesRead >= 0) {
272            a32.update(buffer, 0, bytesRead);
273            bytesRead = input.read(buffer);
274        }
275
276        // write checksum, utilizing logic in DexWriter to write the integer value properly
277        OutputStream output = dataStore.outputAt(HeaderItem.CHECKSUM_OFFSET);
278        DexDataWriter.writeInt(output, (int)a32.getValue());
279        output.close();
280    }
281
282    private static DexDataWriter outputAt(DexDataStore dataStore, int filePosition) throws IOException {
283        return new DexDataWriter(dataStore.outputAt(filePosition), filePosition);
284    }
285
286    private void writeStrings(@Nonnull DexDataWriter indexWriter, @Nonnull DexDataWriter offsetWriter) throws IOException {
287        stringIndexSectionOffset = indexWriter.getPosition();
288        stringDataSectionOffset = offsetWriter.getPosition();
289        int index = 0;
290        List<Entry<? extends StringKey, Integer>> stringEntries = Lists.newArrayList(stringSection.getItems());
291        Collections.sort(stringEntries, toStringKeyComparator);
292
293        for (Map.Entry<? extends StringKey, Integer>  entry: stringEntries) {
294            entry.setValue(index++);
295            indexWriter.writeInt(offsetWriter.getPosition());
296            String stringValue = entry.getKey().toString();
297            offsetWriter.writeUleb128(stringValue.length());
298            offsetWriter.writeString(stringValue);
299            offsetWriter.write(0);
300        }
301    }
302
303    private void writeTypes(@Nonnull DexDataWriter writer) throws IOException {
304        typeSectionOffset = writer.getPosition();
305        int index = 0;
306
307        List<Map.Entry<? extends TypeKey, Integer>> typeEntries = Lists.newArrayList(typeSection.getItems());
308        Collections.sort(typeEntries, toStringKeyComparator);
309
310        for (Map.Entry<? extends TypeKey, Integer> entry : typeEntries) {
311            entry.setValue(index++);
312            writer.writeInt(stringSection.getItemIndex(typeSection.getString(entry.getKey())));
313        }
314    }
315
316    private void writeProtos(@Nonnull DexDataWriter writer) throws IOException {
317        protoSectionOffset = writer.getPosition();
318        int index = 0;
319
320        List<Map.Entry<? extends ProtoKey, Integer>> protoEntries = Lists.newArrayList(protoSection.getItems());
321        Collections.sort(protoEntries, DexWriter.<ProtoKey>comparableKeyComparator());
322
323        for (Map.Entry<? extends ProtoKey, Integer> entry: protoEntries) {
324            entry.setValue(index++);
325            ProtoKey key = entry.getKey();
326            writer.writeInt(stringSection.getItemIndex(protoSection.getShorty(key)));
327            writer.writeInt(typeSection.getItemIndex(protoSection.getReturnType(key)));
328            writer.writeInt(typeListSection.getNullableItemOffset(protoSection.getParameters(key)));
329        }
330    }
331
332    private void writeFields(@Nonnull DexDataWriter writer) throws IOException {
333        fieldSectionOffset = writer.getPosition();
334        int index = 0;
335
336        List<Map.Entry<? extends FieldRefKey, Integer>> fieldEntries = Lists.newArrayList(fieldSection.getItems());
337        Collections.sort(fieldEntries, DexWriter.<FieldRefKey>comparableKeyComparator());
338
339        for (Map.Entry<? extends FieldRefKey, Integer> entry: fieldEntries) {
340            entry.setValue(index++);
341            FieldRefKey key = entry.getKey();
342            writer.writeUshort(typeSection.getItemIndex(fieldSection.getDefiningClass(key)));
343            writer.writeUshort(typeSection.getItemIndex(fieldSection.getFieldType(key)));
344            writer.writeInt(stringSection.getItemIndex(fieldSection.getName(key)));
345        }
346    }
347
348    private void writeMethods(@Nonnull DexDataWriter writer) throws IOException {
349        methodSectionOffset = writer.getPosition();
350        int index = 0;
351
352        List<Map.Entry<? extends MethodRefKey, Integer>> methodEntries = Lists.newArrayList(methodSection.getItems());
353        Collections.sort(methodEntries, DexWriter.<MethodRefKey>comparableKeyComparator());
354
355        for (Map.Entry<? extends MethodRefKey, Integer> entry: methodEntries) {
356            entry.setValue(index++);
357            MethodRefKey key = entry.getKey();
358            writer.writeUshort(typeSection.getItemIndex(methodSection.getDefiningClass(key)));
359            writer.writeUshort(protoSection.getItemIndex(methodSection.getPrototype(key)));
360            writer.writeInt(stringSection.getItemIndex(methodSection.getName(key)));
361        }
362    }
363
364    private void writeClasses(@Nonnull DexDataWriter indexWriter, @Nonnull DexDataWriter offsetWriter) throws IOException {
365        classIndexSectionOffset = indexWriter.getPosition();
366        classDataSectionOffset = offsetWriter.getPosition();
367
368        List<Map.Entry<? extends ClassKey, Integer>> classEntries = Lists.newArrayList(classSection.getItems());
369        Collections.sort(classEntries, DexWriter.<ClassKey>comparableKeyComparator());
370
371        int index = 0;
372        for (Map.Entry<? extends ClassKey, Integer> key: classEntries) {
373            index = writeClass(indexWriter, offsetWriter, index, key);
374        }
375    }
376
377    /**
378     * Writes out the class_def_item and class_data_item for the given class.
379     *
380     * This will recursively write out any unwritten superclass/interface before writing the class itself, as per the
381     * dex specification.
382     *
383     * @return the index for the next class to be written
384     */
385    private int writeClass(@Nonnull DexDataWriter indexWriter, @Nonnull DexDataWriter offsetWriter,
386                           int nextIndex, @Nullable Map.Entry<? extends ClassKey, Integer> entry) throws IOException {
387        if (entry == null) {
388            // class does not exist in this dex file, cannot write it
389            return nextIndex;
390        }
391
392        if (entry.getValue() != NO_INDEX) {
393            // class has already been written, no need to write it
394            return nextIndex;
395        }
396
397        ClassKey key = entry.getKey();
398
399        // set a bogus index, to make sure we don't recurse and double-write it
400        entry.setValue(0);
401
402        // first, try to write the superclass
403        Map.Entry<? extends ClassKey, Integer> superEntry =
404                classSection.getClassEntryByType(classSection.getSuperclass(key));
405        nextIndex = writeClass(indexWriter, offsetWriter, nextIndex, superEntry);
406
407        // then, try to write interfaces
408        for (TypeKey interfaceTypeKey: typeListSection.getTypes(classSection.getSortedInterfaces(key))) {
409            Map.Entry<? extends ClassKey, Integer> interfaceEntry = classSection.getClassEntryByType(interfaceTypeKey);
410            nextIndex = writeClass(indexWriter, offsetWriter, nextIndex, interfaceEntry);
411        }
412
413        // now set the index for real
414        entry.setValue(nextIndex++);
415
416        // and finally, write the class itself
417        // first, the class_def_item
418        indexWriter.writeInt(typeSection.getItemIndex(classSection.getType(key)));
419        indexWriter.writeInt(classSection.getAccessFlags(key));
420        indexWriter.writeInt(typeSection.getNullableItemIndex(classSection.getSuperclass(key)));
421        indexWriter.writeInt(typeListSection.getNullableItemOffset(classSection.getSortedInterfaces(key)));
422        indexWriter.writeInt(stringSection.getNullableItemIndex(classSection.getSourceFile(key)));
423        indexWriter.writeInt(classSection.getAnnotationDirectoryOffset(key));
424
425        Collection<? extends FieldKey> staticFields = classSection.getSortedStaticFields(key);
426        Collection<? extends FieldKey> instanceFields = classSection.getSortedInstanceFields(key);
427        Collection<? extends MethodKey> directMethods = classSection.getSortedDirectMethods(key);
428        Collection<? extends MethodKey> virtualMethods = classSection.getSortedVirtualMethods(key);
429        boolean classHasData = staticFields.size() > 0 ||
430                instanceFields.size() > 0 ||
431                directMethods.size() > 0 ||
432                virtualMethods.size() > 0;
433
434        if (classHasData) {
435            indexWriter.writeInt(offsetWriter.getPosition());
436        } else {
437            indexWriter.writeInt(0);
438        }
439
440        indexWriter.writeInt(classSection.getEncodedArrayOffset(key));
441
442        // now write the class_data_item
443        if (classHasData) {
444            numClassDataItems++;
445
446            offsetWriter.writeUleb128(staticFields.size());
447            offsetWriter.writeUleb128(instanceFields.size());
448            offsetWriter.writeUleb128(directMethods.size());
449            offsetWriter.writeUleb128(virtualMethods.size());
450
451            writeEncodedFields(offsetWriter, staticFields);
452            writeEncodedFields(offsetWriter, instanceFields);
453            writeEncodedMethods(offsetWriter, directMethods);
454            writeEncodedMethods(offsetWriter, virtualMethods);
455        }
456
457        return nextIndex;
458    }
459
460    private void writeEncodedFields(@Nonnull DexDataWriter writer, @Nonnull Collection<? extends FieldKey> fields)
461            throws IOException {
462        int prevIndex = 0;
463        for (FieldKey key: fields) {
464            int index = fieldSection.getFieldIndex(key);
465            writer.writeUleb128(index - prevIndex);
466            writer.writeUleb128(classSection.getFieldAccessFlags(key));
467            prevIndex = index;
468        }
469    }
470
471    private void writeEncodedMethods(@Nonnull DexDataWriter writer, @Nonnull Collection<? extends MethodKey> methods)
472            throws IOException {
473        int prevIndex = 0;
474        for (MethodKey key: methods) {
475            int index = methodSection.getMethodIndex(key);
476            writer.writeUleb128(index-prevIndex);
477            writer.writeUleb128(classSection.getMethodAccessFlags(key));
478            writer.writeUleb128(classSection.getCodeItemOffset(key));
479            prevIndex = index;
480        }
481    }
482
483    private void writeTypeLists(@Nonnull DexDataWriter writer) throws IOException {
484        writer.align();
485        typeListSectionOffset = writer.getPosition();
486        for (Map.Entry<? extends TypeListKey, Integer> entry: typeListSection.getItems()) {
487            writer.align();
488            entry.setValue(writer.getPosition());
489
490            Collection<? extends TypeKey> types = typeListSection.getTypes(entry.getKey());
491            writer.writeInt(types.size());
492            for (TypeKey typeKey: types) {
493                writer.writeUshort(typeSection.getItemIndex(typeKey));
494            }
495        }
496    }
497
498    private static class EncodedArrayKey<EncodedValue> {
499        @Nonnull Collection<? extends EncodedValue> elements;
500
501        public EncodedArrayKey() {
502        }
503
504        @Override public int hashCode() {
505            return CollectionUtils.listHashCode(elements);
506        }
507
508        @Override public boolean equals(Object o) {
509            if (o instanceof EncodedArrayKey) {
510                EncodedArrayKey other = (EncodedArrayKey)o;
511                if (elements.size() != other.elements.size()) {
512                    return false;
513                }
514                return Iterables.elementsEqual(elements, other.elements);
515            }
516            return false;
517        }
518    }
519
520    private void writeEncodedArrays(@Nonnull DexDataWriter writer) throws IOException {
521        InternalEncodedValueWriter encodedValueWriter = new InternalEncodedValueWriter(writer);
522        encodedArraySectionOffset = writer.getPosition();
523
524        HashMap<EncodedArrayKey<EncodedValue>, Integer> internedItems = Maps.newHashMap();
525        EncodedArrayKey<EncodedValue> key = new EncodedArrayKey<EncodedValue>();
526
527        for (ClassKey classKey: classSection.getSortedClasses()) {
528            Collection <? extends EncodedValue> elements = classSection.getStaticInitializers(classKey);
529            if (elements != null && elements.size() > 0) {
530                key.elements = elements;
531                Integer prev = internedItems.get(key);
532                if (prev != null) {
533                    classSection.setEncodedArrayOffset(classKey, prev);
534                } else {
535                    int offset = writer.getPosition();
536                    internedItems.put(key, offset);
537                    classSection.setEncodedArrayOffset(classKey, offset);
538                    key = new EncodedArrayKey<EncodedValue>();
539
540                    numEncodedArrayItems++;
541
542                    writer.writeUleb128(elements.size());
543                    for (EncodedValue value: elements) {
544                        writeEncodedValue(encodedValueWriter, value);
545                    }
546                }
547            }
548        }
549    }
550
551    private void writeAnnotations(@Nonnull DexDataWriter writer) throws IOException {
552        InternalEncodedValueWriter encodedValueWriter = new InternalEncodedValueWriter(writer);
553
554        annotationSectionOffset = writer.getPosition();
555        for (Map.Entry<? extends AnnotationKey, Integer> entry: annotationSection.getItems()) {
556            entry.setValue(writer.getPosition());
557
558            AnnotationKey key = entry.getKey();
559
560            writer.writeUbyte(annotationSection.getVisibility(key));
561            writer.writeUleb128(typeSection.getItemIndex(annotationSection.getType(key)));
562
563            Collection<? extends AnnotationElement> elements = Ordering.from(BaseAnnotationElement.BY_NAME)
564                    .immutableSortedCopy(annotationSection.getElements(key));
565
566            writer.writeUleb128(elements.size());
567
568            for (AnnotationElement element: elements) {
569                writer.writeUleb128(stringSection.getItemIndex(annotationSection.getElementName(element)));
570                writeEncodedValue(encodedValueWriter, annotationSection.getElementValue(element));
571            }
572        }
573    }
574
575
576    private void writeAnnotationSets(@Nonnull DexDataWriter writer) throws IOException {
577        writer.align();
578        annotationSetSectionOffset = writer.getPosition();
579        for (Map.Entry<? extends AnnotationSetKey, Integer> entry: annotationSetSection.getItems()) {
580            Collection<? extends AnnotationKey> annotations = Ordering.from(BaseAnnotation.BY_TYPE)
581                    .immutableSortedCopy(annotationSetSection.getAnnotations(entry.getKey()));
582
583            writer.align();
584            entry.setValue(writer.getPosition());
585            writer.writeInt(annotations.size());
586            for (AnnotationKey annotationKey: annotations) {
587                writer.writeInt(annotationSection.getItemOffset(annotationKey));
588            }
589        }
590    }
591
592    private void writeAnnotationSetRefs(@Nonnull DexDataWriter writer) throws IOException {
593        writer.align();
594        annotationSetRefSectionOffset = writer.getPosition();
595        HashMap<List<? extends AnnotationSetKey>, Integer> internedItems = Maps.newHashMap();
596
597        for (ClassKey classKey: classSection.getSortedClasses()) {
598            for (MethodKey methodKey: classSection.getSortedMethods(classKey)) {
599                List<? extends AnnotationSetKey> parameterAnnotations = classSection.getParameterAnnotations(methodKey);
600                if (parameterAnnotations != null) {
601                    Integer prev = internedItems.get(parameterAnnotations);
602                    if (prev != null) {
603                        classSection.setAnnotationSetRefListOffset(methodKey, prev);
604                    } else {
605                        writer.align();
606                        int position = writer.getPosition();
607                        classSection.setAnnotationSetRefListOffset(methodKey, position);
608                        internedItems.put(parameterAnnotations, position);
609
610                        numAnnotationSetRefItems++;
611
612                        writer.writeInt(parameterAnnotations.size());
613                        for (AnnotationSetKey annotationSetKey: parameterAnnotations) {
614                            if (annotationSetSection.getAnnotations(annotationSetKey).size() > 0) {
615                                writer.writeInt(annotationSetSection.getItemOffset(annotationSetKey));
616                            } else {
617                                writer.writeInt(NO_OFFSET);
618                            }
619                        }
620                    }
621                }
622            }
623        }
624    }
625
626    private void writeAnnotationDirectories(@Nonnull DexDataWriter writer) throws IOException {
627        writer.align();
628        annotationDirectorySectionOffset = writer.getPosition();
629        HashMap<AnnotationSetKey, Integer> internedItems = Maps.newHashMap();
630
631        ByteBuffer tempBuffer = ByteBuffer.allocate(65536);
632        tempBuffer.order(ByteOrder.LITTLE_ENDIAN);
633
634        for (ClassKey key: classSection.getSortedClasses()) {
635            // first, we write the field/method/parameter items to a temporary buffer, so that we can get a count
636            // of each type, and determine if we even need to write an annotation directory for this class
637
638            Collection<? extends FieldKey> fields = classSection.getSortedFields(key);
639            Collection<? extends MethodKey> methods = classSection.getSortedMethods(key);
640
641            // this is how much space we'll need if every field and method has annotations.
642            int maxSize = fields.size() * 8 + methods.size() * 16;
643            if (maxSize > tempBuffer.capacity()) {
644                tempBuffer = ByteBuffer.allocate(maxSize);
645                tempBuffer.order(ByteOrder.LITTLE_ENDIAN);
646            }
647
648            tempBuffer.clear();
649
650            int fieldAnnotations = 0;
651            int methodAnnotations = 0;
652            int parameterAnnotations = 0;
653
654            for (FieldKey field: fields) {
655                AnnotationSetKey fieldAnnotationsKey = classSection.getFieldAnnotations(field);
656                if (fieldAnnotationsKey != null) {
657                    fieldAnnotations++;
658                    tempBuffer.putInt(fieldSection.getFieldIndex(field));
659                    tempBuffer.putInt(annotationSetSection.getItemOffset(fieldAnnotationsKey));
660                }
661            }
662
663            for (MethodKey method: methods) {
664                AnnotationSetKey methodAnnotationsKey = classSection.getMethodAnnotations(method);
665                if (methodAnnotationsKey != null) {
666                    methodAnnotations++;
667                    tempBuffer.putInt(methodSection.getMethodIndex(method));
668                    tempBuffer.putInt(annotationSetSection.getItemOffset(methodAnnotationsKey));
669                }
670            }
671
672            for (MethodKey method: methods) {
673                int offset = classSection.getAnnotationSetRefListOffset(method);
674                if (offset != DexWriter.NO_OFFSET) {
675                    parameterAnnotations++;
676                    tempBuffer.putInt(methodSection.getMethodIndex(method));
677                    tempBuffer.putInt(offset);
678                }
679            }
680
681            // now, we finally know how many field/method/parameter annotations were written to the temp buffer
682
683            AnnotationSetKey classAnnotationKey = classSection.getClassAnnotations(key);
684            if (fieldAnnotations == 0 && methodAnnotations == 0 && parameterAnnotations == 0) {
685                if (classAnnotationKey != null) {
686                    // This is an internable directory. Let's see if we've already written one like it
687                    Integer directoryOffset = internedItems.get(classAnnotationKey);
688                    if (directoryOffset != null) {
689                        classSection.setAnnotationDirectoryOffset(key, directoryOffset);
690                        continue;
691                    } else {
692                        internedItems.put(classAnnotationKey, writer.getPosition());
693                    }
694                } else {
695                    continue;
696                }
697            }
698
699            // yep, we need to write it out
700            numAnnotationDirectoryItems++;
701            classSection.setAnnotationDirectoryOffset(key, writer.getPosition());
702
703            writer.writeInt(annotationSetSection.getNullableItemOffset(classAnnotationKey));
704            writer.writeInt(fieldAnnotations);
705            writer.writeInt(methodAnnotations);
706            writer.writeInt(parameterAnnotations);
707            writer.write(tempBuffer.array(), 0, tempBuffer.position());
708        }
709    }
710
711    private static class CodeItemOffset<MethodKey> {
712        @Nonnull MethodKey method;
713        int codeOffset;
714
715        private CodeItemOffset(@Nonnull MethodKey method, int codeOffset) {
716            this.codeOffset = codeOffset;
717            this.method = method;
718        }
719    }
720
721    private void writeDebugAndCodeItems(@Nonnull DexDataWriter offsetWriter,
722                                        @Nonnull DeferredOutputStream temp) throws IOException {
723        ByteArrayOutputStream ehBuf = new ByteArrayOutputStream();
724        debugSectionOffset = offsetWriter.getPosition();
725        DebugWriter<StringKey, TypeKey> debugWriter =
726                new DebugWriter<StringKey, TypeKey>(stringSection, typeSection, offsetWriter);
727
728        DexDataWriter codeWriter = new DexDataWriter(temp, 0);
729
730        List<CodeItemOffset<MethodKey>> codeOffsets = Lists.newArrayList();
731
732        for (ClassKey classKey: classSection.getSortedClasses()) {
733            Collection<? extends MethodKey> directMethods = classSection.getSortedDirectMethods(classKey);
734            Collection<? extends MethodKey> virtualMethods = classSection.getSortedVirtualMethods(classKey);
735
736            Iterable<MethodKey> methods = Iterables.concat(directMethods, virtualMethods);
737
738            for (MethodKey methodKey: methods) {
739                List<? extends TryBlock<? extends ExceptionHandler>> tryBlocks =
740                        classSection.getTryBlocks(methodKey);
741                Iterable<? extends Instruction> instructions = classSection.getInstructions(methodKey);
742                Iterable<? extends DebugItem> debugItems = classSection.getDebugItems(methodKey);
743
744                if (instructions != null && stringSection.hasJumboIndexes()) {
745                    boolean needsFix = false;
746                    for (Instruction instruction: instructions) {
747                        if (instruction.getOpcode() == Opcode.CONST_STRING) {
748                            if (stringSection.getItemIndex(
749                                    (StringRef)((ReferenceInstruction)instruction).getReference()) >= 65536) {
750                                needsFix = true;
751                                break;
752                            }
753                        }
754                    }
755
756                    if (needsFix) {
757                        MutableMethodImplementation mutableMethodImplementation =
758                                classSection.makeMutableMethodImplementation(methodKey);
759                        fixInstructions(mutableMethodImplementation);
760
761                        instructions = mutableMethodImplementation.getInstructions();
762                        tryBlocks = mutableMethodImplementation.getTryBlocks();
763                        debugItems = mutableMethodImplementation.getDebugItems();
764                    }
765                }
766
767                int debugItemOffset = writeDebugItem(offsetWriter, debugWriter,
768                        classSection.getParameterNames(methodKey), debugItems);
769                int codeItemOffset = writeCodeItem(codeWriter, ehBuf, methodKey, tryBlocks, instructions, debugItemOffset);
770
771                if (codeItemOffset != -1) {
772                    codeOffsets.add(new CodeItemOffset<MethodKey>(methodKey, codeItemOffset));
773                }
774            }
775        }
776
777        offsetWriter.align();
778        codeSectionOffset = offsetWriter.getPosition();
779
780        codeWriter.close();
781        temp.writeTo(offsetWriter);
782        temp.close();
783
784        for (CodeItemOffset<MethodKey> codeOffset: codeOffsets) {
785            classSection.setCodeItemOffset(codeOffset.method, codeSectionOffset + codeOffset.codeOffset);
786        }
787    }
788
789    private void fixInstructions(@Nonnull MutableMethodImplementation methodImplementation) {
790        List<? extends Instruction> instructions = methodImplementation.getInstructions();
791
792        for (int i=0; i<instructions.size(); i++) {
793            Instruction instruction = instructions.get(i);
794
795            if (instruction.getOpcode() == Opcode.CONST_STRING) {
796                if (stringSection.getItemIndex(
797                        (StringRef)((ReferenceInstruction)instruction).getReference()) >= 65536) {
798                    methodImplementation.replaceInstruction(i, new BuilderInstruction31c(Opcode.CONST_STRING_JUMBO,
799                            ((OneRegisterInstruction)instruction).getRegisterA(),
800                            ((ReferenceInstruction)instruction).getReference()));
801                }
802            }
803        }
804    }
805
806    private int writeDebugItem(@Nonnull DexDataWriter writer,
807                               @Nonnull DebugWriter<StringKey, TypeKey> debugWriter,
808                               @Nullable Iterable<? extends StringKey> parameterNames,
809                               @Nullable Iterable<? extends DebugItem> debugItems) throws IOException {
810        int parameterCount = 0;
811        int lastNamedParameterIndex = -1;
812        if (parameterNames != null) {
813            parameterCount = Iterables.size(parameterNames);
814            int index = 0;
815            for (StringKey parameterName: parameterNames) {
816                if (parameterName != null) {
817                    lastNamedParameterIndex = index;
818                }
819                index++;
820            }
821        }
822
823
824        if (lastNamedParameterIndex == -1 && (debugItems == null || Iterables.isEmpty(debugItems))) {
825            return NO_OFFSET;
826        }
827
828        numDebugInfoItems++;
829
830        int debugItemOffset = writer.getPosition();
831        int startingLineNumber = 0;
832
833        if (debugItems != null) {
834            for (org.jf.dexlib2.iface.debug.DebugItem debugItem: debugItems) {
835                if (debugItem instanceof LineNumber) {
836                    startingLineNumber = ((LineNumber)debugItem).getLineNumber();
837                    break;
838                }
839            }
840        }
841        writer.writeUleb128(startingLineNumber);
842
843        writer.writeUleb128(parameterCount);
844        if (parameterNames != null) {
845            int index = 0;
846            for (StringKey parameterName: parameterNames) {
847                if (index == parameterCount) {
848                    break;
849                }
850                index++;
851                writer.writeUleb128(stringSection.getNullableItemIndex(parameterName) + 1);
852            }
853        }
854
855        if (debugItems != null) {
856            debugWriter.reset(startingLineNumber);
857
858            for (DebugItem debugItem: debugItems) {
859                classSection.writeDebugItem(debugWriter, debugItem);
860            }
861        }
862        // write an END_SEQUENCE opcode, to end the debug item
863        writer.write(0);
864
865        return debugItemOffset;
866    }
867
868    private int writeCodeItem(@Nonnull DexDataWriter writer,
869                              @Nonnull ByteArrayOutputStream ehBuf,
870                              @Nonnull MethodKey methodKey,
871                              @Nonnull List<? extends TryBlock<? extends ExceptionHandler>> tryBlocks,
872                              @Nullable Iterable<? extends Instruction> instructions,
873                              int debugItemOffset) throws IOException {
874        if (instructions == null && debugItemOffset == NO_OFFSET) {
875            return -1;
876        }
877
878        numCodeItemItems++;
879
880        writer.align();
881
882        int codeItemOffset = writer.getPosition();
883
884        writer.writeUshort(classSection.getRegisterCount(methodKey));
885
886        boolean isStatic = AccessFlags.STATIC.isSet(classSection.getMethodAccessFlags(methodKey));
887        Collection<? extends TypeKey> parameters = typeListSection.getTypes(
888                protoSection.getParameters(methodSection.getPrototype(methodKey)));
889
890        writer.writeUshort(MethodUtil.getParameterRegisterCount(parameters, isStatic));
891
892        if (instructions != null) {
893            tryBlocks = TryListBuilder.massageTryBlocks(tryBlocks);
894
895            int outParamCount = 0;
896            int codeUnitCount = 0;
897            for (Instruction instruction: instructions) {
898                codeUnitCount += instruction.getCodeUnits();
899                if (instruction.getOpcode().referenceType == ReferenceType.METHOD) {
900                    ReferenceInstruction refInsn = (ReferenceInstruction)instruction;
901                    MethodReference methodRef = (MethodReference)refInsn.getReference();
902                    int paramCount = MethodUtil.getParameterRegisterCount(methodRef, InstructionUtil.isInvokeStatic(instruction.getOpcode()));
903                    if (paramCount > outParamCount) {
904                        outParamCount = paramCount;
905                    }
906                }
907            }
908
909            writer.writeUshort(outParamCount);
910            writer.writeUshort(tryBlocks.size());
911            writer.writeInt(debugItemOffset);
912
913            InstructionWriter instructionWriter =
914                    InstructionWriter.makeInstructionWriter(writer, stringSection, typeSection, fieldSection,
915                            methodSection);
916
917            writer.writeInt(codeUnitCount);
918            for (Instruction instruction: instructions) {
919                switch (instruction.getOpcode().format) {
920                    case Format10t:
921                        instructionWriter.write((Instruction10t)instruction);
922                        break;
923                    case Format10x:
924                        instructionWriter.write((Instruction10x)instruction);
925                        break;
926                    case Format11n:
927                        instructionWriter.write((Instruction11n)instruction);
928                        break;
929                    case Format11x:
930                        instructionWriter.write((Instruction11x)instruction);
931                        break;
932                    case Format12x:
933                        instructionWriter.write((Instruction12x)instruction);
934                        break;
935                    case Format20bc:
936                        instructionWriter.write((Instruction20bc)instruction);
937                        break;
938                    case Format20t:
939                        instructionWriter.write((Instruction20t)instruction);
940                        break;
941                    case Format21c:
942                        instructionWriter.write((Instruction21c)instruction);
943                        break;
944                    case Format21ih:
945                        instructionWriter.write((Instruction21ih)instruction);
946                        break;
947                    case Format21lh:
948                        instructionWriter.write((Instruction21lh)instruction);
949                        break;
950                    case Format21s:
951                        instructionWriter.write((Instruction21s)instruction);
952                        break;
953                    case Format21t:
954                        instructionWriter.write((Instruction21t)instruction);
955                        break;
956                    case Format22b:
957                        instructionWriter.write((Instruction22b)instruction);
958                        break;
959                    case Format22c:
960                        instructionWriter.write((Instruction22c)instruction);
961                        break;
962                    case Format22s:
963                        instructionWriter.write((Instruction22s)instruction);
964                        break;
965                    case Format22t:
966                        instructionWriter.write((Instruction22t)instruction);
967                        break;
968                    case Format22x:
969                        instructionWriter.write((Instruction22x)instruction);
970                        break;
971                    case Format23x:
972                        instructionWriter.write((Instruction23x)instruction);
973                        break;
974                    case Format30t:
975                        instructionWriter.write((Instruction30t)instruction);
976                        break;
977                    case Format31c:
978                        instructionWriter.write((Instruction31c)instruction);
979                        break;
980                    case Format31i:
981                        instructionWriter.write((Instruction31i)instruction);
982                        break;
983                    case Format31t:
984                        instructionWriter.write((Instruction31t)instruction);
985                        break;
986                    case Format32x:
987                        instructionWriter.write((Instruction32x)instruction);
988                        break;
989                    case Format35c:
990                        instructionWriter.write((Instruction35c)instruction);
991                        break;
992                    case Format3rc:
993                        instructionWriter.write((Instruction3rc)instruction);
994                        break;
995                    case Format51l:
996                        instructionWriter.write((Instruction51l)instruction);
997                        break;
998                    case ArrayPayload:
999                        instructionWriter.write((ArrayPayload)instruction);
1000                        break;
1001                    case PackedSwitchPayload:
1002                        instructionWriter.write((PackedSwitchPayload)instruction);
1003                        break;
1004                    case SparseSwitchPayload:
1005                        instructionWriter.write((SparseSwitchPayload)instruction);
1006                        break;
1007                    default:
1008                        throw new ExceptionWithContext("Unsupported instruction format: %s",
1009                                instruction.getOpcode().format);
1010                }
1011            }
1012
1013            if (tryBlocks.size() > 0) {
1014                writer.align();
1015
1016                // filter out unique lists of exception handlers
1017                Map<List<? extends ExceptionHandler>, Integer> exceptionHandlerOffsetMap = Maps.newHashMap();
1018                for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) {
1019                    exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), 0);
1020                }
1021                DexDataWriter.writeUleb128(ehBuf, exceptionHandlerOffsetMap.size());
1022
1023                for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) {
1024                    int startAddress = tryBlock.getStartCodeAddress();
1025                    int endAddress = startAddress + tryBlock.getCodeUnitCount();
1026
1027                    int tbCodeUnitCount = endAddress - startAddress;
1028
1029                    writer.writeInt(startAddress);
1030                    writer.writeUshort(tbCodeUnitCount);
1031
1032                    if (tryBlock.getExceptionHandlers().size() == 0) {
1033                        throw new ExceptionWithContext("No exception handlers for the try block!");
1034                    }
1035
1036                    Integer offset = exceptionHandlerOffsetMap.get(tryBlock.getExceptionHandlers());
1037                    if (offset != 0) {
1038                        // exception handler has already been written out, just use it
1039                        writer.writeUshort(offset);
1040                    } else {
1041                        // if offset has not been set yet, we are about to write out a new exception handler
1042                        offset = ehBuf.size();
1043                        writer.writeUshort(offset);
1044                        exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), offset);
1045
1046                        // check if the last exception handler is a catch-all and adjust the size accordingly
1047                        int ehSize = tryBlock.getExceptionHandlers().size();
1048                        ExceptionHandler ehLast = tryBlock.getExceptionHandlers().get(ehSize-1);
1049                        if (ehLast.getExceptionType() == null) {
1050                            ehSize = ehSize * (-1) + 1;
1051                        }
1052
1053                        // now let's layout the exception handlers, assuming that catch-all is always last
1054                        DexDataWriter.writeSleb128(ehBuf, ehSize);
1055                        for (ExceptionHandler eh : tryBlock.getExceptionHandlers()) {
1056                            TypeKey exceptionTypeKey = classSection.getExceptionType(eh);
1057
1058                            int codeAddress = eh.getHandlerCodeAddress();
1059
1060                            if (exceptionTypeKey != null) {
1061                                //regular exception handling
1062                                DexDataWriter.writeUleb128(ehBuf, typeSection.getItemIndex(exceptionTypeKey));
1063                                DexDataWriter.writeUleb128(ehBuf, codeAddress);
1064                            } else {
1065                                //catch-all
1066                                DexDataWriter.writeUleb128(ehBuf, codeAddress);
1067                            }
1068                        }
1069                    }
1070                }
1071
1072                if (ehBuf.size() > 0) {
1073                    ehBuf.writeTo(writer);
1074                    ehBuf.reset();
1075                }
1076            }
1077        } else {
1078            // no instructions, all we have is the debug item offset
1079            writer.writeUshort(0);
1080            writer.writeUshort(0);
1081            writer.writeInt(debugItemOffset);
1082            writer.writeInt(0);
1083        }
1084
1085        return codeItemOffset;
1086    }
1087
1088    private int calcNumItems() {
1089        int numItems = 0;
1090
1091        // header item
1092        numItems++;
1093
1094        if (stringSection.getItems().size() > 0) {
1095            numItems += 2; // index and data
1096        }
1097        if (typeSection.getItems().size()  > 0) {
1098            numItems++;
1099        }
1100        if (protoSection.getItems().size() > 0) {
1101            numItems++;
1102        }
1103        if (fieldSection.getItems().size() > 0) {
1104            numItems++;
1105        }
1106        if (methodSection.getItems().size() > 0) {
1107            numItems++;
1108        }
1109        if (typeListSection.getItems().size() > 0) {
1110            numItems++;
1111        }
1112        if (numEncodedArrayItems > 0) {
1113            numItems++;
1114        }
1115        if (annotationSection.getItems().size() > 0) {
1116            numItems++;
1117        }
1118        if (annotationSetSection.getItems().size() > 0) {
1119            numItems++;
1120        }
1121        if (numAnnotationSetRefItems > 0) {
1122            numItems++;
1123        }
1124        if (numAnnotationDirectoryItems > 0) {
1125            numItems++;
1126        }
1127        if (numDebugInfoItems > 0) {
1128            numItems++;
1129        }
1130        if (numCodeItemItems > 0) {
1131            numItems++;
1132        }
1133        if (classSection.getItems().size() > 0) {
1134            numItems++;
1135        }
1136        if (numClassDataItems > 0) {
1137            numItems++;
1138        }
1139        // map item itself
1140        numItems++;
1141
1142        return numItems;
1143    }
1144
1145    private void writeMapItem(@Nonnull DexDataWriter writer) throws IOException{
1146        writer.align();
1147        mapSectionOffset = writer.getPosition();
1148        int numItems = calcNumItems();
1149
1150        writer.writeInt(numItems);
1151
1152        // index section
1153        writeMapItem(writer, ItemType.HEADER_ITEM, 1, 0);
1154        writeMapItem(writer, ItemType.STRING_ID_ITEM, stringSection.getItems().size(), stringIndexSectionOffset);
1155        writeMapItem(writer, ItemType.TYPE_ID_ITEM, typeSection.getItems().size(), typeSectionOffset);
1156        writeMapItem(writer, ItemType.PROTO_ID_ITEM, protoSection.getItems().size(), protoSectionOffset);
1157        writeMapItem(writer, ItemType.FIELD_ID_ITEM, fieldSection.getItems().size(), fieldSectionOffset);
1158        writeMapItem(writer, ItemType.METHOD_ID_ITEM, methodSection.getItems().size(), methodSectionOffset);
1159        writeMapItem(writer, ItemType.CLASS_DEF_ITEM, classSection.getItems().size(), classIndexSectionOffset);
1160
1161        // data section
1162        writeMapItem(writer, ItemType.STRING_DATA_ITEM, stringSection.getItems().size(), stringDataSectionOffset);
1163        writeMapItem(writer, ItemType.TYPE_LIST, typeListSection.getItems().size(), typeListSectionOffset);
1164        writeMapItem(writer, ItemType.ENCODED_ARRAY_ITEM, numEncodedArrayItems, encodedArraySectionOffset);
1165        writeMapItem(writer, ItemType.ANNOTATION_ITEM, annotationSection.getItems().size(), annotationSectionOffset);
1166        writeMapItem(writer, ItemType.ANNOTATION_SET_ITEM, annotationSetSection.getItems().size(),
1167                annotationSetSectionOffset);
1168        writeMapItem(writer, ItemType.ANNOTATION_SET_REF_LIST, numAnnotationSetRefItems, annotationSetRefSectionOffset);
1169        writeMapItem(writer, ItemType.ANNOTATION_DIRECTORY_ITEM, numAnnotationDirectoryItems,
1170                annotationDirectorySectionOffset);
1171        writeMapItem(writer, ItemType.DEBUG_INFO_ITEM, numDebugInfoItems, debugSectionOffset);
1172        writeMapItem(writer, ItemType.CODE_ITEM, numCodeItemItems, codeSectionOffset);
1173        writeMapItem(writer, ItemType.CLASS_DATA_ITEM, numClassDataItems, classDataSectionOffset);
1174        writeMapItem(writer, ItemType.MAP_LIST, 1, mapSectionOffset);
1175    }
1176
1177    private void writeMapItem(@Nonnull DexDataWriter writer, int type, int size, int offset) throws IOException {
1178        if (size > 0) {
1179            writer.writeUshort(type);
1180            writer.writeUshort(0);
1181            writer.writeInt(size);
1182            writer.writeInt(offset);
1183        }
1184    }
1185
1186    private void writeHeader(@Nonnull DexDataWriter writer, int dataOffset, int fileSize) throws IOException {
1187        // always write the 035 version, there's no reason to use the 036 version for now
1188        writer.write(HeaderItem.MAGIC_VALUES[0]);
1189
1190        // checksum placeholder
1191        writer.writeInt(0);
1192
1193        // signature placeholder
1194        writer.write(new byte[20]);
1195
1196        writer.writeInt(fileSize);
1197        writer.writeInt(HeaderItem.ITEM_SIZE);
1198        writer.writeInt(HeaderItem.LITTLE_ENDIAN_TAG);
1199
1200        // link
1201        writer.writeInt(0);
1202        writer.writeInt(0);
1203
1204        // map
1205        writer.writeInt(mapSectionOffset);
1206
1207        // index sections
1208
1209        writeSectionInfo(writer, stringSection.getItems().size(), stringIndexSectionOffset);
1210        writeSectionInfo(writer, typeSection.getItems().size(), typeSectionOffset);
1211        writeSectionInfo(writer, protoSection.getItems().size(), protoSectionOffset);
1212        writeSectionInfo(writer, fieldSection.getItems().size(), fieldSectionOffset);
1213        writeSectionInfo(writer, methodSection.getItems().size(), methodSectionOffset);
1214        writeSectionInfo(writer, classSection.getItems().size(), classIndexSectionOffset);
1215
1216        // data section
1217        writer.writeInt(fileSize - dataOffset);
1218        writer.writeInt(dataOffset);
1219    }
1220
1221    private void writeSectionInfo(DexDataWriter writer, int numItems, int offset) throws IOException {
1222        writer.writeInt(numItems);
1223        if (numItems > 0) {
1224            writer.writeInt(offset);
1225        } else {
1226            writer.writeInt(0);
1227        }
1228    }
1229}
1230