/**
* Copyright (C) 2006 Robin Bygrave
*
* This file is part of Ebean.
*
* Ebean is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Ebean is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Ebean; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.avaje.ebean.enhance.agent;
import java.util.List;
import com.avaje.ebean.enhance.asm.ClassVisitor;
import com.avaje.ebean.enhance.asm.Label;
import com.avaje.ebean.enhance.asm.MethodVisitor;
import com.avaje.ebean.enhance.asm.Opcodes;
/**
* Generate the methods based on the list of fields.
* <p>
* This includes the createCopy, getField and setField methods etc.
* </p>
*/
public class IndexFieldWeaver implements Opcodes {
public static void addMethods(ClassVisitor cv, ClassMeta classMeta) {
List<FieldMeta> fields = classMeta.getAllFields();
if (fields.size() == 0) {
classMeta.log("Has no fields?");
return;
}
if (classMeta.isLog(3)) {
classMeta.log("fields size:" + fields.size()+" "+fields.toString());
}
generateCreateCopy(cv, classMeta, fields);
generateGetField(cv, classMeta, fields, false);
generateGetField(cv, classMeta, fields, true);
generateSetField(cv, classMeta, fields, false);
generateSetField(cv, classMeta, fields, true);
generateGetDesc(cv, classMeta, fields);
if (classMeta.hasEqualsOrHashCode()) {
// equals or hashCode is already implemented
if (classMeta.isLog(1)) {
classMeta.log("... skipping add equals() ... already has equals() hashcode() methods");
}
return;
}
// search for the id field...
int idIndex = -1;
FieldMeta idFieldMeta = null;
// find id field only local to this class
for (int i = 0; i < fields.size(); i++) {
FieldMeta fieldMeta = fields.get(i);
if (fieldMeta.isId() && fieldMeta.isLocalField(classMeta)) {
if (idIndex == -1) {
// we have found an id field
idIndex = i;
idFieldMeta = fieldMeta;
} else {
// there are 2 or more id fields
idIndex = -2;
}
}
}
if (idIndex == -2) {
// there are 2 or more id fields?
if (classMeta.isLog(1)) {
classMeta.log("has 2 or more id fields. Not adding equals() method.");
}
} else if (idIndex == -1) {
// there are no id fields local to this type
if (classMeta.isLog(1)) {
classMeta.log("has no id fields on this type. Not adding equals() method. Expected when Id property on superclass.");
}
} else {
// add the _ebean_getIdentity(), equals() and hashCode() methods
MethodEquals.addMethods(cv, classMeta, idIndex, idFieldMeta);
}
}
/**
* Generate the invokeGet method.
*/
private static void generateGetField(ClassVisitor cv, ClassMeta classMeta, List<FieldMeta> fields,
boolean intercept) {
String className = classMeta.getClassName();
MethodVisitor mv = null;
if (intercept) {
mv = cv.visitMethod(ACC_PUBLIC, "_ebean_getFieldIntercept", "(ILjava/lang/Object;)Ljava/lang/Object;",null, null);
} else {
mv = cv.visitMethod(ACC_PUBLIC, "_ebean_getField", "(ILjava/lang/Object;)Ljava/lang/Object;", null, null);
}
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(1, l0);
mv.visitVarInsn(ALOAD, 2);
mv.visitTypeInsn(CHECKCAST, className);
mv.visitVarInsn(ASTORE, 3);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(1, l1);
mv.visitVarInsn(ILOAD, 1);
Label[] switchLabels = new Label[fields.size()];
for (int i = 0; i < switchLabels.length; i++) {
switchLabels[i] = new Label();
}
int maxIndex = switchLabels.length - 1;
Label labelException = new Label();
mv.visitTableSwitchInsn(0, maxIndex, labelException, switchLabels);
for (int i = 0; i < fields.size(); i++) {
FieldMeta fieldMeta = fields.get(i);
mv.visitLabel(switchLabels[i]);
mv.visitLineNumber(1, switchLabels[i]);
mv.visitVarInsn(ALOAD, 3);
fieldMeta.appendSwitchGet(mv, classMeta, intercept);
mv.visitInsn(ARETURN);
}
mv.visitLabel(labelException);
mv.visitLineNumber(1, labelException);
mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitLdcInsn("Invalid index ");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(ATHROW);
Label l5 = new Label();
mv.visitLabel(l5);
mv.visitLocalVariable("this", "L" + className + ";", null, l0, l5, 0);
mv.visitLocalVariable("index", "I", null, l0, l5, 1);
mv.visitLocalVariable("o", "Ljava/lang/Object;", null, l0, l5, 2);
mv.visitLocalVariable("p", "L" + className + ";", null, l1, l5, 3);
mv.visitMaxs(5, 4);
mv.visitEnd();
}
/**
* Generate the _ebean_setField or _ebean_setFieldBypass method.
* <p>
* Bypass will bypass the interception. The interception checks that the
* property has been loaded and creates oldValues if the bean is being made
* dirty for the first time.
* </p>
*/
private static void generateSetField(ClassVisitor cv, ClassMeta classMeta, List<FieldMeta> fields,boolean intercept) {
String className = classMeta.getClassName();
MethodVisitor mv = null;
if (intercept) {
mv = cv.visitMethod(ACC_PUBLIC, "_ebean_setFieldIntercept", "(ILjava/lang/Object;Ljava/lang/Object;)V",
null, null);
} else {
mv = cv.visitMethod(ACC_PUBLIC, "_ebean_setField", "(ILjava/lang/Object;Ljava/lang/Object;)V", null, null);
}
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(1, l0);
mv.visitVarInsn(ALOAD, 2);
mv.visitTypeInsn(CHECKCAST, className);
mv.visitVarInsn(ASTORE, 4);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(1, l1);
mv.visitVarInsn(ILOAD, 1);
Label[] switchLabels = new Label[fields.size()];
for (int i = 0; i < switchLabels.length; i++) {
switchLabels[i] = new Label();
}
Label labelException = new Label();
int maxIndex = switchLabels.length - 1;
mv.visitTableSwitchInsn(0, maxIndex, labelException, switchLabels);
for (int i = 0; i < fields.size(); i++) {
FieldMeta fieldMeta = fields.get(i);
mv.visitLabel(switchLabels[i]);
mv.visitLineNumber(1, switchLabels[i]);
mv.visitVarInsn(ALOAD, 4);
mv.visitVarInsn(ALOAD, 3);
fieldMeta.appendSwitchSet(mv, classMeta, intercept);
Label l6 = new Label();
mv.visitLabel(l6);
mv.visitLineNumber(1, l6);
mv.visitInsn(RETURN);
}
mv.visitLabel(labelException);
mv.visitLineNumber(1, labelException);
mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitLdcInsn("Invalid index ");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(ATHROW);
Label l9 = new Label();
mv.visitLabel(l9);
mv.visitLocalVariable("this", "L" + className + ";", null, l0, l9, 0);
mv.visitLocalVariable("index", "I", null, l0, l9, 1);
mv.visitLocalVariable("o", "Ljava/lang/Object;", null, l0, l9, 2);
mv.visitLocalVariable("arg", "Ljava/lang/Object;", null, l0, l9, 3);
mv.visitLocalVariable("p", "L" + className + ";", null, l1, l9, 4);
mv.visitMaxs(5, 5);
mv.visitEnd();
}
/**
* Generate the _ebean_createCopy() method.
*/
private static void generateCreateCopy(ClassVisitor cv, ClassMeta classMeta, List<FieldMeta> fields) {
String className = classMeta.getClassName();
String copyClassName = className;
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "_ebean_createCopy", "()Ljava/lang/Object;", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(1, l0);
mv.visitTypeInsn(NEW, copyClassName);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, copyClassName, "<init>", "()V");
mv.visitVarInsn(ASTORE, 1);
Label l1 = null;
for (int i = 0; i < fields.size(); i++) {
FieldMeta fieldMeta = fields.get(i);
if (fieldMeta.isPersistent()){
// only copy persistent fields
Label label = new Label();
if (i == 0) {
l1 = label;
}
mv.visitLabel(label);
mv.visitLineNumber(1, label);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 0);
// get put the fields or if using subclassing
// then use the getter setter methods on the
// super object
fieldMeta.addFieldCopy(mv, classMeta);
}
}
Label l4 = new Label();
mv.visitLabel(l4);
mv.visitLineNumber(1, l4);
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(ARETURN);
Label l5 = new Label();
mv.visitLabel(l5);
if (l1 == null){
l1 = l4;
}
mv.visitLocalVariable("this", "L" + className + ";", null, l0, l5, 0);
mv.visitLocalVariable("p", "L" + copyClassName + ";", null, l1, l5, 1);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
private static void generateGetDesc(ClassVisitor cv, ClassMeta classMeta, List<FieldMeta> fields) {
String className = classMeta.getClassName();
int size = fields.size();
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "_ebean_getFieldNames", "()[Ljava/lang/String;", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(1, l0);
visitIntInsn(mv, size);
mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
for (int i = 0; i < size; i++) {
FieldMeta fieldMeta = fields.get(i);
mv.visitInsn(DUP);
visitIntInsn(mv, i);
mv.visitLdcInsn(fieldMeta.getName());
mv.visitInsn(AASTORE);
}
mv.visitInsn(ARETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "L" + className + ";", null, l0, l1, 0);
mv.visitMaxs(4, 1);
mv.visitEnd();
}
/**
* Helper method for visiting an int value.
* <p>
* This can use special constant values for int values from 0 to 5.
* </p>
*/
public static void visitIntInsn(MethodVisitor mv, int value) {
switch (value) {
case 0:
mv.visitInsn(ICONST_0);
break;
case 1:
mv.visitInsn(ICONST_1);
break;
case 2:
mv.visitInsn(ICONST_2);
break;
case 3:
mv.visitInsn(ICONST_3);
break;
case 4:
mv.visitInsn(ICONST_4);
break;
case 5:
mv.visitInsn(ICONST_5);
break;
default:
if (value <= Byte.MAX_VALUE){
mv.visitIntInsn(BIPUSH, value);
} else {
mv.visitIntInsn(SIPUSH, value);
}
}
}
}