blob: 7cde9a93f7530f512e086209877a25293550b2ff [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.dx.gen;
import com.android.dx.dex.DexFormat;
import com.android.dx.dex.DexOptions;
import com.android.dx.dex.code.DalvCode;
import com.android.dx.dex.code.PositionList;
import com.android.dx.dex.code.RopTranslator;
import com.android.dx.dex.file.ClassDefItem;
import com.android.dx.dex.file.DexFile;
import com.android.dx.dex.file.EncodedField;
import com.android.dx.dex.file.EncodedMethod;
import com.android.dx.rop.code.AccessFlags;
import static com.android.dx.rop.code.AccessFlags.*;
import com.android.dx.rop.code.LocalVariableInfo;
import com.android.dx.rop.code.RopMethod;
import com.android.dx.rop.cst.CstString;
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.type.StdTypeList;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
/**
* Define types, fields and methods.
*/
public final class DexGenerator {
private final Map<Type<?>, TypeDeclaration> types
= new LinkedHashMap<Type<?>, TypeDeclaration>();
private TypeDeclaration getTypeDeclaration(Type<?> type) {
TypeDeclaration result = types.get(type);
if (result == null) {
result = new TypeDeclaration(type);
types.put(type, result);
}
return result;
}
/**
* @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#CLASS_FLAGS}.
*/
public void declare(Type<?> type, String sourceFile, int flags,
Type<?> supertype, Type<?>... interfaces) {
TypeDeclaration declaration = getTypeDeclaration(type);
if (declaration.declared) {
throw new IllegalStateException("already declared: " + type);
}
declaration.declared = true;
declaration.flags = flags;
declaration.supertype = supertype;
declaration.sourceFile = sourceFile;
declaration.interfaces = new TypeList(interfaces);
}
/**
* @param flags any flags masked by {@link com.android.dx.rop.code.AccessFlags#METHOD_FLAGS}.
*/
public Code declare(MethodId<?, ?> method, int flags) {
TypeDeclaration typeDeclaration = getTypeDeclaration(method.declaringType);
if (typeDeclaration.methods.containsKey(method)) {
throw new IllegalStateException("already declared: " + method);
}
MethodDeclaration methodDeclaration = new MethodDeclaration(method, flags);
typeDeclaration.methods.put(method, methodDeclaration);
return methodDeclaration.code;
}
/**
* @param flags any flags masked by {@link AccessFlags#FIELD_FLAGS}.
*/
public void declare(FieldId<?, ?> fieldId, int flags, Object staticValue) {
TypeDeclaration typeDeclaration = getTypeDeclaration(fieldId.declaringType);
if (typeDeclaration.fields.containsKey(fieldId)) {
throw new IllegalStateException("already declared: " + fieldId);
}
FieldDeclaration fieldDeclaration = new FieldDeclaration(fieldId, flags, staticValue);
typeDeclaration.fields.put(fieldId, fieldDeclaration);
}
/**
* Returns a .dex formatted file.
*/
public byte[] generate() {
DexFile outputDex = new DexFile(new DexOptions());
for (TypeDeclaration typeDeclaration : types.values()) {
outputDex.add(typeDeclaration.toClassDefItem());
}
try {
return outputDex.toDex(null, false);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Loads the generated types into the current dalvikvm process.
*/
public ClassLoader load(ClassLoader parent) throws IOException {
byte[] dex = generate();
/*
* This implementation currently dumps the dex to the filesystem. It
* jars the emitted .dex for the benefit of Gingerbread and earlier
* devices, which can't load .dex files directly.
*
* TODO: load the dex from memory where supported.
*/
File result = File.createTempFile("Generated", ".jar");
result.deleteOnExit();
JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result));
jarOut.putNextEntry(new JarEntry(DexFormat.DEX_IN_JAR_NAME));
jarOut.write(dex);
jarOut.closeEntry();
jarOut.close();
try {
Class<?> pathClassLoader = Class.forName("dalvik.system.PathClassLoader");
return (ClassLoader) pathClassLoader.getConstructor(String.class, ClassLoader.class)
.newInstance(result.getPath(), parent);
} catch (ClassNotFoundException e) {
throw new UnsupportedOperationException("load() requires a Dalvik VM", e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
} catch (InstantiationException e) {
throw new AssertionError();
} catch (NoSuchMethodException e) {
throw new AssertionError();
} catch (IllegalAccessException e) {
throw new AssertionError();
}
}
private static class TypeDeclaration {
private final Type<?> type;
/** declared state */
private boolean declared;
private int flags;
private Type<?> supertype;
private String sourceFile;
private TypeList interfaces;
private final Map<FieldId, FieldDeclaration> fields
= new LinkedHashMap<FieldId, FieldDeclaration>();
private final Map<MethodId, MethodDeclaration> methods
= new LinkedHashMap<MethodId, MethodDeclaration>();
TypeDeclaration(Type<?> type) {
this.type = type;
}
ClassDefItem toClassDefItem() {
if (!declared) {
throw new IllegalStateException("Undeclared type " + type + " declares members: "
+ fields.keySet() + " " + methods.keySet());
}
DexOptions dexOptions = new DexOptions();
dexOptions.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
CstType thisType = type.constant;
ClassDefItem out = new ClassDefItem(thisType, flags, supertype.constant,
interfaces.ropTypes, new CstString(sourceFile));
for (MethodDeclaration method : methods.values()) {
EncodedMethod encoded = method.toEncodedMethod(dexOptions);
if (method.isDirect()) {
out.addDirectMethod(encoded);
} else {
out.addVirtualMethod(encoded);
}
}
for (FieldDeclaration field : fields.values()) {
EncodedField encoded = field.toEncodedField();
if (field.isStatic()) {
out.addStaticField(encoded, Constants.getConstant(field.staticValue));
} else {
out.addInstanceField(encoded);
}
}
return out;
}
}
static class FieldDeclaration {
final FieldId<?, ?> fieldId;
private final int accessFlags;
private final Object staticValue;
FieldDeclaration(FieldId<?, ?> fieldId, int accessFlags, Object staticValue) {
if ((accessFlags & (AccessFlags.ACC_STATIC)) == 0 && staticValue != null) {
throw new IllegalArgumentException("instance fields may not have a value");
}
this.fieldId = fieldId;
this.accessFlags = accessFlags;
this.staticValue = staticValue;
}
EncodedField toEncodedField() {
return new EncodedField(fieldId.constant, accessFlags);
}
public boolean isStatic() {
return (accessFlags & (AccessFlags.ACC_STATIC)) != 0;
}
}
static class MethodDeclaration {
final MethodId<?, ?> method;
private final int flags;
private final Code code;
public MethodDeclaration(MethodId<?, ?> method, int flags) {
this.method = method;
this.flags = flags;
this.code = new Code(this);
}
boolean isStatic() {
return (flags & ACC_STATIC) != 0;
}
boolean isDirect() {
return (flags & (ACC_STATIC | ACC_PRIVATE | ACC_CONSTRUCTOR)) != 0;
}
EncodedMethod toEncodedMethod(DexOptions dexOptions) {
RopMethod ropMethod = new RopMethod(code.toBasicBlocks(), 0);
LocalVariableInfo locals = null;
DalvCode dalvCode = RopTranslator.translate(
ropMethod, PositionList.NONE, locals, code.paramSize(), dexOptions);
return new EncodedMethod(method.constant, flags, dalvCode, StdTypeList.EMPTY);
}
}
}