DexWriter.java revision f3c33259dd0567294ef814be879b59a450c24f70
1/*
2 * Copyright 2012, 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.base.Preconditions;
35import org.jf.util.ExceptionWithContext;
36
37import javax.annotation.Nonnull;
38import java.io.IOException;
39import java.io.OutputStream;
40import java.nio.ByteBuffer;
41import java.nio.MappedByteBuffer;
42import java.nio.channels.FileChannel;
43
44public class DexWriter extends OutputStream {
45    private static final int MAP_SIZE = 1024*1024;
46    private static final int BUF_SIZE = 256*1024;
47
48    @Nonnull private final FileChannel channel;
49    private MappedByteBuffer byteBuffer;
50
51    /** The position in the file at which byteBuffer starts. */
52    private int mappedFilePosition;
53
54    private byte[] buf = new byte[BUF_SIZE];
55    /** The index within buf to write to */
56    private int bufPosition;
57
58    /**
59     * A temporary buffer that can be used for larger writes. Can be replaced with a larger buffer if needed.
60     * Must be at least 8 bytes
61     */
62    private byte[] tempBuf = new byte[8];
63
64    /** A buffer of 0s we used for writing alignment values */
65    private byte[] zeroBuf = new byte[3];
66
67    public DexWriter(FileChannel channel, int position) throws IOException {
68        this.channel = channel;
69        this.mappedFilePosition = position;
70
71        byteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, position, MAP_SIZE);
72    }
73
74    @Override
75    public void write(int b) throws IOException {
76        if (bufPosition >= BUF_SIZE) {
77            flushBuffer();
78        }
79        buf[bufPosition++] = (byte)b;
80    }
81
82    @Override
83    public void write(byte[] b) throws IOException {
84        write(b, 0, b.length);
85    }
86
87    @Override
88    public void write(byte[] b, int off, int len) throws IOException {
89        int toWrite = len;
90
91        if (bufPosition == BUF_SIZE) {
92            flushBuffer();
93        }
94        int remainingBuffer = BUF_SIZE - bufPosition;
95        if (toWrite >= remainingBuffer) {
96            // fill up and write out the current buffer
97            System.arraycopy(b, 0, buf, bufPosition, remainingBuffer);
98            bufPosition += remainingBuffer;
99            toWrite -= remainingBuffer;
100            flushBuffer();
101
102            // skip the intermediate buffer while we have a full buffer's worth
103            while (toWrite >= BUF_SIZE) {
104                writeBufferToMap(b, len - toWrite, BUF_SIZE);
105                toWrite -= BUF_SIZE;
106            }
107        }
108        // write out the final chunk, if any
109        if (toWrite > 0) {
110            System.arraycopy(b, len-toWrite, buf, bufPosition, len);
111            bufPosition += len;
112        }
113    }
114
115    public void writeLong(long value) throws IOException {
116        writeInt((int)value);
117        writeInt((int)(value >> 32));
118    }
119
120    public void writeInt(int value) throws IOException {
121        write(value);
122        write(value >> 8);
123        write(value >> 16);
124        write(value >> 24);
125    }
126
127    public void writeShort(int value) throws IOException {
128        if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
129            throw new ExceptionWithContext("Short value out of range: %d", value);
130        }
131        write(value);
132        write(value >> 8);
133    }
134
135    public void writeUshort(int value) throws IOException {
136        if (value < 0 || value > 0xFFFF) {
137            throw new ExceptionWithContext("Unsigned short value out of range: %d", value);
138        }
139        write(value);
140        write(value >> 8);
141    }
142
143    public void writeUbyte(int value) throws IOException {
144        if (value < 0 || value > 0xFF) {
145            throw new ExceptionWithContext("Unsigned byte value out of range: %d", value);
146        }
147        write(value);
148    }
149
150    public static void writeUleb128(OutputStream out, int value) throws IOException {
151        while (value > 0x7f) {
152            out.write((value & 0x7f) | 0x80);
153            value >>>= 7;
154        }
155        out.write(value);
156    }
157
158    public void writeUleb128(int value) throws IOException {
159        writeUleb128(this, value);
160    }
161
162    public static void writeSleb128(OutputStream out, int value) throws IOException {
163        if (value >= 0) {
164            while (value > 0x3f) {
165                out.write((value & 0x7f) | 0x80);
166                value >>>= 7;
167            }
168            out.write(value & 0x7f);
169        } else {
170            while (value < -0x40) {
171                out.write((value & 0x7f) | 0x80);
172                value >>= 7;
173            }
174            out.write(value & 0x7f);
175        }
176    }
177
178    public void writeSleb128(int value) throws IOException {
179        writeSleb128(this, value);
180    }
181
182    public void writeEncodedValueHeader(int valueType, int valueArg) throws IOException {
183        write(valueType | (valueArg << 5));
184    }
185
186    public void writeEncodedInt(int valueType, int value) throws IOException {
187        int index = 0;
188        if (value >= 0) {
189            while (value > 0x7f) {
190                tempBuf[index++] = (byte)value;
191                value >>= 8;
192            }
193        } else {
194            while (value < -0x80) {
195                tempBuf[index++] = (byte)value;
196                value >>= 8;
197            }
198        }
199        tempBuf[index++] = (byte)value;
200        writeEncodedValueHeader(valueType, index);
201        write(tempBuf, 0, index);
202    }
203
204    public void writeEncodedLong(int valueType, long value) throws IOException {
205        int index = 0;
206        if (value >= 0) {
207            while (value > 0x7f) {
208                tempBuf[index++] = (byte)value;
209                value >>= 8;
210            }
211        } else {
212            while (value < -0x80) {
213                tempBuf[index++] = (byte)value;
214                value >>= 8;
215            }
216        }
217        tempBuf[index++] = (byte)value;
218        writeEncodedValueHeader(valueType, index);
219        write(tempBuf, 0, index);
220    }
221
222    public void writeEncodedUint(int valueType, int value) throws IOException {
223        int index = 0;
224        do {
225            tempBuf[index++] = (byte)value;
226            value >>= 8;
227        } while (value != 0);
228        writeEncodedValueHeader(valueType, index);
229        write(tempBuf, 0, index);
230    }
231
232    public void writeEncodedFloat(int valueType, float value) throws IOException {
233        int intValue = Float.floatToRawIntBits(value);
234
235        int index = 3;
236        do {
237            buf[index--] = (byte)((intValue & 0xFF000000) >>> 24);
238            intValue <<= 8;
239        } while (intValue != 0);
240        writeEncodedValueHeader(valueType, 4-index);
241        write(buf, index+1, 4-index);
242    }
243
244    public void writeEncodedDouble(int valueType, double value) throws IOException {
245        long longValue = Double.doubleToRawLongBits(value);
246
247        int index = 7;
248        do {
249            buf[index--] = (byte)((longValue & 0xFF00000000000000L) >>> 56);
250            longValue <<= 8;
251        } while (longValue != 0);
252        writeEncodedValueHeader(valueType, 7-index);
253        write(buf, index+1, 7-index);
254    }
255
256    public void writeString(String string) throws IOException {
257        int len = string.length();
258
259        // make sure we have enough room in the temporary buffer
260        if (tempBuf.length <= string.length()*3) {
261            tempBuf = new byte[string.length()*3];
262        }
263
264        final byte[] buf = tempBuf;
265
266        int bufPos = 0;
267        for (int i = 0; i < len; i++) {
268            char c = string.charAt(i);
269            if ((c != 0) && (c < 0x80)) {
270                buf[bufPos++] = (byte)c;
271            } else if (c < 0x800) {
272                buf[bufPos++] = (byte)(((c >> 6) & 0x1f) | 0xc0);
273                buf[bufPos++] = (byte)((c & 0x3f) | 0x80);
274            } else {
275                buf[bufPos++] = (byte)(((c >> 12) & 0x0f) | 0xe0);
276                buf[bufPos++] = (byte)(((c >> 6) & 0x3f) | 0x80);
277                buf[bufPos++] = (byte)((c & 0x3f) | 0x80);
278            }
279        }
280        write(buf, 0, bufPos);
281    }
282
283    public void align() throws IOException {
284        int zeros = (-getPosition()) & 3;
285        if (zeros > 0) {
286            write(zeroBuf, 0, zeros);
287        }
288    }
289
290    @Override
291    public void flush() throws IOException {
292        if (bufPosition > 0) {
293            writeBufferToMap(buf, 0, bufPosition);
294            bufPosition = 0;
295        }
296        byteBuffer.force();
297        mappedFilePosition += byteBuffer.position();
298        channel.position(mappedFilePosition + MAP_SIZE);
299        channel.write(ByteBuffer.wrap(new byte[]{0}));
300        byteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, mappedFilePosition, MAP_SIZE);
301    }
302
303    @Override
304    public void close() throws IOException {
305        if (bufPosition > 0) {
306            writeBufferToMap(buf, 0, bufPosition);
307            bufPosition = 0;
308        }
309        byteBuffer.force();
310        byteBuffer = null;
311        buf = null;
312        tempBuf = null;
313    }
314
315    private void flushBuffer() throws IOException {
316        Preconditions.checkState(bufPosition == BUF_SIZE);
317        writeBufferToMap(buf, 0, BUF_SIZE);
318        bufPosition = 0;
319    }
320
321    private void writeBufferToMap(byte[] buf, int bufOffset, int len) throws IOException {
322        // we always write BUF_SIZE, which evenly divides our mapped size, so we only care if remaining is 0 yet
323        if (!byteBuffer.hasRemaining()) {
324            byteBuffer.force();
325            mappedFilePosition += MAP_SIZE;
326            byteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, mappedFilePosition, MAP_SIZE);
327        }
328        byteBuffer.put(buf, bufOffset, len);
329    }
330
331    public int getPosition() {
332        return mappedFilePosition + byteBuffer.position() + bufPosition;
333    }
334}
335