| /* |
| * 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; |
| } |
| } |