com.google.dexmaker
Class DexMaker

java.lang.Object
  extended by com.google.dexmaker.DexMaker

public final class DexMaker
extends Object

Generates a Dalvik EXecutable (dex) file for execution on Android. Dex files define classes and interfaces, including their member methods and fields, executable code, and debugging information. They also define annotations, though this API currently has no facility to create a dex file that contains annotations.

This library is intended to satisfy two use cases:

Example: Fibonacci

To illustrate how this API is used, we'll use DexMaker to generate a class equivalent to the following Java source:
 package com.publicobject.fib;

 public class Fibonacci {
   public static int fib(int i) {
     if (i < 2) {
       return i;
     }
     return fib(i - 1) + fib(i - 2);
   }
 }

We start by creating a TypeId to identify the generated Fibonacci class. DexMaker identifies types by their internal names like Ljava/lang/Object; rather than their Java identifiers like java.lang.Object.

   TypeId<?> fibonacci = TypeId.get("Lcom/google/dexmaker/examples/Fibonacci;");
 

Next we declare the class. It allows us to specify the type's source file for stack traces, its modifiers, its superclass, and the interfaces it implements. In this case, Fibonacci is a public class that extends from Object:

   String fileName = "Fibonacci.generated";
   DexMaker dexMaker = new DexMaker();
   dexMaker.declare(fibonacci, fileName, Modifier.PUBLIC, TypeId.OBJECT);
 
It is illegal to declare members of a class without also declaring the class itself.

To make it easier to go from our Java method to dex instructions, we'll manually translate it to pseudocode fit for an assembler. We need to replace control flow like if() blocks and for() loops with labels and branches. We'll also avoid performing multiple operations in one statement, using local variables to hold intermediate values as necessary:

   int constant1 = 1;
   int constant2 = 2;
   if (i < constant2) goto baseCase;
   int a = i - constant1;
   int b = i - constant2;
   int c = fib(a);
   int d = fib(b);
   int result = c + d;
   return result;
 baseCase:
   return i;
 

We look up the MethodId for the method on the declaring type. This takes the method's return type (possibly TypeId.VOID), its name and its parameters types. Next we declare the method, specifying its modifiers by bitwise ORing constants from Modifier. The declare call returns a Code object, which we'll use to define the method's instructions.

   MethodId<?, Integer> fib = fibonacci.getMethod(TypeId.INT, "fib", TypeId.INT);
   Code code = dexMaker.declare(fib, Modifier.PUBLIC | Modifier.STATIC);
 

One limitation of DexMaker's API is that it requires all local variables to be created before any instructions are emitted. Use newLocal() to create a new local variable. The method's parameters are exposed as locals using getParameter(). For non-static methods the this pointer is exposed using getThis(). Here we declare all of the local variables that we'll need for our fib() method:

   Local<Integer> i = code.getParameter(0, TypeId.INT);
   Local<Integer> constant1 = code.newLocal(TypeId.INT);
   Local<Integer> constant2 = code.newLocal(TypeId.INT);
   Local<Integer> a = code.newLocal(TypeId.INT);
   Local<Integer> b = code.newLocal(TypeId.INT);
   Local<Integer> c = code.newLocal(TypeId.INT);
   Local<Integer> d = code.newLocal(TypeId.INT);
   Local<Integer> result = code.newLocal(TypeId.INT);
 

Notice that Local has a type parameter of Integer. This is useful for generating code that works with existing types like String and Integer, but it can be a hindrance when generating code that involves new types. For this reason you may prefer to use raw types only and add @SuppressWarnings("unsafe") on your calling code. This will yield the same result but you won't get IDE support if you make a type error.

We're ready to start defining our method's instructions. The Code class catalogs the available instructions and their use.

   code.loadConstant(constant1, 1);
   code.loadConstant(constant2, 2);
   Label baseCase = new Label();
   code.compare(Comparison.LT, baseCase, i, constant2);
   code.op(BinaryOp.SUBTRACT, a, i, constant1);
   code.op(BinaryOp.SUBTRACT, b, i, constant2);
   code.invokeStatic(fib, c, a);
   code.invokeStatic(fib, d, b);
   code.op(BinaryOp.ADD, result, c, d);
   code.returnValue(result);
   code.mark(baseCase);
   code.returnValue(i);
 

We're done defining the dex file. We just need to write it to the filesystem or load it into the current process. For this example we'll load the generated code into the current process. This only works when the current process is running on Android. We use generateAndLoad() which takes the class loader that will be used as our generated code's parent class loader. It also requires a directory where temporary files can be written.

   ClassLoader loader = dexMaker.generateAndLoad(
       FibonacciMaker.class.getClassLoader(), getDataDirectory());
 
Finally we'll use reflection to lookup our generated class on its class loader and invoke its fib() method:
   Class<?> fibonacciClass = loader.loadClass("com.google.dexmaker.examples.Fibonacci");
   Method fibMethod = fibonacciClass.getMethod("fib", int.class);
   System.out.println(fibMethod.invoke(null, 8));
 


Constructor Summary
DexMaker()
          Creates a new DexMaker instance, which can be used to create a single dex file.
 
Method Summary
 void declare(FieldId<?,?> fieldId, int flags, Object staticValue)
          Declares a field.
 Code declare(MethodId<?,?> method, int flags)
          Declares a method or constructor.
 void declare(TypeId<?> type, String sourceFile, int flags, TypeId<?> supertype, TypeId<?>... interfaces)
          Declares type.
 byte[] generate()
          Generates a dex file and returns its bytes.
 ClassLoader generateAndLoad(ClassLoader parent, File dexDir)
          Generates a dex file and loads its types into the current process.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Constructor Detail

DexMaker

public DexMaker()
Creates a new DexMaker instance, which can be used to create a single dex file.

Method Detail

declare

public void declare(TypeId<?> type,
                    String sourceFile,
                    int flags,
                    TypeId<?> supertype,
                    TypeId<?>... interfaces)
Declares type.

Parameters:
flags - a bitwise combination of Modifier.PUBLIC, Modifier.FINAL and Modifier.ABSTRACT.

declare

public Code declare(MethodId<?,?> method,
                    int flags)
Declares a method or constructor.

Parameters:
flags - a bitwise combination of Modifier.PUBLIC, Modifier.PRIVATE, Modifier.PROTECTED, Modifier.STATIC, Modifier.FINAL and Modifier.SYNCHRONIZED.

Warning: the Modifier.SYNCHRONIZED flag is insufficient to generate a synchronized method. You must also use Code.monitorEnter(com.google.dexmaker.Local) and Code.monitorExit(com.google.dexmaker.Local) to acquire a monitor.


declare

public void declare(FieldId<?,?> fieldId,
                    int flags,
                    Object staticValue)
Declares a field.

Parameters:
flags - a bitwise combination of Modifier.PUBLIC, Modifier.PRIVATE, Modifier.PROTECTED, Modifier.STATIC, Modifier.FINAL, Modifier.VOLATILE, and Modifier.TRANSIENT.
staticValue - a constant representing the initial value for the static field, possibly null. This must be null if this field is non-static.

generate

public byte[] generate()
Generates a dex file and returns its bytes.


generateAndLoad

public ClassLoader generateAndLoad(ClassLoader parent,
                                   File dexDir)
                            throws IOException
Generates a dex file and loads its types into the current process.

All parameters are optional; you may pass null and suitable defaults will be used.

If you opt to provide your own dexDir, take care to ensure that it is not world-writable, otherwise a malicious app may be able to inject code into your process. A suitable parameter is: getApplicationContext().getDir("dx", Context.MODE_PRIVATE);

Parameters:
parent - the parent ClassLoader to be used when loading our generated types
dexDir - the destination directory where generated and optimized dex files will be written.
Throws:
IOException