1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.build; 18 19 import com.android.SdkConstants; 20 import com.android.ide.eclipse.adt.AdtPlugin; 21 22 import org.eclipse.core.runtime.CoreException; 23 import org.eclipse.core.runtime.IStatus; 24 import org.eclipse.core.runtime.Status; 25 26 import java.io.File; 27 import java.io.PrintStream; 28 import java.lang.reflect.Constructor; 29 import java.lang.reflect.Field; 30 import java.lang.reflect.Method; 31 import java.net.MalformedURLException; 32 import java.net.URL; 33 import java.net.URLClassLoader; 34 import java.util.Collection; 35 36 /** 37 * Wrapper to access dx.jar through reflection. 38 * <p/>Since there is no proper api to call the method in the dex library, this wrapper is going 39 * to access it through reflection. 40 */ 41 public final class DexWrapper { 42 43 private final static String DEX_MAIN = "com.android.dx.command.dexer.Main"; //$NON-NLS-1$ 44 private final static String DEX_CONSOLE = "com.android.dx.command.DxConsole"; //$NON-NLS-1$ 45 private final static String DEX_ARGS = "com.android.dx.command.dexer.Main$Arguments"; //$NON-NLS-1$ 46 47 private final static String MAIN_RUN = "run"; //$NON-NLS-1$ 48 49 private Method mRunMethod; 50 51 private Constructor<?> mArgConstructor; 52 private Field mArgOutName; 53 private Field mArgVerbose; 54 private Field mArgJarOutput; 55 private Field mArgFileNames; 56 private Field mArgForceJumbo; 57 58 private Field mConsoleOut; 59 private Field mConsoleErr; 60 61 /** 62 * Loads the dex library from a file path. 63 * 64 * The loaded library can be used via 65 * {@link DexWrapper#run(String, String[], boolean, PrintStream, PrintStream)}. 66 * 67 * @param osFilepath the location of the dx.jar file. 68 * @return an IStatus indicating the result of the load. 69 */ 70 public synchronized IStatus loadDex(String osFilepath) { 71 try { 72 File f = new File(osFilepath); 73 if (f.isFile() == false) { 74 return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, String.format( 75 Messages.DexWrapper_s_does_not_exists, osFilepath)); 76 } 77 URL url = f.toURI().toURL(); 78 79 URLClassLoader loader = new URLClassLoader(new URL[] { url }, 80 DexWrapper.class.getClassLoader()); 81 82 // get the classes. 83 Class<?> mainClass = loader.loadClass(DEX_MAIN); 84 Class<?> consoleClass = loader.loadClass(DEX_CONSOLE); 85 Class<?> argClass = loader.loadClass(DEX_ARGS); 86 87 try { 88 // now get the fields/methods we need 89 mRunMethod = mainClass.getMethod(MAIN_RUN, argClass); 90 91 mArgConstructor = argClass.getConstructor(); 92 mArgOutName = argClass.getField("outName"); //$NON-NLS-1$ 93 mArgJarOutput = argClass.getField("jarOutput"); //$NON-NLS-1$ 94 mArgFileNames = argClass.getField("fileNames"); //$NON-NLS-1$ 95 mArgVerbose = argClass.getField("verbose"); //$NON-NLS-1$ 96 mArgForceJumbo = argClass.getField("forceJumbo"); //$NON-NLS-1$ 97 98 mConsoleOut = consoleClass.getField("out"); //$NON-NLS-1$ 99 mConsoleErr = consoleClass.getField("err"); //$NON-NLS-1$ 100 101 } catch (SecurityException e) { 102 return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_API, e); 103 } catch (NoSuchMethodException e) { 104 return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_Method, e); 105 } catch (NoSuchFieldException e) { 106 return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_Field, e); 107 } 108 109 return Status.OK_STATUS; 110 } catch (MalformedURLException e) { 111 // really this should not happen. 112 return createErrorStatus( 113 String.format(Messages.DexWrapper_Failed_to_load_s, osFilepath), e); 114 } catch (ClassNotFoundException e) { 115 return createErrorStatus( 116 String.format(Messages.DexWrapper_Failed_to_load_s, osFilepath), e); 117 } 118 } 119 120 /** 121 * Removes any reference to the dex library. 122 * <p/> 123 * {@link #loadDex(String)} must be called on the wrapper 124 * before {@link #run(String, String[], boolean, PrintStream, PrintStream)} can 125 * be used again. 126 */ 127 public synchronized void unload() { 128 mRunMethod = null; 129 mArgConstructor = null; 130 mArgOutName = null; 131 mArgJarOutput = null; 132 mArgFileNames = null; 133 mArgVerbose = null; 134 mConsoleOut = null; 135 mConsoleErr = null; 136 System.gc(); 137 } 138 139 /** 140 * Runs the dex command. 141 * The wrapper must have been initialized via {@link #loadDex(String)} first. 142 * 143 * @param osOutFilePath the OS path to the outputfile (classes.dex 144 * @param osFilenames list of input source files (.class and .jar files) 145 * @param forceJumbo force jumbo mode. 146 * @param verbose verbose mode. 147 * @param outStream the stdout console 148 * @param errStream the stderr console 149 * @return the integer return code of com.android.dx.command.dexer.Main.run() 150 * @throws CoreException 151 */ 152 public synchronized int run(String osOutFilePath, Collection<String> osFilenames, 153 boolean forceJumbo, boolean verbose, 154 PrintStream outStream, PrintStream errStream) throws CoreException { 155 156 assert mRunMethod != null; 157 assert mArgConstructor != null; 158 assert mArgOutName != null; 159 assert mArgJarOutput != null; 160 assert mArgFileNames != null; 161 assert mArgForceJumbo != null; 162 assert mArgVerbose != null; 163 assert mConsoleOut != null; 164 assert mConsoleErr != null; 165 166 if (mRunMethod == null) { 167 throw new CoreException(createErrorStatus( 168 String.format(Messages.DexWrapper_Unable_To_Execute_Dex_s, 169 "wrapper was not properly loaded first"), 170 null /*exception*/)); 171 } 172 173 try { 174 // set the stream 175 mConsoleErr.set(null /* obj: static field */, errStream); 176 mConsoleOut.set(null /* obj: static field */, outStream); 177 178 // create the Arguments object. 179 Object args = mArgConstructor.newInstance(); 180 mArgOutName.set(args, osOutFilePath); 181 mArgFileNames.set(args, osFilenames.toArray(new String[osFilenames.size()])); 182 mArgJarOutput.set(args, osOutFilePath.endsWith(SdkConstants.DOT_JAR)); 183 mArgForceJumbo.set(args, forceJumbo); 184 mArgVerbose.set(args, verbose); 185 186 // call the run method 187 Object res = mRunMethod.invoke(null /* obj: static method */, args); 188 189 if (res instanceof Integer) { 190 return ((Integer)res).intValue(); 191 } 192 193 return -1; 194 } catch (Exception e) { 195 Throwable t = e; 196 while (t.getCause() != null) { 197 t = t.getCause(); 198 } 199 200 String msg = t.getMessage(); 201 if (msg == null) { 202 msg = String.format("%s. Check the Eclipse log for stack trace.", 203 t.getClass().getName()); 204 } 205 206 throw new CoreException(createErrorStatus( 207 String.format(Messages.DexWrapper_Unable_To_Execute_Dex_s, msg), t)); 208 } 209 } 210 211 private static IStatus createErrorStatus(String message, Throwable e) { 212 AdtPlugin.log(e, message); 213 AdtPlugin.printErrorToConsole(Messages.DexWrapper_Dex_Loader, message); 214 215 return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message, e); 216 } 217 } 218