blob: 9bc1fd000a0f12eee35ca49df9f1a0b47813ed4a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.enhance;
import java.lang.reflect.Constructor;
import java.security.AccessController;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.util.InternalException;
import serp.bytecode.BCClass;
import serp.bytecode.BCClassLoader;
import serp.bytecode.BCField;
import serp.bytecode.BCMethod;
import serp.bytecode.Code;
import serp.bytecode.Constants;
import serp.bytecode.Instruction;
import serp.bytecode.JumpInstruction;
import serp.bytecode.LoadInstruction;
import serp.bytecode.Project;
import serp.bytecode.TableSwitchInstruction;
/**
* Factory for creating new {@link DynamicStorage} classes. Can be
* extended to decorate/modify the generated instances behavior.
*
* @author Steve Kim
* @since 0.3.2.0
*/
public class DynamicStorageGenerator {
// prefix for generic generated classes.
private static final String PREFIX = "openjpastorage$";
/**
* Constant to throw an exception on invalid index passed to type set/get
* methods
*/
protected static final int POLICY_EXCEPTION = 0;
/**
* Constant to not generate type set/get methods.
*/
protected static final int POLICY_EMPTY = 1;
/**
* Constant to be as silent as possible during invalid index passed
* to set/get type methods. On getting an Object, for example,
* null will be returned.
* However, on primitive gets, an exception will be thrown.
*/
protected static final int POLICY_SILENT = 2;
// wrappers for primitive types
private static final Class[][] WRAPPERS = new Class[][]{
{ boolean.class, Boolean.class },
{ byte.class, Byte.class },
{ char.class, Character.class },
{ int.class, Integer.class },
{ short.class, Short.class },
{ long.class, Long.class },
{ float.class, Float.class },
{ double.class, Double.class },
};
// primitive types
private static final int[] TYPES = new int[]{
JavaTypes.BOOLEAN,
JavaTypes.BYTE,
JavaTypes.CHAR,
JavaTypes.INT,
JavaTypes.SHORT,
JavaTypes.LONG,
JavaTypes.FLOAT,
JavaTypes.DOUBLE,
JavaTypes.OBJECT
};
// the project/classloader for the classes.
private final Project _project = new Project();
private final BCClassLoader _loader =
AccessController.doPrivileged(J2DoPrivHelper.newBCClassLoaderAction(
_project, AccessController.doPrivileged(J2DoPrivHelper
.getClassLoaderAction(DynamicStorage.class))));
/**
* Generate a generic {@link DynamicStorage} instance with the given
* array of {@link JavaTypes} constants and the given object as
* the user key for generation.
*/
public DynamicStorage generateStorage(int[] types, Object obj) {
if (obj == null)
return null;
String name = getClassName(obj);
BCClass bc = _project.loadClass(name);
declareClasses(bc);
bc.addDefaultConstructor().makePublic();
int objectCount = declareFields(types, bc);
addFactoryMethod(bc);
addFieldCount(bc, types, objectCount);
addSetMethods(bc, types, objectCount);
addGetMethods(bc, types);
addInitialize(bc, objectCount);
decorate(obj, bc, types);
return createFactory(bc);
}
/**
* Return a class name to use for the given user key. By default,
* returns the stringified key prefixed by PREFIX.
*/
protected String getClassName(Object obj) {
return PREFIX + obj.toString();
}
/**
* Return the default field ACCESS constant for generated fields from
* {@link Constants}.
*/
protected int getFieldAccess() {
return Constants.ACCESS_PRIVATE;
}
/**
* Return the name for the generated field at the given index. Returns
* <code>"field" + i</code> by default.
*/
protected String getFieldName(int index) {
return "field" + index;
}
/**
* Return the policy constant for how to create type methods.
*/
protected int getCreateFieldMethods(int type) {
return POLICY_EXCEPTION;
}
/**
* Decorate the generated class.
*/
protected void decorate(Object obj, BCClass cls, int[] types) {
}
/**
* Create a stub factory instance for the given class.
*/
protected DynamicStorage createFactory(BCClass bc) {
try {
Class cls = Class.forName(bc.getName(), false, _loader);
Constructor cons = cls.getConstructor((Class[]) null);
DynamicStorage data = (DynamicStorage) cons.newInstance
((Object[]) null);
_project.clear(); // remove old refs
return data;
} catch (Throwable t) {
throw new InternalException("cons-access", t).setFatal(true);
}
}
/**
* Add interface or superclass declarations to the generated class.
*/
protected void declareClasses(BCClass bc) {
bc.declareInterface(DynamicStorage.class);
}
/**
* Implement the newInstance method.
*/
private void addFactoryMethod(BCClass bc) {
BCMethod method = bc.declareMethod("newInstance",
DynamicStorage.class, null);
Code code = method.getCode(true);
code.anew().setType(bc);
code.dup();
code.invokespecial().setMethod(bc.getName(), "<init>", "void", null);
code.areturn();
code.calculateMaxLocals();
code.calculateMaxStack();
}
/**
* Implement getFieldCount/getObjectCount.
*/
private void addFieldCount(BCClass bc, int[] types, int objectCount) {
BCMethod method = bc.declareMethod("getFieldCount", int.class, null);
Code code = method.getCode(true);
code.constant().setValue(types.length);
code.ireturn();
code.calculateMaxLocals();
code.calculateMaxStack();
method = bc.declareMethod("getObjectCount", int.class, null);
code = method.getCode(true);
code.constant().setValue(objectCount);
code.ireturn();
code.calculateMaxLocals();
code.calculateMaxStack();
}
/**
* Implement initialize.
*/
private void addInitialize(BCClass bc, int objectCount) {
BCMethod meth = bc.declareMethod("initialize", void.class, null);
Code code = meth.getCode(true);
JumpInstruction ifins = null;
if (objectCount > 0) {
// if (objects == null)
// objects = new Object[objectCount];
code.aload().setThis();
code.getfield().setField("objects", Object[].class);
ifins = code.ifnonnull();
code.aload().setThis();
code.constant().setValue(objectCount);
code.anewarray().setType(Object.class);
code.putfield().setField("objects", Object[].class);
}
Instruction ins = code.vreturn();
if (ifins != null)
ifins.setTarget(ins);
code.calculateMaxLocals();
code.calculateMaxStack();
}
/**
* Declare the primitive fields and the object field.
*/
private int declareFields(int[] types, BCClass bc) {
bc.declareField("objects", Object[].class).makePrivate();
int objectCount = 0;
Class type;
for (int i = 0; i < types.length; i++) {
type = forType(types[i]);
if (type == Object.class)
objectCount++;
else {
BCField field = bc.declareField(getFieldName(i), type);
field.setAccessFlags(getFieldAccess());
}
}
return objectCount;
}
/**
* Add all the typed set by index method.
*/
private void addSetMethods(BCClass bc, int[] types, int totalObjects) {
for (int type : TYPES) {
addSetMethod(type, bc, types, totalObjects);
}
}
/**
* Add the typed set by index method.
*/
private void addSetMethod(int typeCode, BCClass bc, int[] types,
int totalObjects) {
int handle = getCreateFieldMethods(typeCode);
if (handle == POLICY_EMPTY)
return;
Class type = forType(typeCode);
// public void set<Type> (int field, <type> val)
String name = Object.class.equals(type) ? "Object" : StringUtil.capitalize(type.getName());
name = "set" + name;
BCMethod method = bc.declareMethod(name, void.class,
new Class[]{ int.class, type });
method.makePublic();
Code code = method.getCode(true);
// switch (field)
code.aload().setParam(0);
TableSwitchInstruction tabins = code.tableswitch();
tabins.setLow(0);
tabins.setHigh(types.length - 1);
Instruction defaultIns;
if (handle == POLICY_SILENT) {
defaultIns = code.vreturn();
}
else {
defaultIns = throwException(code, IllegalArgumentException.class);
}
tabins.setDefaultTarget(defaultIns);
int objectCount = 0;
for (int i = 0; i < types.length; i++) {
// default: throw new IllegalArgumentException
if (!isCompatible(types[i], typeCode)) {
tabins.addTarget(tabins.getDefaultTarget());
continue;
}
tabins.addTarget(code.aload().setThis());
if (typeCode >= JavaTypes.OBJECT) {
// if (objects == null)
// objects = new Object[totalObjects];
code.aload().setThis();
code.getfield().setField("objects", Object[].class);
JumpInstruction ifins = code.ifnonnull();
code.aload().setThis();
code.constant().setValue(totalObjects);
code.anewarray().setType(Object.class);
code.putfield().setField("objects", Object[].class);
// objects[objectCount] = val;
ifins.setTarget(code.aload().setThis());
code.getfield().setField("objects", Object[].class);
code.constant().setValue(objectCount);
code.aload().setParam(1);
code.aastore();
objectCount++;
} else {
// case i: fieldi = val;
LoadInstruction load = code.xload();
load.setType(type);
load.setParam(1);
code.putfield().setField("field" + i, type);
}
// return
code.vreturn();
}
code.calculateMaxLocals();
code.calculateMaxStack();
}
/**
* Add all typed get by index method for the given fields.
*/
private void addGetMethods(BCClass bc, int[] types) {
for (int type : TYPES) {
addGetMethod(type, bc, types);
}
}
/**
* Add typed get by index method.
*/
private void addGetMethod(int typeCode, BCClass bc, int[] types) {
int handle = getCreateFieldMethods(typeCode);
if (handle == POLICY_EMPTY)
return;
Class type = forType(typeCode);
// public <type> get<Type>Field (int field)
String name = Object.class.equals(type) ? "Object" :
StringUtil.capitalize(type.getName());
name = "get" + name;
BCMethod method = bc.declareMethod(name, type,
new Class[]{ int.class });
method.makePublic();
Code code = method.getCode(true);
// switch (field)
code.aload().setParam(0);
TableSwitchInstruction tabins = code.tableswitch();
tabins.setLow(0);
tabins.setHigh(types.length - 1);
Instruction defaultIns = null;
if (typeCode == JavaTypes.OBJECT && handle == POLICY_SILENT) {
defaultIns = code.constant().setNull();
code.areturn();
} else
defaultIns = throwException
(code, IllegalArgumentException.class);
tabins.setDefaultTarget(defaultIns);
int objectCount = 0;
for (int i = 0; i < types.length; i++) {
// default: throw new IllegalArgumentException
if (!isCompatible(types[i], typeCode)) {
tabins.addTarget(tabins.getDefaultTarget());
continue;
}
tabins.addTarget(code.aload().setThis());
if (typeCode >= JavaTypes.OBJECT) {
// if (objects == null)
// return null;
// return objects[objectCount];
code.aload().setThis();
code.getfield().setField("objects", Object[].class);
JumpInstruction ifins = code.ifnonnull();
code.constant().setNull();
code.areturn();
ifins.setTarget(code.aload().setThis());
code.getfield().setField("objects", Object[].class);
code.constant().setValue(objectCount);
code.aaload();
code.areturn();
objectCount++;
} else {
// case i: return fieldi;
code.getfield().setField("field" + i, type);
code.xreturn().setType(type);
}
}
code.calculateMaxLocals();
code.calculateMaxStack();
}
/////////////
// Utilities
/////////////
/**
* Clear code associated with the given method signature, and return
* the empty code. Will return null if the method should be empty.
*/
protected Code replaceMethod(BCClass bc, String name, Class retType,
Class[] args, boolean remove) {
bc.removeDeclaredMethod(name, args);
BCMethod meth = bc.declareMethod(name, retType, args);
Code code = meth.getCode(true);
if (!remove)
return code;
code.xreturn().setType(retType);
code.calculateMaxStack();
code.calculateMaxLocals();
return null;
}
/**
* Add a bean field of the given name and type.
*/
protected BCField addBeanField(BCClass bc, String name, Class type) {
if (name == null)
throw new IllegalArgumentException("name == null");
// private <type> <name>
BCField field = bc.declareField(name, type);
field.setAccessFlags(getFieldAccess());
name = StringUtil.capitalize(name);
// getter
String prefix = (type == boolean.class) ? "is" : "get";
BCMethod method = bc.declareMethod(prefix + name, type, null);
method.makePublic();
Code code = method.getCode(true);
code.aload().setThis();
code.getfield().setField(field);
code.xreturn().setType(type);
code.calculateMaxStack();
code.calculateMaxLocals();
// setter
method = bc.declareMethod("set" + name, void.class,
new Class[]{ type });
method.makePublic();
code = method.getCode(true);
code.aload().setThis();
code.xload().setParam(0).setType(type);
code.putfield().setField(field);
code.vreturn();
code.calculateMaxStack();
code.calculateMaxLocals();
return field;
}
/**
* Return true if the given field type and storage type are compatible.
*/
protected boolean isCompatible(int fieldType, int storageType) {
if (storageType == JavaTypes.OBJECT)
return fieldType >= JavaTypes.OBJECT;
return fieldType == storageType;
}
/**
* Throw an exception of the given type.
*/
protected Instruction throwException(Code code, Class type) {
Instruction ins = code.anew().setType(type);
code.dup();
code.invokespecial().setMethod(type, "<init>", void.class, null);
code.athrow();
return ins;
}
/**
* Return the proper type for the given {@link JavaTypes} constant.
*/
protected Class forType(int type) {
switch (type) {
case JavaTypes.BOOLEAN:
return boolean.class;
case JavaTypes.BYTE:
return byte.class;
case JavaTypes.CHAR:
return char.class;
case JavaTypes.INT:
return int.class;
case JavaTypes.SHORT:
return short.class;
case JavaTypes.LONG:
return long.class;
case JavaTypes.FLOAT:
return float.class;
case JavaTypes.DOUBLE:
return double.class;
}
return Object.class;
}
/**
* get the wrapper for the given {@link JavaTypes} constant.
*/
protected Class getWrapper(int type) {
return getWrapper(forType(type));
}
/**
* Get the wrapper for the given type.
*/
protected Class getWrapper(Class c) {
for (Class[] wrapper : WRAPPERS) {
if (wrapper[0].equals(c))
return wrapper[1];
}
return c;
}
}