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