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 org.jf.util.ExceptionWithContext;
35
36import javax.annotation.Nonnull;
37import java.io.BufferedOutputStream;
38import java.io.IOException;
39import java.io.OutputStream;
40
41public class DexDataWriter extends BufferedOutputStream {
42    /**
43     * The position within the file that we will write to next. This is only updated when the buffer is flushed to the
44     * outputStream.
45     */
46    private int filePosition;
47
48    /**
49     * A temporary buffer that can be used for larger writes. Can be replaced with a larger buffer if needed.
50     * Must be at least 8 bytes
51     */
52    private byte[] tempBuf = new byte[8];
53
54    /** A buffer of 0s to use for writing alignment values */
55    private byte[] zeroBuf = new byte[3];
56
57    /**
58     * Construct a new DexWriter instance that writes to output.
59     *
60     * @param output An OutputStream to write the data to.
61     * @param filePosition The position within the file that OutputStream will write to.
62     */
63    public DexDataWriter(@Nonnull OutputStream output, int filePosition) {
64        this(output, filePosition, 256 * 1024);
65    }
66
67    public DexDataWriter(@Nonnull OutputStream output, int filePosition, int bufferSize) {
68        super(output, bufferSize);
69
70        this.filePosition = filePosition;
71    }
72
73    @Override
74    public void write(int b) throws IOException {
75        filePosition++;
76        super.write(b);
77    }
78
79    @Override
80    public void write(byte[] b) throws IOException {
81        write(b, 0, b.length);
82    }
83
84    @Override
85    public void write(byte[] b, int off, int len) throws IOException {
86        filePosition += len;
87        super.write(b, off, len);
88    }
89
90    public void writeLong(long value) throws IOException {
91        writeInt((int)value);
92        writeInt((int)(value >> 32));
93    }
94
95    public static void writeInt(OutputStream out, int value) throws IOException {
96        out.write(value);
97        out.write(value >> 8);
98        out.write(value >> 16);
99        out.write(value >> 24);
100    }
101
102    public void writeInt(int value) throws IOException {
103        writeInt(this, value);
104    }
105
106    public void writeShort(int value) throws IOException {
107        if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
108            throw new ExceptionWithContext("Short value out of range: %d", value);
109        }
110        write(value);
111        write(value >> 8);
112    }
113
114    public void writeUshort(int value) throws IOException {
115        if (value < 0 || value > 0xFFFF) {
116            throw new ExceptionWithContext("Unsigned short value out of range: %d", value);
117        }
118        write(value);
119        write(value >> 8);
120    }
121
122    public void writeUbyte(int value) throws IOException {
123        if (value < 0 || value > 0xFF) {
124            throw new ExceptionWithContext("Unsigned byte value out of range: %d", value);
125        }
126        write(value);
127    }
128
129    public static void writeUleb128(OutputStream out, int value) throws IOException {
130        while (value > 0x7f) {
131            out.write((value & 0x7f) | 0x80);
132            value >>>= 7;
133        }
134        out.write(value);
135    }
136
137    public void writeUleb128(int value) throws IOException {
138        writeUleb128(this, value);
139    }
140
141    public static void writeSleb128(OutputStream out, int value) throws IOException {
142        if (value >= 0) {
143            while (value > 0x3f) {
144                out.write((value & 0x7f) | 0x80);
145                value >>>= 7;
146            }
147            out.write(value & 0x7f);
148        } else {
149            while (value < -0x40) {
150                out.write((value & 0x7f) | 0x80);
151                value >>= 7;
152            }
153            out.write(value & 0x7f);
154        }
155    }
156
157    public void writeSleb128(int value) throws IOException {
158        writeSleb128(this, value);
159    }
160
161    public void writeEncodedValueHeader(int valueType, int valueArg) throws IOException {
162        write(valueType | (valueArg << 5));
163    }
164
165    public void writeEncodedInt(int valueType, int value) throws IOException {
166        int index = 0;
167        if (value >= 0) {
168            while (value > 0x7f) {
169                tempBuf[index++] = (byte)value;
170                value >>= 8;
171            }
172        } else {
173            while (value < -0x80) {
174                tempBuf[index++] = (byte)value;
175                value >>= 8;
176            }
177        }
178        tempBuf[index++] = (byte)value;
179        writeEncodedValueHeader(valueType, index-1);
180        write(tempBuf, 0, index);
181    }
182
183    public void writeEncodedLong(int valueType, long value) throws IOException {
184        int index = 0;
185        if (value >= 0) {
186            while (value > 0x7f) {
187                tempBuf[index++] = (byte)value;
188                value >>= 8;
189            }
190        } else {
191            while (value < -0x80) {
192                tempBuf[index++] = (byte)value;
193                value >>= 8;
194            }
195        }
196        tempBuf[index++] = (byte)value;
197        writeEncodedValueHeader(valueType, index-1);
198        write(tempBuf, 0, index);
199    }
200
201    public void writeEncodedUint(int valueType, int value) throws IOException {
202        int index = 0;
203        do {
204            tempBuf[index++] = (byte)value;
205            value >>>= 8;
206        } while (value != 0);
207        writeEncodedValueHeader(valueType, index-1);
208        write(tempBuf, 0, index);
209    }
210
211    public void writeEncodedFloat(int valueType, float value) throws IOException {
212        writeRightZeroExtendedInt(valueType, Float.floatToRawIntBits(value));
213    }
214
215    protected void writeRightZeroExtendedInt(int valueType, int value) throws IOException {
216        int index = 3;
217        do {
218            tempBuf[index--] = (byte)((value & 0xFF000000) >>> 24);
219            value <<= 8;
220        } while (value != 0);
221
222        int firstElement = index+1;
223        int encodedLength = 4-firstElement;
224        writeEncodedValueHeader(valueType, encodedLength - 1);
225        write(tempBuf, firstElement, encodedLength);
226    }
227
228    public void writeEncodedDouble(int valueType, double value) throws IOException {
229        writeRightZeroExtendedLong(valueType, Double.doubleToRawLongBits(value));
230    }
231
232    protected void writeRightZeroExtendedLong(int valueType, long value) throws IOException {
233        int index = 7;
234        do {
235            tempBuf[index--] = (byte)((value & 0xFF00000000000000L) >>> 56);
236            value <<= 8;
237        } while (value != 0);
238
239        int firstElement = index+1;
240        int encodedLength = 8-firstElement;
241        writeEncodedValueHeader(valueType, encodedLength - 1);
242        write(tempBuf, firstElement, encodedLength);
243    }
244
245    public void writeString(String string) throws IOException {
246        int len = string.length();
247
248        // make sure we have enough room in the temporary buffer
249        if (tempBuf.length <= string.length()*3) {
250            tempBuf = new byte[string.length()*3];
251        }
252
253        final byte[] buf = tempBuf;
254
255        int bufPos = 0;
256        for (int i = 0; i < len; i++) {
257            char c = string.charAt(i);
258            if ((c != 0) && (c < 0x80)) {
259                buf[bufPos++] = (byte)c;
260            } else if (c < 0x800) {
261                buf[bufPos++] = (byte)(((c >> 6) & 0x1f) | 0xc0);
262                buf[bufPos++] = (byte)((c & 0x3f) | 0x80);
263            } else {
264                buf[bufPos++] = (byte)(((c >> 12) & 0x0f) | 0xe0);
265                buf[bufPos++] = (byte)(((c >> 6) & 0x3f) | 0x80);
266                buf[bufPos++] = (byte)((c & 0x3f) | 0x80);
267            }
268        }
269        write(buf, 0, bufPos);
270    }
271
272    public void align() throws IOException {
273        int zeros = (-getPosition()) & 3;
274        if (zeros > 0) {
275            write(zeroBuf, 0, zeros);
276        }
277    }
278
279    public int getPosition() {
280        return filePosition;
281    }
282}
283