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