1081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson/*
2081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson * Copyright (C) 2011 The Android Open Source Project
3081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson *
4081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson * Licensed under the Apache License, Version 2.0 (the "License");
5081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson * you may not use this file except in compliance with the License.
6081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson * You may obtain a copy of the License at
7081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson *
8081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson *      http://www.apache.org/licenses/LICENSE-2.0
9081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson *
10081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson * Unless required by applicable law or agreed to in writing, software
11081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson * distributed under the License is distributed on an "AS IS" BASIS,
12081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson * See the License for the specific language governing permissions and
14081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson * limitations under the License.
15081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson */
16081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
17081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonpackage com.android.dx.merge;
18081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
19fe107fb6e3f308ac5174ebdc5a794ee880c741d9Jesse Wilsonimport com.android.dex.Dex;
20081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonimport java.io.File;
21081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonimport java.io.FileInputStream;
22081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonimport java.io.FileOutputStream;
23081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonimport java.io.IOException;
24081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonimport java.io.InputStream;
25081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonimport java.io.OutputStream;
2620d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilsonimport java.lang.annotation.Annotation;
2720d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilsonimport java.lang.reflect.Field;
2820d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilsonimport java.lang.reflect.Method;
29081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonimport java.util.Arrays;
30081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonimport java.util.jar.JarEntry;
31081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonimport java.util.jar.JarOutputStream;
32081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonimport junit.framework.TestCase;
33081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
34081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson/**
35081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson * Test that DexMerge works by merging dex files, and then loading them into
36081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson * the current VM.
37081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson */
38081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilsonpublic final class DexMergeTest extends TestCase {
39081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
40081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    public void testFillArrayData() throws Exception {
41081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        ClassLoader loader = mergeAndLoad(
42081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                "/testdata/Basic.dex",
43081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                "/testdata/FillArrayData.dex");
44081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
45081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        Class<?> basic = loader.loadClass("testdata.Basic");
46081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals(1, basic.getDeclaredMethods().length);
47081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
48081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        Class<?> fillArrayData = loader.loadClass("testdata.FillArrayData");
49081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertTrue(Arrays.equals(
50081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                new byte[] { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, -112, -23, 121 },
51081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                (byte[]) fillArrayData.getMethod("newByteArray").invoke(null)));
52081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertTrue(Arrays.equals(
53081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                new char[] { 0xFFFF, 0x4321, 0xABCD, 0, 'a', 'b', 'c' },
54081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                (char[]) fillArrayData.getMethod("newCharArray").invoke(null)));
55081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertTrue(Arrays.equals(
56081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                new long[] { 4660046610375530309L, 7540113804746346429L, -6246583658587674878L },
57081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                (long[]) fillArrayData.getMethod("newLongArray").invoke(null)));
58081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    }
59081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
60081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    public void testTryCatchFinally() throws Exception {
61081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        ClassLoader loader = mergeAndLoad(
62081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                "/testdata/Basic.dex",
63081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                "/testdata/TryCatchFinally.dex");
64081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
65081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        Class<?> basic = loader.loadClass("testdata.Basic");
66081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals(1, basic.getDeclaredMethods().length);
67081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
68081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        Class<?> tryCatchFinally = loader.loadClass("testdata.TryCatchFinally");
69081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        tryCatchFinally.getDeclaredMethod("method").invoke(null);
70081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    }
71081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
72081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    public void testStaticValues() throws Exception {
73081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        ClassLoader loader = mergeAndLoad(
74081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                "/testdata/Basic.dex",
75081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson                "/testdata/StaticValues.dex");
76081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
77081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        Class<?> basic = loader.loadClass("testdata.Basic");
78081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals(1, basic.getDeclaredMethods().length);
79081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
80081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        Class<?> staticValues = loader.loadClass("testdata.StaticValues");
81081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals((byte) 1, staticValues.getField("a").get(null));
82081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals((short) 2, staticValues.getField("b").get(null));
83081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals('C', staticValues.getField("c").get(null));
84081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals(0xabcd1234, staticValues.getField("d").get(null));
85081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals(4660046610375530309L,staticValues.getField("e").get(null));
86081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals(0.5f, staticValues.getField("f").get(null));
87081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals(-0.25, staticValues.getField("g").get(null));
88081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals("this is a String", staticValues.getField("h").get(null));
89081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals(String.class, staticValues.getField("i").get(null));
90081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals("[0, 1]", Arrays.toString((int[]) staticValues.getField("j").get(null)));
91081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals(null, staticValues.getField("k").get(null));
92081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals(true, staticValues.getField("l").get(null));
93081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        assertEquals(false, staticValues.getField("m").get(null));
94081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    }
95081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
9620d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson    public void testAnnotations() throws Exception {
9720d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        ClassLoader loader = mergeAndLoad(
9820d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                "/testdata/Basic.dex",
9920d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                "/testdata/Annotated.dex");
10020d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson
10120d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        Class<?> basic = loader.loadClass("testdata.Basic");
10220d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        assertEquals(1, basic.getDeclaredMethods().length);
10320d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson
10420d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        Class<?> annotated = loader.loadClass("testdata.Annotated");
10520d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        Method method = annotated.getMethod("method", String.class, String.class);
10620d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        Field field = annotated.getField("field");
10720d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson
10820d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        @SuppressWarnings("unchecked")
10920d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        Class<? extends Annotation> marker
11020d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                = (Class<? extends Annotation>) loader.loadClass("testdata.Annotated$Marker");
11120d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson
11220d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        assertEquals("@testdata.Annotated$Marker(a=on class, b=[A, B, C], "
11320d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                + "c=@testdata.Annotated$Nested(e=E1, f=1695938256, g=7264081114510713000), "
11420d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                + "d=[@testdata.Annotated$Nested(e=E2, f=1695938256, g=7264081114510713000)])",
11520d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                annotated.getAnnotation(marker).toString());
11620d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        assertEquals("@testdata.Annotated$Marker(a=on method, b=[], "
11720d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                + "c=@testdata.Annotated$Nested(e=, f=0, g=0), d=[])",
11820d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                method.getAnnotation(marker).toString());
11920d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        assertEquals("@testdata.Annotated$Marker(a=on field, b=[], "
12020d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                + "c=@testdata.Annotated$Nested(e=, f=0, g=0), d=[])",
12120d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                field.getAnnotation(marker).toString());
12220d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson        assertEquals("@testdata.Annotated$Marker(a=on parameter, b=[], "
12320d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                + "c=@testdata.Annotated$Nested(e=, f=0, g=0), d=[])",
12420d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson                method.getParameterAnnotations()[1][0].toString());
12520d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson    }
12620d269ea2a9e8d41b298134f3937c6d959288b53Jesse Wilson
1274ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson    /**
1284ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson     * Merging dex files uses pessimistic sizes that naturally leave gaps in the
1294ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson     * output files. If those gaps grow too large, the merger is supposed to
1304ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson     * compact the result. This exercises that by repeatedly merging a dex with
1314ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson     * itself.
1324ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson     */
1334ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson    public void testMergedOutputSizeIsBounded() throws Exception {
1344ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson        /*
1354ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson         * At the time this test was written, the output would grow ~25% with
1364ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson         * each merge. Setting a low 1KiB ceiling on the maximum size caused
1374ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson         * the file to be compacted every four merges.
1384ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson         */
1394ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson        int steps = 100;
1404ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson        int compactWasteThreshold = 1024;
1414ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson
142fe107fb6e3f308ac5174ebdc5a794ee880c741d9Jesse Wilson        Dex dexA = resourceToDexBuffer("/testdata/Basic.dex");
143fe107fb6e3f308ac5174ebdc5a794ee880c741d9Jesse Wilson        Dex dexB = resourceToDexBuffer("/testdata/TryCatchFinally.dex");
144fe107fb6e3f308ac5174ebdc5a794ee880c741d9Jesse Wilson        Dex merged = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge();
1454ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson
1464ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson        int maxLength = 0;
1474ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson        for (int i = 0; i < steps; i++) {
14809d308b04b7c4fd3ed83a8f8f4c07be67a25478cJesse Wilson            DexMerger dexMerger = new DexMerger(dexA, merged, CollisionPolicy.KEEP_FIRST);
1494ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson            dexMerger.setCompactWasteThreshold(compactWasteThreshold);
1504ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson            merged = dexMerger.merge();
1514ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson            maxLength = Math.max(maxLength, merged.getLength());
1524ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson        }
1534ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson
1544ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson        int maxExpectedLength = dexA.getLength() + dexB.getLength() + compactWasteThreshold;
1554ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson        assertTrue(maxLength + " < " + maxExpectedLength, maxLength < maxExpectedLength);
1564ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson    }
1574ceb6bc262c780c456c4d40222a2d0a56eaec02aJesse Wilson
158324d2f23a7174848f7d130c43c903325c99a93c7Jesse Wilson    public ClassLoader mergeAndLoad(String dexAResource, String dexBResource) throws Exception {
159fe107fb6e3f308ac5174ebdc5a794ee880c741d9Jesse Wilson        Dex dexA = resourceToDexBuffer(dexAResource);
160fe107fb6e3f308ac5174ebdc5a794ee880c741d9Jesse Wilson        Dex dexB = resourceToDexBuffer(dexBResource);
161fe107fb6e3f308ac5174ebdc5a794ee880c741d9Jesse Wilson        Dex merged = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge();
162081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        File mergedDex = File.createTempFile("DexMergeTest", ".classes.dex");
163dc86cd9edc8b80953c8b698a83cdaebf6825d798Jesse Wilson        merged.writeTo(mergedDex);
164081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        File mergedJar = dexToJar(mergedDex);
165324d2f23a7174848f7d130c43c903325c99a93c7Jesse Wilson        // simplify the javac classpath by not depending directly on 'dalvik.system' classes
166324d2f23a7174848f7d130c43c903325c99a93c7Jesse Wilson        return (ClassLoader) Class.forName("dalvik.system.PathClassLoader")
167324d2f23a7174848f7d130c43c903325c99a93c7Jesse Wilson                .getConstructor(String.class, ClassLoader.class)
168324d2f23a7174848f7d130c43c903325c99a93c7Jesse Wilson                .newInstance(mergedJar.getPath(), getClass().getClassLoader());
169081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    }
170081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
171fe107fb6e3f308ac5174ebdc5a794ee880c741d9Jesse Wilson    private Dex resourceToDexBuffer(String resource) throws IOException {
172fe107fb6e3f308ac5174ebdc5a794ee880c741d9Jesse Wilson        return new Dex(getClass().getResourceAsStream(resource));
173081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    }
174081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
175081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    private File dexToJar(File dex) throws IOException {
176081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        File result = File.createTempFile("DexMergeTest", ".jar");
177081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        result.deleteOnExit();
178081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result));
179081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        jarOut.putNextEntry(new JarEntry("classes.dex"));
180081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        copy(new FileInputStream(dex), jarOut);
181081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        jarOut.closeEntry();
182081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        jarOut.close();
183081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        return result;
184081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    }
185081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson
186081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    private void copy(InputStream in, OutputStream out) throws IOException {
187081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        byte[] buffer = new byte[1024];
188081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        int count;
189081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        while ((count = in.read(buffer)) != -1) {
190081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson            out.write(buffer, 0, count);
191081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        }
192081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson        in.close();
193081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson    }
194081c7142b29ccd6e1744b26e097b6a4d7c12f2bdJesse Wilson}
195