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.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