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 @SuppressWarnings("resource") 80 URLClassLoader loader = new URLClassLoader(new URL[] { url }, 81 DexWrapper.class.getClassLoader()); 82 83 // get the classes. 84 Class<?> mainClass = loader.loadClass(DEX_MAIN); 85 Class<?> consoleClass = loader.loadClass(DEX_CONSOLE); 86 Class<?> argClass = loader.loadClass(DEX_ARGS); 87 88 try { 89 // now get the fields/methods we need 90 mRunMethod = mainClass.getMethod(MAIN_RUN, argClass); 91 92 mArgConstructor = argClass.getConstructor(); 93 mArgOutName = argClass.getField("outName"); //$NON-NLS-1$ 94 mArgJarOutput = argClass.getField("jarOutput"); //$NON-NLS-1$ 95 mArgFileNames = argClass.getField("fileNames"); //$NON-NLS-1$ 96 mArgVerbose = argClass.getField("verbose"); //$NON-NLS-1$ 97 mArgForceJumbo = argClass.getField("forceJumbo"); //$NON-NLS-1$ 98 99 mConsoleOut = consoleClass.getField("out"); //$NON-NLS-1$ 100 mConsoleErr = consoleClass.getField("err"); //$NON-NLS-1$ 101 102 } catch (SecurityException e) { 103 return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_API, e); 104 } catch (NoSuchMethodException e) { 105 return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_Method, e); 106 } catch (NoSuchFieldException e) { 107 return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_Field, e); 108 } 109 110 return Status.OK_STATUS; 111 } catch (MalformedURLException e) { 112 // really this should not happen. 113 return createErrorStatus( 114 String.format(Messages.DexWrapper_Failed_to_load_s, osFilepath), e); 115 } catch (ClassNotFoundException e) { 116 return createErrorStatus( 117 String.format(Messages.DexWrapper_Failed_to_load_s, osFilepath), e); 118 } 119 } 120 121 /** 122 * Removes any reference to the dex library. 123 * <p/> 124 * {@link #loadDex(String)} must be called on the wrapper 125 * before {@link #run(String, String[], boolean, PrintStream, PrintStream)} can 126 * be used again. 127 */ 128 public synchronized void unload() { 129 mRunMethod = null; 130 mArgConstructor = null; 131 mArgOutName = null; 132 mArgJarOutput = null; 133 mArgFileNames = null; 134 mArgVerbose = null; 135 mConsoleOut = null; 136 mConsoleErr = null; 137 System.gc(); 138 } 139 140 /** 141 * Runs the dex command. 142 * The wrapper must have been initialized via {@link #loadDex(String)} first. 143 * 144 * @param osOutFilePath the OS path to the outputfile (classes.dex 145 * @param osFilenames list of input source files (.class and .jar files) 146 * @param forceJumbo force jumbo mode. 147 * @param verbose verbose mode. 148 * @param outStream the stdout console 149 * @param errStream the stderr console 150 * @return the integer return code of com.android.dx.command.dexer.Main.run() 151 * @throws CoreException 152 */ 153 public synchronized int run(String osOutFilePath, Collection<String> osFilenames, 154 boolean forceJumbo, boolean verbose, 155 PrintStream outStream, PrintStream errStream) throws CoreException { 156 157 assert mRunMethod != null; 158 assert mArgConstructor != null; 159 assert mArgOutName != null; 160 assert mArgJarOutput != null; 161 assert mArgFileNames != null; 162 assert mArgForceJumbo != null; 163 assert mArgVerbose != null; 164 assert mConsoleOut != null; 165 assert mConsoleErr != null; 166 167 if (mRunMethod == null) { 168 throw new CoreException(createErrorStatus( 169 String.format(Messages.DexWrapper_Unable_To_Execute_Dex_s, 170 "wrapper was not properly loaded first"), 171 null /*exception*/)); 172 } 173 174 try { 175 // set the stream 176 mConsoleErr.set(null /* obj: static field */, errStream); 177 mConsoleOut.set(null /* obj: static field */, outStream); 178 179 // create the Arguments object. 180 Object args = mArgConstructor.newInstance(); 181 mArgOutName.set(args, osOutFilePath); 182 mArgFileNames.set(args, osFilenames.toArray(new String[osFilenames.size()])); 183 mArgJarOutput.set(args, osOutFilePath.endsWith(SdkConstants.DOT_JAR)); 184 mArgForceJumbo.set(args, forceJumbo); 185 mArgVerbose.set(args, verbose); 186 187 // call the run method 188 Object res = mRunMethod.invoke(null /* obj: static method */, args); 189 190 if (res instanceof Integer) { 191 return ((Integer)res).intValue(); 192 } 193 194 return -1; 195 } catch (Exception e) { 196 Throwable t = e; 197 while (t.getCause() != null) { 198 t = t.getCause(); 199 } 200 201 String msg = t.getMessage(); 202 if (msg == null) { 203 msg = String.format("%s. Check the Eclipse log for stack trace.", 204 t.getClass().getName()); 205 } 206 207 throw new CoreException(createErrorStatus( 208 String.format(Messages.DexWrapper_Unable_To_Execute_Dex_s, msg), t)); 209 } 210 } 211 212 private static IStatus createErrorStatus(String message, Throwable e) { 213 AdtPlugin.log(e, message); 214 AdtPlugin.printErrorToConsole(Messages.DexWrapper_Dex_Loader, message); 215 216 return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message, e); 217 } 218 } 219