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