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