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.EnhanceConstants;
010import io.ebean.enhance.common.VisitUtil;
011
012/**
013 * Generate the equals hashCode method using the identity.
014 * <p>
015 * This will add a _ebean_getIdentity() equals() and hashCode() methods based on
016 * having a single ID property and no existing equals() or hashCode() methods.
017 * </p>
018 */
019public class MethodEquals implements Opcodes, EnhanceConstants {
020
021        private static final String _EBEAN_GET_IDENTITY = "_ebean_getIdentity";
022
023        /**
024         * Adds equals(), hashCode() and _ebean_getIdentity() methods.
025         * <p>
026         * If the class already has a equals() or hashCode() method defined then
027         * these methods are not added (its a noop).
028         * </p>
029         * 
030         * @param idFieldIndex
031         *            the index of the id field
032         */
033        public static void addMethods(ClassVisitor cv, ClassMeta meta, int idFieldIndex, FieldMeta idFieldMeta) {
034
035                if (meta.hasEqualsOrHashCode()) {
036                        // already has a equals or hashcode method...
037                        // so we will not add our identity based one
038                        if (meta.isLog(2)) {
039                                meta.log("already has a equals() or hashCode() method. Not adding the identity based one.");
040                        }
041                } else {
042                        if (meta.isLog(2)) {
043                                meta.log("adding equals() hashCode() and _ebean_getIdentity() with Id field " 
044                                        + idFieldMeta.getName()+ " index:" + idFieldIndex+" primitive:"+idFieldMeta.isPrimitiveType());
045                        }
046                        if (idFieldMeta.isPrimitiveType()){
047                                addGetIdentityPrimitive(cv, meta, idFieldMeta);
048                        } else {
049                                addGetIdentityObject(cv, meta, idFieldIndex);   
050                        }
051                        addEquals(cv, meta);
052                        addHashCode(cv, meta);
053                }
054
055        }
056
057        /**
058         * The identity field used for implementing equals via the
059         * _ebean_getIdentity() method.
060         */
061        public static void addIdentityField(ClassVisitor cv) {
062
063            int access = ACC_PROTECTED + ACC_TRANSIENT;
064                FieldVisitor f0 = cv.visitField(access, IDENTITY_FIELD, "Ljava/lang/Object;", null, null);
065                f0.visitEnd();
066        }
067
068        /**
069         * Generate the _ebean_getIdentity method for primitive id types.
070         * <p>
071         * For primitives we need to check for == 0 rather than null.
072         * <p>
073         * <p>
074         * This is used for implementing equals().
075         * <p>
076         * 
077         * <pre>
078         * private Object _ebean_getIdentity() {
079         *      synchronized (this) {
080         *              if (_ebean_identity != null) {
081         *                      return _ebean_identity;
082         *              }
083         *              
084         *              if (0 != getId()) {
085         *                      _ebean_identity = Integer.valueOf(getId());
086         *              } else {
087         *                      _ebean_identity = new Object();
088         *              }
089         * 
090         *              return _ebean_identity;
091         *      }
092         * }
093         * </pre>
094         */
095        private static void addGetIdentityPrimitive(ClassVisitor cv, ClassMeta classMeta, FieldMeta idFieldMeta) {
096                
097                String className = classMeta.getClassName();
098
099                MethodVisitor mv;
100
101                mv = cv.visitMethod(ACC_PRIVATE, _EBEAN_GET_IDENTITY, "()Ljava/lang/Object;", null, null);
102                mv.visitCode();
103                Label l0 = new Label();
104                Label l1 = new Label();
105                Label l2 = new Label();
106                mv.visitTryCatchBlock(l0, l1, l2, null);
107                Label l3 = new Label();
108                Label l4 = new Label();
109                mv.visitTryCatchBlock(l3, l4, l2, null);
110                Label l5 = new Label();
111                mv.visitTryCatchBlock(l2, l5, l2, null);
112                Label l6 = new Label();
113                mv.visitLabel(l6);
114                mv.visitLineNumber(1, l6);
115                mv.visitVarInsn(ALOAD, 0);
116                mv.visitInsn(DUP);
117                mv.visitVarInsn(ASTORE, 1);
118                mv.visitInsn(MONITORENTER);
119                mv.visitLabel(l0);
120                mv.visitLineNumber(1, l0);
121                mv.visitVarInsn(ALOAD, 0);
122                mv.visitFieldInsn(GETFIELD, className, IDENTITY_FIELD, "Ljava/lang/Object;");
123                mv.visitJumpInsn(IFNULL, l3);
124                Label l7 = new Label();
125                mv.visitLabel(l7);
126                mv.visitLineNumber(1, l7);
127                mv.visitVarInsn(ALOAD, 0);
128                mv.visitFieldInsn(GETFIELD, className, IDENTITY_FIELD, "Ljava/lang/Object;");
129                mv.visitVarInsn(ALOAD, 1);
130                mv.visitInsn(MONITOREXIT);
131                mv.visitLabel(l1);
132                mv.visitInsn(ARETURN);
133                mv.visitLabel(l3);
134                mv.visitLineNumber(1, l3);
135                mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"java/lang/Object"}, 0, null);
136                mv.visitVarInsn(ALOAD, 0);
137                idFieldMeta.appendGetPrimitiveIdValue(mv, classMeta);
138                idFieldMeta.appendCompare(mv, classMeta);
139
140                Label l8 = new Label();
141                mv.visitJumpInsn(IFEQ, l8);
142                Label l9 = new Label();
143                mv.visitLabel(l9);
144                mv.visitLineNumber(1, l9);
145                mv.visitVarInsn(ALOAD, 0);
146                mv.visitVarInsn(ALOAD, 0);
147                idFieldMeta.appendGetPrimitiveIdValue(mv, classMeta);
148                idFieldMeta.appendValueOf(mv);
149                mv.visitFieldInsn(PUTFIELD, className, IDENTITY_FIELD, "Ljava/lang/Object;");
150                Label l10 = new Label();
151                mv.visitJumpInsn(GOTO, l10);
152                mv.visitLabel(l8);
153                mv.visitLineNumber(1, l8);
154                mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
155                mv.visitVarInsn(ALOAD, 0);
156                mv.visitTypeInsn(NEW, "java/lang/Object");
157                mv.visitInsn(DUP);
158                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
159                mv.visitFieldInsn(PUTFIELD, className, IDENTITY_FIELD, "Ljava/lang/Object;");
160                mv.visitLabel(l10);
161                mv.visitLineNumber(1, l10);
162                mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
163                mv.visitVarInsn(ALOAD, 0);
164                mv.visitFieldInsn(GETFIELD, className, IDENTITY_FIELD, "Ljava/lang/Object;");
165                mv.visitVarInsn(ALOAD, 1);
166                mv.visitInsn(MONITOREXIT);
167                mv.visitLabel(l4);
168                mv.visitInsn(ARETURN);
169                mv.visitLabel(l2);
170                mv.visitLineNumber(1, l2);
171                mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
172                mv.visitVarInsn(ASTORE, 2);
173                mv.visitVarInsn(ALOAD, 1);
174                mv.visitInsn(MONITOREXIT);
175                mv.visitLabel(l5);
176                mv.visitVarInsn(ALOAD, 2);
177                mv.visitInsn(ATHROW);
178                Label l11 = new Label();
179                mv.visitLabel(l11);
180                mv.visitLocalVariable("this", "L"+className+";", null, l6, l11, 0);
181                mv.visitMaxs(4, 3);
182                mv.visitEnd();
183        }
184        
185        /**
186         * Generate the _ebean_getIdentity method for used with equals().
187         * 
188         * <pre>
189         * private Object _ebean_getIdentity() {
190         *      synchronized (this) {
191         *              if (_ebean_identity != null) {
192         *                      return _ebean_identity;
193         *              }
194         * 
195         *              Object id = getId();
196         *              if (id != null) {
197         *                      _ebean_identity = id;
198         *              } else {
199         *                      _ebean_identity = new Object();
200         *              }
201         * 
202         *              return _ebean_identity;
203         *      }
204         * }
205         * </pre>
206         */
207        private static void addGetIdentityObject(ClassVisitor cv, ClassMeta classMeta, int idFieldIndex) {
208
209                String className = classMeta.getClassName();
210
211                MethodVisitor mv;
212
213                mv = cv.visitMethod(ACC_PRIVATE, _EBEAN_GET_IDENTITY, "()Ljava/lang/Object;", null, null);
214                mv.visitCode();
215                Label l0 = new Label();
216                Label l1 = new Label();
217                Label l2 = new Label();
218                mv.visitTryCatchBlock(l0, l1, l2, null);
219                Label l3 = new Label();
220                Label l4 = new Label();
221                mv.visitTryCatchBlock(l3, l4, l2, null);
222                Label l5 = new Label();
223                mv.visitTryCatchBlock(l2, l5, l2, null);
224                Label l6 = new Label();
225                mv.visitLabel(l6);
226                mv.visitLineNumber(1, l6);
227                mv.visitVarInsn(ALOAD, 0);
228                mv.visitInsn(DUP);
229                mv.visitVarInsn(ASTORE, 1);
230                mv.visitInsn(MONITORENTER);
231                mv.visitLabel(l0);
232                mv.visitLineNumber(1, l0);
233                mv.visitVarInsn(ALOAD, 0);
234                mv.visitFieldInsn(GETFIELD, className, IDENTITY_FIELD, "Ljava/lang/Object;");
235                mv.visitJumpInsn(IFNULL, l3);
236                Label l7 = new Label();
237                mv.visitLabel(l7);
238                mv.visitLineNumber(1, l7);
239                mv.visitVarInsn(ALOAD, 0);
240                mv.visitFieldInsn(GETFIELD, className, IDENTITY_FIELD, "Ljava/lang/Object;");
241                mv.visitVarInsn(ALOAD, 1);
242                mv.visitInsn(MONITOREXIT);
243                mv.visitLabel(l1);
244                mv.visitInsn(ARETURN);
245                mv.visitLabel(l3);
246                mv.visitLineNumber(1, l3);
247                mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"java/lang/Object"}, 0, null);
248                mv.visitVarInsn(ALOAD, 0);
249                VisitUtil.visitIntInsn(mv, idFieldIndex);
250                mv.visitMethodInsn(INVOKESPECIAL, className, "_ebean_getField", "(I)Ljava/lang/Object;", false);
251                mv.visitVarInsn(ASTORE, 2);
252                Label l8 = new Label();
253                mv.visitLabel(l8);
254                mv.visitLineNumber(1, l8);
255                mv.visitVarInsn(ALOAD, 2);
256                Label l9 = new Label();
257                mv.visitJumpInsn(IFNULL, l9);
258                Label l10 = new Label();
259                mv.visitLabel(l10);
260                mv.visitLineNumber(1, l10);
261                mv.visitVarInsn(ALOAD, 0);
262                mv.visitVarInsn(ALOAD, 2);
263                mv.visitFieldInsn(PUTFIELD, className, IDENTITY_FIELD, "Ljava/lang/Object;");
264                Label l11 = new Label();
265                mv.visitJumpInsn(GOTO, l11);
266                mv.visitLabel(l9);
267                mv.visitLineNumber(1, l9);
268                mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{"java/lang/Object"}, 0, null);
269                mv.visitVarInsn(ALOAD, 0);
270                mv.visitTypeInsn(NEW, "java/lang/Object");
271                mv.visitInsn(DUP);
272                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
273                mv.visitFieldInsn(PUTFIELD, className, IDENTITY_FIELD, "Ljava/lang/Object;");
274                mv.visitLabel(l11);
275                mv.visitLineNumber(1, l11);
276                mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
277                mv.visitVarInsn(ALOAD, 0);
278                mv.visitFieldInsn(GETFIELD, className, IDENTITY_FIELD, "Ljava/lang/Object;");
279                mv.visitVarInsn(ALOAD, 1);
280                mv.visitInsn(MONITOREXIT);
281                mv.visitLabel(l4);
282                mv.visitInsn(ARETURN);
283                mv.visitLabel(l2);
284                mv.visitLineNumber(1, l2);
285                mv.visitFrame(Opcodes.F_FULL, 2, new Object[]{className, "java/lang/Object"}, 1, new Object[]{"java/lang/Throwable"});
286                mv.visitVarInsn(ASTORE, 3);
287                mv.visitVarInsn(ALOAD, 1);
288                mv.visitInsn(MONITOREXIT);
289                mv.visitLabel(l5);
290                mv.visitVarInsn(ALOAD, 3);
291                mv.visitInsn(ATHROW);
292                Label l12 = new Label();
293                mv.visitLabel(l12);
294                mv.visitLocalVariable("this", "L"+className+";", null, l6, l12, 0);
295                mv.visitLocalVariable("tmpId", "Ljava/lang/Object;", null, l8, l2, 2);
296                mv.visitMaxs(3, 4);
297                mv.visitEnd();
298        }
299
300    /**
301     * Generate the equals method.
302     * 
303     * <pre>
304     * public boolean equals(Object o) {
305     *     if (o == null) {
306     *         return false;
307     *     }
308     *     if (!this.getClass().equals(o.getClass())) {
309     *         return false;
310     *     }
311     *     if (o == this) {
312     *         return true;
313     *     }
314     *     return _ebean_getIdentity().equals(((FooEntity)o)._ebean_getIdentity());
315     * }
316     * </pre>
317     */
318        private static void addEquals(ClassVisitor cv, ClassMeta classMeta) {
319
320                MethodVisitor mv;
321
322                mv = cv.visitMethod(ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null);
323                mv.visitCode();
324                Label l0 = new Label();
325                mv.visitLabel(l0);
326                mv.visitLineNumber(1, l0);
327                mv.visitVarInsn(ALOAD, 1);
328                Label l1 = new Label();
329                mv.visitJumpInsn(IFNONNULL, l1);
330                Label l2 = new Label();
331                mv.visitLabel(l2);
332                mv.visitLineNumber(2, l2);
333                mv.visitInsn(ICONST_0);
334                mv.visitInsn(IRETURN);
335                mv.visitLabel(l1);
336                mv.visitLineNumber(3, l1);
337                mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
338                mv.visitVarInsn(ALOAD, 0);
339                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
340                mv.visitVarInsn(ALOAD, 1);
341                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
342                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false);
343                Label l3 = new Label();
344                mv.visitJumpInsn(IFNE, l3);
345                Label l4 = new Label();
346                mv.visitLabel(l4);
347                mv.visitLineNumber(4, l4);
348                mv.visitInsn(ICONST_0);
349                mv.visitInsn(IRETURN);
350                mv.visitLabel(l3);
351                mv.visitLineNumber(5, l3);
352                mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
353                mv.visitVarInsn(ALOAD, 1);
354                mv.visitVarInsn(ALOAD, 0);
355                Label l5 = new Label();
356                mv.visitJumpInsn(IF_ACMPNE, l5);
357                Label l6 = new Label();
358                mv.visitLabel(l6);
359                mv.visitLineNumber(6, l6);
360                mv.visitInsn(ICONST_1);
361                mv.visitInsn(IRETURN);
362                mv.visitLabel(l5);
363                mv.visitLineNumber(7, l5);
364                mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
365                mv.visitVarInsn(ALOAD, 0);
366                mv.visitMethodInsn(INVOKEVIRTUAL, classMeta.getClassName(), _EBEAN_GET_IDENTITY, "()Ljava/lang/Object;", false);
367                mv.visitVarInsn(ALOAD, 1);
368                mv.visitTypeInsn(CHECKCAST, classMeta.getClassName());
369                mv.visitMethodInsn(INVOKEVIRTUAL, classMeta.getClassName(), _EBEAN_GET_IDENTITY, "()Ljava/lang/Object;", false);
370                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false);
371                mv.visitInsn(IRETURN);
372                Label l7 = new Label();
373                mv.visitLabel(l7);
374                mv.visitLocalVariable("this", "L"+classMeta.getClassName()+";", null, l0, l7, 0);
375                mv.visitLocalVariable("obj", "Ljava/lang/Object;", null, l0, l7, 1);
376                mv.visitMaxs(2, 2);
377                mv.visitEnd();
378        }
379
380        /**
381         * Generate a hashCode method used to go with MethodEquals.
382         * 
383         * <pre><code>
384         * public int hashCode() {
385         *      return ebeanGetIdentity().hashCode();
386         * }
387         * </code></pre>
388         */
389        private static void addHashCode(ClassVisitor cv, ClassMeta meta) {
390
391                MethodVisitor mv;
392
393                mv = cv.visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
394                mv.visitCode();
395                Label l0 = new Label();
396                mv.visitLabel(l0);
397                mv.visitLineNumber(1, l0);
398                mv.visitVarInsn(ALOAD, 0);
399                mv.visitMethodInsn(INVOKESPECIAL, meta.getClassName(), _EBEAN_GET_IDENTITY, "()Ljava/lang/Object;", false);
400                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "hashCode", "()I", false);
401                mv.visitInsn(IRETURN);
402                Label l1 = new Label();
403                mv.visitLabel(l1);
404                mv.visitLocalVariable("this", "L" + meta.getClassName() + ";", null, l0, l1, 0);
405                mv.visitMaxs(1, 1);
406                mv.visitEnd();
407
408        }
409
410}