This library is intended to satisfy two use cases:
{@code 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 {@link TypeId} to identify the generated {@code Fibonacci} class. DexMaker identifies types by their internal names like{@code Ljava/lang/Object;} rather than their Java identifiers like {@code java.lang.Object}.
{@code 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, {@code Fibonacci} is a public class that extendsfrom {@code Object}:
{@code 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 {@code if()} blocks and {@code for()} loops with labels andbranches. We'll also avoid performing multiple operations in one statement, using local variables to hold intermediate values as necessary:
{@code 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 {@code MethodId} for the method on the declaring type. Thistakes the method's return type (possibly {@link TypeId#VOID}), its name and its parameters types. Next we declare the method, specifying its modifiers by bitwise ORing constants from {@link java.lang.reflect.Modifier}. The declare call returns a {@link Code} object, which we'll use to define the method'sinstructions.
{@code MethodId, Integer> fib = fibonacci.getMethod(TypeId.INT, "fib", TypeId.INT); Code code = dexMaker.declare(fib, Modifier.PUBLIC | Modifier.STATIC);}
One limitation of {@code DexMaker}'s API is that it requires all local variables to be created before any instructions are emitted. Use {@link Code#newLocal newLocal()} to create a new local variable. The method'sparameters are exposed as locals using {@link Code#getParameter getParameter()}. For non-static methods the {@code this} pointer is exposedusing {@link Code#getThis getThis()}. Here we declare all of the local variables that we'll need for our {@code fib()} method:
{@code Locali = code.getParameter(0, TypeId.INT); Local constant1 = code.newLocal(TypeId.INT); Local constant2 = code.newLocal(TypeId.INT); Local a = code.newLocal(TypeId.INT); Local b = code.newLocal(TypeId.INT); Local c = code.newLocal(TypeId.INT); Local d = code.newLocal(TypeId.INT); Local result = code.newLocal(TypeId.INT);}
Notice that {@link Local} has a type parameter of {@code Integer}. This is useful for generating code that works with existing types like {@code String}and {@code 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 {@code @SuppressWarnings("unsafe")} on your calling code. This will yieldthe 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 {@link Code}class catalogs the available instructions and their use.
{@code 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 {@link #generateAndLoad generateAndLoad()} which takes the class loader that will be used as ourgenerated code's parent class loader. It also requires a directory where temporary files can be written.
{@code 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 {@code fib()} method:
{@code Class> fibonacciClass = loader.loadClass("com.google.dexmaker.examples.Fibonacci"); Method fibMethod = fibonacciClass.getMethod("fib", int.class); System.out.println(fibMethod.invoke(null, 8));}
|
|
|
|
|
|