package act.util; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * Licensed 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. * #L% */ import act.asm.*; import act.data.annotation.Data; import org.osgl.$; import org.osgl.util.C; import org.osgl.util.FastStr; import org.osgl.util.S; import java.util.List; /** * A tool to enhance a object by generating common {@link Object} * methods, e.g. {@link Object#equals(Object)} */ public class DataObjectEnhancer extends AppByteCodeEnhancer<DataObjectEnhancer> { private ObjectMetaInfo metaInfo; public DataObjectEnhancer() { super(S.F.startsWith("act.").negate().or(S.F.startsWith("act.fsa"))); } protected DataObjectEnhancer(ClassVisitor cv) { super($.F.<String>yes(), cv); } @Override protected Class<DataObjectEnhancer> subClass() { return DataObjectEnhancer.class; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { Type type = Type.getObjectType(name); Type superType = null == superName ? null : Type.getObjectType(superName); metaInfo = new ObjectMetaInfo(type, superType); super.visit(version, access, name, signature, superName, interfaces); } private static final String EQ_IGNORE = Type.getType(EqualIgnore.class).getDescriptor(); private static final String EQ_FORCE = Type.getType(EqualField.class).getDescriptor(); @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { FieldVisitor fv = super.visitField(access, name, desc, signature, value); boolean isStatic = ((access & ACC_STATIC) != 0); if (isStatic) { return fv; } if (metaInfo.hasDataAnnotation()) { boolean isTransient = ((access & ACC_TRANSIENT) != 0); Type fieldType = Type.getType(desc); final ObjectMetaInfo.FieldMetaInfo fi = metaInfo.addField(name, fieldType, isTransient); return new FieldVisitor(ASM5, fv) { @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (EQ_IGNORE.equals(desc)) { fi.setEqualIgnore(); } else if (EQ_FORCE.equals(desc)) { fi.setEqualForce(); } return super.visitAnnotation(desc, visible); } }; } return fv; } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { AnnotationVisitor def = super.visitAnnotation(desc, visible); if (Type.getType(Data.class).getDescriptor().equals(desc)) { metaInfo.autoObjectAnnotationFound(); return new AnnotationVisitor(ASM5, def) { @Override public void visit(String name, Object value) { if ("callSuper".equals(name)) { String s = String.valueOf(value); if (Boolean.parseBoolean(s)) { metaInfo.requireCallSuper(); } } } }; } return def; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (metaInfo.hasDataAnnotation()) { if (S.eq("hashCode", name) && S.eq("()I", desc)) { metaInfo.hashCodeMethodFound(); } else if (S.eq("equals", name) && S.eq("(Ljava/lang/Object;)Z", desc)) { metaInfo.equalMethodFound(); } } return mv; } @Override public void visitEnd() { if (metaInfo.shouldGenerateEqualsMethod()) { generateEqualsMethod(); } if (metaInfo.shouldGenerateHashCodeMethod()) { generateHashCodeMethod(); } super.visitEnd(); } private void generateEqualsMethod() { MethodVisitor mv = equalsMethodBegin(this); // check if obj == this mv.visitVarInsn(ALOAD, 1); // obj mv.visitVarInsn(ALOAD, 0); // this Label lbl_continue_with_instanceof_check = new Label(); mv.visitJumpInsn(IF_ACMPNE, lbl_continue_with_instanceof_check); mv.visitInsn(ICONST_1); // true mv.visitInsn(IRETURN); // return mv.visitLabel(lbl_continue_with_instanceof_check); // check if obj instance of the type mv.visitVarInsn(ALOAD, 1); // obj Type host = metaInfo.type(); String hostInternalName = host.getInternalName(); mv.visitTypeInsn(INSTANCEOF, hostInternalName); Label lbl_exit_with_false = new Label(); mv.visitJumpInsn(IFEQ, lbl_exit_with_false); // if not instance of then return false // do type cast mv.visitVarInsn(ALOAD, 1); // obj mv.visitTypeInsn(CHECKCAST, hostInternalName); mv.visitVarInsn(ASTORE, 2); // store cast object to that // should we check super result? if (metaInfo.shouldCallSuper()) { mv.visitVarInsn(ALOAD, 0); // load this mv.visitVarInsn(ALOAD, 2); // load that mv.visitMethodInsn(INVOKESPECIAL, metaInfo.superType().getInternalName(), "equals", "(Ljava/lang/Object;)Z", false); mv.visitJumpInsn(IFEQ, lbl_exit_with_false); } // call Osgl.eq(that.f1, this.f1) C.List<ObjectMetaInfo.FieldMetaInfo> fields = metaInfo.fields(); for (ObjectMetaInfo.FieldMetaInfo fi : fields) { fi.addEqualInstructions(host, mv, lbl_exit_with_false); } mv.visitInsn(ICONST_1); // true mv.visitInsn(IRETURN); mv.visitLabel(lbl_exit_with_false); mv.visitInsn(ICONST_0); // false mv.visitInsn(IRETURN); equalsMethodEnd(mv); } private int fieldCount(List<ObjectMetaInfo.FieldMetaInfo> fields) { int cnt = 0; for (ObjectMetaInfo.FieldMetaInfo field : fields) { if (field.eligible()) { cnt++; } } return cnt; } private void generateHashCodeMethod() { MethodVisitor mv = hashCodeMethodBegin(this); Type host = metaInfo.type(); C.List<ObjectMetaInfo.FieldMetaInfo> fields = metaInfo.fields(); int fieldCount = fieldCount(fields); if (fieldCount < 6) { int cnt = 0; for (ObjectMetaInfo.FieldMetaInfo fi : fields) { boolean added = fi.addHashCodeInstruction(host, mv); if (added) { cnt++; } } if (metaInfo.shouldCallSuper()) { mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, metaInfo.superType().getInternalName(), "hashCode", "()I", false); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); cnt++; } hashCodeMethodEnd(mv, cnt); } else { mv.visitTypeInsn(NEW, "java/util/ArrayList"); mv.visitInsn(DUP); mv.visitIntInsn(BIPUSH, fieldCount); mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "(I)V", false); mv.visitVarInsn(ASTORE, 1); for (ObjectMetaInfo.FieldMetaInfo fi : fields) { if (!fi.eligible()) { continue; } mv.visitVarInsn(ALOAD, 1); fi.addHashCodeInstruction(host, mv); mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true); mv.visitInsn(POP); } if (metaInfo.shouldCallSuper()) { mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, metaInfo.superType().getInternalName(), "hashCode", "()I", false); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); } mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESTATIC, "org/osgl/$", "hc", "(Ljava/lang/Object;)I", false); mv.visitInsn(IRETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } } static MethodVisitor equalsMethodBegin(ClassVisitor cw) { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null); mv.visitCode(); return mv; } static void equalsMethodEnd(MethodVisitor mv) { mv.visitMaxs(0, 0); // just pass any number and have ASM to calculate mv.visitEnd(); } static MethodVisitor hashCodeMethodBegin(ClassVisitor cw) { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null); mv.visitCode(); return mv; } static void hashCodeMethodEnd(MethodVisitor mv, int fieldCnt) { FastStr signature; if (fieldCnt < 6) { signature = FastStr.of("Ljava/lang/Object;").times(fieldCnt); } else { signature = FastStr.of("Ljava/lang/Object;").times(5).append("[Ljava/lang/Object;"); } signature = signature.prepend("(").append(")I"); mv.visitMethodInsn(INVOKESTATIC, "org/osgl/Osgl", "hc", signature.toString(), false); mv.visitInsn(IRETURN); mv.visitMaxs(0, 0); // just pass any number and have ASM to calculate mv.visitEnd(); } }