Home | History | Annotate | Download | only in build
      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