001package io.ebean.enhance.entity;
002
003import io.ebean.enhance.asm.ClassVisitor;
004import io.ebean.enhance.asm.FieldVisitor;
005import io.ebean.enhance.asm.Label;
006import io.ebean.enhance.asm.MethodVisitor;
007import io.ebean.enhance.asm.Opcodes;
008import io.ebean.enhance.common.ClassMeta;
009import io.ebean.enhance.common.VisitUtil;
010
011import java.util.List;
012
013import static io.ebean.enhance.common.EnhanceConstants.CLINIT;
014import static io.ebean.enhance.common.EnhanceConstants.INIT;
015import static io.ebean.enhance.common.EnhanceConstants.NOARG_VOID;
016
017/**
018 * Generate the methods based on the list of fields.
019 * <p>
020 * This includes the createCopy, getField and setField methods etc.
021 * </p>
022 */
023public class IndexFieldWeaver implements Opcodes {
024
025  private static final String _EBEAN_PROPS = "_ebean_props";
026
027  public static void addPropertiesField(ClassVisitor cv) {
028    FieldVisitor fv = cv.visitField(ACC_PUBLIC + ACC_STATIC, _EBEAN_PROPS, "[Ljava/lang/String;", null, null);
029    fv.visitEnd();
030  }
031
032  public static void addPropertiesInit(ClassVisitor cv, ClassMeta classMeta) {
033    MethodVisitor mv = cv.visitMethod(ACC_STATIC, CLINIT, NOARG_VOID, null, null);
034    mv.visitCode();
035    addPropertiesInit(mv, classMeta);
036
037    Label l1 = new Label();
038    mv.visitLabel(l1);
039    mv.visitLineNumber(1, l1);
040    mv.visitInsn(RETURN);
041    mv.visitMaxs(4, 0);
042    mv.visitEnd();
043  }
044
045  public static void addPropertiesInit(MethodVisitor mv, ClassMeta classMeta) {
046
047    List<FieldMeta> fields = classMeta.getAllFields();
048
049    Label l0 = new Label();
050    mv.visitLabel(l0);
051    mv.visitLineNumber(1, l0);
052    VisitUtil.visitIntInsn(mv, fields.size());
053    mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
054
055    if (fields.isEmpty()) {
056      if (classMeta.isLog(3)) {
057        classMeta.log("Has no fields?");
058      }
059
060    } else {
061      for (int i=0; i<fields.size(); i++) {
062        FieldMeta field = fields.get(i);
063        mv.visitInsn(DUP);
064        VisitUtil.visitIntInsn(mv, i);
065        mv.visitLdcInsn(field.getName());
066        mv.visitInsn(AASTORE);
067      }
068    }
069
070    mv.visitFieldInsn(PUTSTATIC, classMeta.getClassName(), _EBEAN_PROPS, "[Ljava/lang/String;");
071  }
072
073
074  public static void addGetPropertyNames(ClassVisitor cv, ClassMeta classMeta) {
075
076    MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "_ebean_getPropertyNames", "()[Ljava/lang/String;", null, null);
077    mv.visitCode();
078    Label l0 = new Label();
079    mv.visitLabel(l0);
080    mv.visitLineNumber(13, l0);
081    mv.visitFieldInsn(GETSTATIC, classMeta.getClassName(), _EBEAN_PROPS, "[Ljava/lang/String;");
082    mv.visitInsn(ARETURN);
083    Label l1 = new Label();
084    mv.visitLabel(l1);
085    mv.visitLocalVariable("this", "L" + classMeta.getClassName() + ";", null, l0, l1, 0);
086    mv.visitMaxs(1, 1);
087    mv.visitEnd();
088  }
089
090  public static void addGetPropertyName(ClassVisitor cv, ClassMeta classMeta) {
091    MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "_ebean_getPropertyName", "(I)Ljava/lang/String;", null, null);
092    mv.visitCode();
093    Label l0 = new Label();
094    mv.visitLabel(l0);
095    mv.visitLineNumber(16, l0);
096    mv.visitFieldInsn(GETSTATIC, classMeta.getClassName(), _EBEAN_PROPS, "[Ljava/lang/String;");
097    mv.visitVarInsn(ILOAD, 1);
098    mv.visitInsn(AALOAD);
099    mv.visitInsn(ARETURN);
100    Label l1 = new Label();
101    mv.visitLabel(l1);
102    mv.visitLocalVariable("this", "L" + classMeta.getClassName() + ";", null, l0, l1, 0);
103    mv.visitLocalVariable("pos", "I", null, l0, l1, 1);
104    mv.visitMaxs(2, 2);
105    mv.visitEnd();
106  }
107
108  public static void addMethods(ClassVisitor cv, ClassMeta classMeta) {
109
110    List<FieldMeta> fields = classMeta.getAllFields();
111    if (fields.isEmpty()) {
112      return;
113    }
114
115    if (classMeta.isLog(3)) {
116      classMeta.log("fields size:" + fields.size()+" "+fields.toString());
117    }
118
119    generateGetField(cv, classMeta, fields, false);
120    generateGetField(cv, classMeta, fields, true);
121
122    generateSetField(cv, classMeta, fields, false);
123    generateSetField(cv, classMeta, fields, true);
124
125    if (classMeta.hasEqualsOrHashCode()) {
126      // equals or hashCode is already implemented
127      if (classMeta.isLog(3)) {
128        classMeta.log("... skipping add equals() ... already has equals() hashcode() methods");
129      }
130      return;
131    }
132
133    // search for the id field...
134    int idIndex = -1;
135    FieldMeta idFieldMeta = null;
136
137    // find id field only local to this class
138    for (int i = 0; i < fields.size(); i++) {
139      FieldMeta fieldMeta = fields.get(i);
140      if (fieldMeta.isId() && fieldMeta.isLocalField(classMeta)) {
141        if (idIndex == -1) {
142          // we have found an id field
143          idIndex = i;
144          idFieldMeta = fieldMeta;
145        } else {
146          // there are 2 or more id fields
147          idIndex = -2;
148        }
149      }
150    }
151
152    if (idIndex == -2) {
153      // there are 2 or more id fields?
154      if (classMeta.isLog(1)) {
155        classMeta.log("has 2 or more id fields. Not adding equals() method.");
156      }
157
158    } else if (idIndex == -1) {
159      // there are no id fields local to this type
160      if (classMeta.isLog(3)) {
161        classMeta.log("has no id fields on this type. Not adding equals() method. Expected when Id property on superclass.");
162      }
163
164    } else {
165      // add the _ebean_getIdentity(), equals() and hashCode() methods
166      MethodEquals.addMethods(cv, classMeta, idIndex, idFieldMeta);
167    }
168  }
169
170  /**
171  * Generate the invokeGet method.
172  */
173  private static void generateGetField(ClassVisitor cv, ClassMeta classMeta, List<FieldMeta> fields, boolean intercept) {
174
175    String className = classMeta.getClassName();
176
177    MethodVisitor mv;
178    if (intercept) {
179      mv = cv.visitMethod(ACC_PUBLIC, "_ebean_getFieldIntercept", "(I)Ljava/lang/Object;",null, null);
180    } else {
181      mv = cv.visitMethod(ACC_PUBLIC, "_ebean_getField", "(I)Ljava/lang/Object;", null, null);
182    }
183
184    mv.visitCode();
185    Label l0 = new Label();
186    mv.visitLabel(l0);
187    mv.visitLineNumber(1, l0);
188    mv.visitVarInsn(ILOAD, 1);
189
190    Label[] switchLabels = new Label[fields.size()];
191    for (int i = 0; i < switchLabels.length; i++) {
192      switchLabels[i] = new Label();
193    }
194
195    int maxIndex = switchLabels.length - 1;
196
197    Label labelException = new Label();
198    mv.visitTableSwitchInsn(0, maxIndex, labelException, switchLabels);
199
200    for (int i = 0; i < fields.size(); i++) {
201
202      FieldMeta fieldMeta = fields.get(i);
203
204      mv.visitLabel(switchLabels[i]);
205      mv.visitLineNumber(1, switchLabels[i]);
206      mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
207      mv.visitVarInsn(ALOAD, 0);
208
209      fieldMeta.appendSwitchGet(mv, classMeta, intercept);
210
211      mv.visitInsn(ARETURN);
212    }
213
214    mv.visitLabel(labelException);
215    mv.visitLineNumber(1, labelException);
216    mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
217    mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
218    mv.visitInsn(DUP);
219    mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
220    mv.visitInsn(DUP);
221    mv.visitLdcInsn("Invalid index ");
222    mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", INIT, "(Ljava/lang/String;)V", false);
223    mv.visitVarInsn(ILOAD, 1);
224    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false);
225    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
226    mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", INIT, "(Ljava/lang/String;)V", false);
227    mv.visitInsn(ATHROW);
228
229    Label l5 = new Label();
230    mv.visitLabel(l5);
231    mv.visitLocalVariable("this", "L" + className + ";", null, l0, l5, 0);
232    mv.visitLocalVariable("index", "I", null, l0, l5, 1);
233    mv.visitMaxs(5, 2);
234    mv.visitEnd();
235  }
236
237  /**
238  * Generate the _ebean_setField or _ebean_setFieldBypass method.
239  * <p>
240  * Bypass will bypass the interception. The interception checks that the
241  * property has been loaded and creates oldValues if the bean is being made
242  * dirty for the first time.
243  * </p>
244  */
245  private static void generateSetField(ClassVisitor cv, ClassMeta classMeta, List<FieldMeta> fields,boolean intercept) {
246
247
248    String className = classMeta.getClassName();
249
250    MethodVisitor mv;
251    if (intercept) {
252      mv = cv.visitMethod(ACC_PUBLIC, "_ebean_setFieldIntercept", "(ILjava/lang/Object;)V",
253        null, null);
254    } else {
255      mv = cv.visitMethod(ACC_PUBLIC, "_ebean_setField", "(ILjava/lang/Object;)V", null, null);
256    }
257
258    mv.visitCode();
259    Label l0 = new Label();
260    mv.visitLabel(l0);
261    mv.visitLineNumber(1, l0);
262
263    Label l1 = new Label();
264    mv.visitLabel(l1);
265    mv.visitLineNumber(1, l1);
266    mv.visitVarInsn(ILOAD, 1);
267
268    Label[] switchLabels = new Label[fields.size()];
269    for (int i = 0; i < switchLabels.length; i++) {
270      switchLabels[i] = new Label();
271    }
272
273    Label labelException = new Label();
274
275    int maxIndex = switchLabels.length - 1;
276
277    mv.visitTableSwitchInsn(0, maxIndex, labelException, switchLabels);
278
279    for (int i = 0; i < fields.size(); i++) {
280
281      FieldMeta fieldMeta = fields.get(i);
282
283      mv.visitLabel(switchLabels[i]);
284      mv.visitLineNumber(1, switchLabels[i]);
285
286      mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
287      mv.visitVarInsn(ALOAD, 0);
288      mv.visitVarInsn(ALOAD, 2);
289
290      fieldMeta.appendSwitchSet(mv, classMeta, intercept);
291
292      Label l6 = new Label();
293      mv.visitLabel(l6);
294      mv.visitLineNumber(1, l6);
295      mv.visitInsn(RETURN);
296    }
297
298    mv.visitLabel(labelException);
299    mv.visitLineNumber(1, labelException);
300    mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
301    mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
302    mv.visitInsn(DUP);
303    mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
304    mv.visitInsn(DUP);
305    mv.visitLdcInsn("Invalid index ");
306    mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", INIT, "(Ljava/lang/String;)V", false);
307    mv.visitVarInsn(ILOAD, 1);
308    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false);
309    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
310    mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", INIT, "(Ljava/lang/String;)V", false);
311    mv.visitInsn(ATHROW);
312    Label l9 = new Label();
313    mv.visitLabel(l9);
314    mv.visitLocalVariable("this", "L" + className + ";", null, l0, l9, 0);
315    mv.visitLocalVariable("index", "I", null, l0, l9, 1);
316    mv.visitLocalVariable("o", "Ljava/lang/Object;", null, l0, l9, 2);
317    mv.visitLocalVariable("arg", "Ljava/lang/Object;", null, l0, l9, 3);
318    mv.visitLocalVariable("p", "L" + className + ";", null, l1, l9, 4);
319    mv.visitMaxs(5, 5);
320    mv.visitEnd();
321  }
322
323}