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