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