APEXMALHAR-1818 Adding BeanClassGenerator for dynamically creating class
Project: http://git-wip-us.apache.org/repos/asf/apex-malhar/repo Commit: http://git-wip-us.apache.org/repos/asf/apex-malhar/commit/b968e4c3 Tree: http://git-wip-us.apache.org/repos/asf/apex-malhar/tree/b968e4c3 Diff: http://git-wip-us.apache.org/repos/asf/apex-malhar/diff/b968e4c3 Branch: refs/heads/master Commit: b968e4c39e983fa24e4c1136eafdd1dd45b7cafb Parents: a059805 Author: Chandni Singh <[email protected]> Authored: Thu Oct 13 17:33:18 2016 +0530 Committer: Chinmay Kolhatkar <[email protected]> Committed: Fri Oct 21 21:24:30 2016 +0530 ---------------------------------------------------------------------- .../malhar/sql/codegen/BeanClassGenerator.java | 796 +++++++++++++++++++ .../sql/codegen/BeanClassGeneratorTest.java | 188 +++++ 2 files changed, 984 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/apex-malhar/blob/b968e4c3/sql/src/main/java/org/apache/apex/malhar/sql/codegen/BeanClassGenerator.java ---------------------------------------------------------------------- diff --git a/sql/src/main/java/org/apache/apex/malhar/sql/codegen/BeanClassGenerator.java b/sql/src/main/java/org/apache/apex/malhar/sql/codegen/BeanClassGenerator.java new file mode 100644 index 0000000..ffeafb5 --- /dev/null +++ b/sql/src/main/java/org/apache/apex/malhar/sql/codegen/BeanClassGenerator.java @@ -0,0 +1,796 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.apex.malhar.sql.codegen; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.codehaus.jettison.json.JSONException; + +import org.apache.apex.malhar.sql.schema.TupleSchemaRegistry; +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.xbean.asm5.ClassWriter; +import org.apache.xbean.asm5.Opcodes; +import org.apache.xbean.asm5.tree.ClassNode; +import org.apache.xbean.asm5.tree.FieldInsnNode; +import org.apache.xbean.asm5.tree.FieldNode; +import org.apache.xbean.asm5.tree.InsnNode; +import org.apache.xbean.asm5.tree.IntInsnNode; +import org.apache.xbean.asm5.tree.JumpInsnNode; +import org.apache.xbean.asm5.tree.LabelNode; +import org.apache.xbean.asm5.tree.LdcInsnNode; +import org.apache.xbean.asm5.tree.MethodInsnNode; +import org.apache.xbean.asm5.tree.MethodNode; +import org.apache.xbean.asm5.tree.TypeInsnNode; +import org.apache.xbean.asm5.tree.VarInsnNode; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +/** + * Creates a bean class on fly. + */ [email protected] +public class BeanClassGenerator +{ + public static final ImmutableMap<String, Character> PRIMITIVE_TYPES; + + static { + Map<String, Character> types = Maps.newHashMap(); + types.put("boolean", 'Z'); + types.put("char", 'C'); + types.put("byte", 'B'); + types.put("short", 'S'); + types.put("int", 'I'); + types.put("float", 'F'); + types.put("long", 'J'); + types.put("double", 'D'); + PRIMITIVE_TYPES = ImmutableMap.copyOf(types); + } + + /** + * Creates a class from give field information and returns byte array of compiled class. + * + * @param fqcn fully qualified class name + * @param fieldList field list for which POJO needs to be generated. + * + * @return byte[] representing compiled class. + * @throws IOException + * @throws JSONException + */ + public static byte[] createAndWriteBeanClass(String fqcn, List<TupleSchemaRegistry.SQLFieldInfo> fieldList) + throws IOException, JSONException + { + return createAndWriteBeanClass(fqcn, fieldList, null); + } + + /** + * Creates a class from given field information and writes it to the output stream. Also returns byte[] of compiled + * class + * + * @param fqcn fully qualified class name + * @param fieldList field list describing the class + * @param outputStream stream to which the class is persisted + * @throws JSONException + * @throws IOException + */ + @SuppressWarnings("unchecked") + public static byte[] createAndWriteBeanClass(String fqcn, List<TupleSchemaRegistry.SQLFieldInfo> fieldList, + FSDataOutputStream outputStream) throws JSONException, IOException + { + ClassNode classNode = new ClassNode(); + + classNode.version = Opcodes.V1_6; //generated class will only run on JRE 1.6 or above + classNode.access = Opcodes.ACC_PUBLIC; + + classNode.name = fqcn.replace('.', '/'); + classNode.superName = "java/lang/Object"; + + // add default constructor + addDefaultConstructor(classNode); + + //for each field in json add a field to this class and a getter and setter for it. + + for (TupleSchemaRegistry.SQLFieldInfo fieldInfo : fieldList) { + String fieldName = fieldInfo.getColumnName(); + String fieldType = fieldInfo.getType().getJavaType().getName(); + String fieldJavaType = getJavaType(fieldType); + + // Add private field + FieldNode fieldNode = new FieldNode(Opcodes.ACC_PRIVATE, fieldName, fieldJavaType, null, null); + classNode.fields.add(fieldNode); + + String fieldNameForMethods = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + + switch (fieldType) { + case "boolean": + addIntGetterNSetter(classNode, fieldName, fieldNameForMethods, fieldJavaType, true); + break; + case "byte": + case "char": + case "short": + case "int": + addIntGetterNSetter(classNode, fieldName, fieldNameForMethods, fieldJavaType, false); + break; + case "long": + addLongGetterNSetter(classNode, fieldName, fieldNameForMethods, fieldJavaType); + break; + case "float": + addFloatGetterNSetter(classNode, fieldName, fieldNameForMethods, fieldJavaType); + break; + case "double": + addDoubleGetterNSetter(classNode, fieldName, fieldNameForMethods, fieldJavaType); + break; + default: + if (fieldJavaType.equals(getJavaType("java.util.Date"))) { + addDateFields(classNode, fieldName, fieldNameForMethods, "java/util/Date"); + } else { + addObjectGetterNSetter(classNode, fieldName, fieldNameForMethods, fieldJavaType); + } + break; + } + } + + addToStringMethod(classNode, fieldList); + addHashCodeMethod(classNode, fieldList); + addEqualsMethod(classNode, fieldList); + + //Write the class + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + classNode.accept(cw); + cw.visitEnd(); + + byte[] classBytes = cw.toByteArray(); + + if (outputStream != null) { + outputStream.write(classBytes); + outputStream.close(); + } + + return classBytes; + } + + private static void addDefaultConstructor(ClassNode classNode) + { + MethodNode constructorNode = new MethodNode(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); + constructorNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + constructorNode.instructions + .add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)); + constructorNode.instructions.add(new InsnNode(Opcodes.RETURN)); + classNode.methods.add(constructorNode); + } + + /** + * Date field is explicitly handled and provided with 3 variants of types of same data. + * 1. java.util.Date format + * 2. long - Epoc time in ms + * 3. int - Epoc time in sec rounded to date + * + * This is purposefully done because SQL operations on Date etc happens on long or int based on whether its a SQL DATE + * field OR SQL TIMESTAMP field. Hence to cater to that 2 more variant of the same data is added to the POJO. + */ + @SuppressWarnings("unchecked") + private static void addDateFields(ClassNode classNode, String fieldName, String fieldNameForMethods, String type) + { + FieldNode fieldNodeSec = new FieldNode(Opcodes.ACC_PRIVATE, fieldName + "Sec", getJavaType("java.lang.Integer"), + null, null); + classNode.fields.add(fieldNodeSec); + FieldNode fieldNodeMs = new FieldNode(Opcodes.ACC_PRIVATE, fieldName + "Ms", getJavaType("java.lang.Long"), null, + null); + classNode.fields.add(fieldNodeMs); + + // Create getter for Date + MethodNode getterNodeDate = new MethodNode(Opcodes.ACC_PUBLIC, "get" + fieldNameForMethods, "()L" + type + ";", + null, null); + getterNodeDate.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + getterNodeDate.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, "L" + type + ";")); + getterNodeDate.instructions.add(new InsnNode(Opcodes.ARETURN)); + classNode.methods.add(getterNodeDate); + + // Create getter for Sec + MethodNode getterNodeSec = new MethodNode(Opcodes.ACC_PUBLIC, "get" + fieldNameForMethods + "Sec", + "()Ljava/lang/Integer;", null, null); + getterNodeSec.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + getterNodeSec.instructions + .add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName + "Sec", "Ljava/lang/Integer;")); + getterNodeSec.instructions.add(new InsnNode(Opcodes.ARETURN)); + classNode.methods.add(getterNodeSec); + + // Create getter for Ms + MethodNode getterNodeMs = new MethodNode(Opcodes.ACC_PUBLIC, "get" + fieldNameForMethods + "Ms", + "()Ljava/lang/Long;", null, null); + getterNodeMs.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + getterNodeMs.instructions + .add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName + "Ms", "Ljava/lang/Long;")); + getterNodeMs.instructions.add(new InsnNode(Opcodes.ARETURN)); + classNode.methods.add(getterNodeMs); + + // Create setter for Date + MethodNode setterNodeDate = new MethodNode(Opcodes.ACC_PUBLIC, "set" + fieldNameForMethods, + "(L" + type + ";)V", null, null); + setterNodeDate.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNodeDate.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + setterNodeDate.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName, "L" + type + ";")); + + setterNodeDate.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNodeDate.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + setterNodeDate.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, type, "getTime", "()J", false)); + setterNodeDate.instructions.add(new LdcInsnNode(new Long(1000))); + setterNodeDate.instructions.add(new InsnNode(Opcodes.LDIV)); + setterNodeDate.instructions.add(new InsnNode(Opcodes.L2I)); + setterNodeDate.instructions + .add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)); + setterNodeDate.instructions + .add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName + "Sec", "Ljava/lang/Integer;")); + + setterNodeDate.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNodeDate.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + setterNodeDate.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, type, "getTime", "()J", false)); + setterNodeDate.instructions + .add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false)); + setterNodeDate.instructions + .add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName + "Ms", "Ljava/lang/Long;")); + + setterNodeDate.instructions.add(new InsnNode(Opcodes.RETURN)); + classNode.methods.add(setterNodeDate); + + // Create setter for Sec + MethodNode setterNodeSec = new MethodNode(Opcodes.ACC_PUBLIC, "set" + fieldNameForMethods + "Sec", + "(Ljava/lang/Integer;)V", null, null); + setterNodeSec.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNodeSec.instructions.add(new TypeInsnNode(Opcodes.NEW, type)); + setterNodeSec.instructions.add(new InsnNode(Opcodes.DUP)); + setterNodeSec.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + setterNodeSec.instructions + .add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false)); + setterNodeSec.instructions.add(new InsnNode(Opcodes.I2L)); + setterNodeSec.instructions.add(new LdcInsnNode(new Long(1000))); + setterNodeSec.instructions.add(new InsnNode(Opcodes.LMUL)); + setterNodeSec.instructions + .add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/util/Date", "<init>", "(J)V", false)); + setterNodeSec.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName, "L" + type + ";")); + + setterNodeSec.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNodeSec.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + setterNodeSec.instructions + .add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName + "Sec", "Ljava/lang/Integer;")); + + setterNodeSec.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNodeSec.instructions.add(new TypeInsnNode(Opcodes.NEW, "java/lang/Long")); + setterNodeSec.instructions.add(new InsnNode(Opcodes.DUP)); + setterNodeSec.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + setterNodeSec.instructions + .add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false)); + setterNodeSec.instructions.add(new InsnNode(Opcodes.I2L)); + setterNodeSec.instructions.add(new LdcInsnNode(new Long(1000))); + setterNodeSec.instructions.add(new InsnNode(Opcodes.LMUL)); + setterNodeSec.instructions + .add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/Long", "<init>", "(J)V", false)); + setterNodeSec.instructions + .add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName + "Ms", "Ljava/lang/Long;")); + + setterNodeSec.instructions.add(new InsnNode(Opcodes.RETURN)); + classNode.methods.add(setterNodeSec); + + // Create setter for Ms + MethodNode setterNodeMs = new MethodNode(Opcodes.ACC_PUBLIC, "set" + fieldNameForMethods + "Ms", + "(Ljava/lang/Long;)V", null, null); + setterNodeMs.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNodeMs.instructions.add(new TypeInsnNode(Opcodes.NEW, type)); + setterNodeMs.instructions.add(new InsnNode(Opcodes.DUP)); + setterNodeMs.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + setterNodeMs.instructions + .add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false)); + setterNodeMs.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/util/Date", "<init>", "(J)V", false)); + setterNodeMs.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName, "L" + type + ";")); + + setterNodeMs.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNodeMs.instructions.add(new TypeInsnNode(Opcodes.NEW, "java/lang/Integer")); + setterNodeMs.instructions.add(new InsnNode(Opcodes.DUP)); + setterNodeMs.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + setterNodeMs.instructions + .add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false)); + setterNodeMs.instructions.add(new LdcInsnNode(new Long(1000))); + setterNodeMs.instructions.add(new InsnNode(Opcodes.LDIV)); + setterNodeMs.instructions.add(new InsnNode(Opcodes.L2I)); + setterNodeMs.instructions + .add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/Integer", "<init>", "(I)V", false)); + setterNodeMs.instructions + .add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName + "Sec", "Ljava/lang/Integer;")); + + setterNodeMs.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNodeMs.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + setterNodeMs.instructions + .add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName + "Ms", "Ljava/lang/Long;")); + + setterNodeMs.instructions.add(new InsnNode(Opcodes.RETURN)); + classNode.methods.add(setterNodeMs); + } + + @SuppressWarnings("unchecked") + private static void addIntGetterNSetter(ClassNode classNode, String fieldName, String fieldNameForMethods, + String fieldJavaType, boolean isBoolean) + { + // Create getter + String getterSignature = "()" + fieldJavaType; + MethodNode getterNode = new MethodNode(Opcodes.ACC_PUBLIC, (isBoolean ? "is" : "get") + fieldNameForMethods, + getterSignature, null, null); + getterNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + getterNode.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, fieldJavaType)); + getterNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + classNode.methods.add(getterNode); + + // Create setter + String setterSignature = '(' + fieldJavaType + ')' + 'V'; + MethodNode setterNode = new MethodNode(Opcodes.ACC_PUBLIC, "set" + fieldNameForMethods, setterSignature, null, + null); + setterNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); + setterNode.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName, fieldJavaType)); + setterNode.instructions.add(new InsnNode(Opcodes.RETURN)); + classNode.methods.add(setterNode); + } + + @SuppressWarnings("unchecked") + private static void addLongGetterNSetter(ClassNode classNode, String fieldName, String fieldNameForMethods, + String fieldJavaType) + { + // Create getter + String getterSignature = "()" + fieldJavaType; + MethodNode getterNode = new MethodNode(Opcodes.ACC_PUBLIC, "get" + fieldNameForMethods, getterSignature, null, + null); + getterNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + getterNode.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, fieldJavaType)); + getterNode.instructions.add(new InsnNode(Opcodes.LRETURN)); + classNode.methods.add(getterNode); + + // Create setter + String setterSignature = '(' + fieldJavaType + ')' + 'V'; + MethodNode setterNode = new MethodNode(Opcodes.ACC_PUBLIC, "set" + fieldNameForMethods, setterSignature, null, + null); + setterNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNode.instructions.add(new VarInsnNode(Opcodes.LLOAD, 1)); + setterNode.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName, fieldJavaType)); + setterNode.instructions.add(new InsnNode(Opcodes.RETURN)); + classNode.methods.add(setterNode); + } + + @SuppressWarnings("unchecked") + private static void addFloatGetterNSetter(ClassNode classNode, String fieldName, String fieldNameForMethods, + String fieldJavaType) + { + // Create getter + String getterSignature = "()" + fieldJavaType; + MethodNode getterNode = new MethodNode(Opcodes.ACC_PUBLIC, "get" + fieldNameForMethods, getterSignature, null, + null); + getterNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + getterNode.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, fieldJavaType)); + getterNode.instructions.add(new InsnNode(Opcodes.FRETURN)); + classNode.methods.add(getterNode); + + // Create setter + String setterSignature = '(' + fieldJavaType + ')' + 'V'; + MethodNode setterNode = new MethodNode(Opcodes.ACC_PUBLIC, "set" + fieldNameForMethods, setterSignature, null, + null); + setterNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNode.instructions.add(new VarInsnNode(Opcodes.FLOAD, 1)); + setterNode.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName, fieldJavaType)); + setterNode.instructions.add(new InsnNode(Opcodes.RETURN)); + classNode.methods.add(setterNode); + } + + @SuppressWarnings("unchecked") + private static void addDoubleGetterNSetter(ClassNode classNode, String fieldName, String fieldNameForMethods, + String fieldJavaType) + { + // Create getter + String getterSignature = "()" + fieldJavaType; + MethodNode getterNode = new MethodNode(Opcodes.ACC_PUBLIC, "get" + fieldNameForMethods, getterSignature, null, + null); + getterNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + getterNode.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, fieldJavaType)); + getterNode.instructions.add(new InsnNode(Opcodes.DRETURN)); + classNode.methods.add(getterNode); + + // Create setter + String setterSignature = '(' + fieldJavaType + ')' + 'V'; + MethodNode setterNode = new MethodNode(Opcodes.ACC_PUBLIC, "set" + fieldNameForMethods, setterSignature, null, + null); + setterNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNode.instructions.add(new VarInsnNode(Opcodes.DLOAD, 1)); + setterNode.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName, fieldJavaType)); + setterNode.instructions.add(new InsnNode(Opcodes.RETURN)); + classNode.methods.add(setterNode); + } + + @SuppressWarnings("unchecked") + private static void addObjectGetterNSetter(ClassNode classNode, String fieldName, String fieldNameForMethods, + String fieldJavaType) + { + // Create getter + String getterSignature = "()" + fieldJavaType; + MethodNode getterNode = new MethodNode(Opcodes.ACC_PUBLIC, "get" + fieldNameForMethods, getterSignature, null, + null); + getterNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + getterNode.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, fieldJavaType)); + getterNode.instructions.add(new InsnNode(Opcodes.ARETURN)); + classNode.methods.add(getterNode); + + // Create setter + String setterSignature = '(' + fieldJavaType + ')' + 'V'; + MethodNode setterNode = new MethodNode(Opcodes.ACC_PUBLIC, "set" + fieldNameForMethods, setterSignature, null, + null); + setterNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + setterNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + setterNode.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldName, fieldJavaType)); + setterNode.instructions.add(new InsnNode(Opcodes.RETURN)); + classNode.methods.add(setterNode); + } + + /** + * Adds a toString method to underlying class. Uses StringBuilder to generate the final string. + * + * @param classNode + * @param fieldList + * @throws JSONException + */ + @SuppressWarnings("unchecked") + private static void addToStringMethod(ClassNode classNode, List<TupleSchemaRegistry.SQLFieldInfo> fieldList) + throws JSONException + { + MethodNode toStringNode = new MethodNode(Opcodes.ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null); + toStringNode.visitAnnotation("Ljava/lang/Override;", true); + + toStringNode.instructions.add(new TypeInsnNode(Opcodes.NEW, "java/lang/StringBuilder")); + toStringNode.instructions.add(new InsnNode(Opcodes.DUP)); + toStringNode.instructions.add(new LdcInsnNode(classNode.name + "{")); + toStringNode.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", + "<init>", "(Ljava/lang/String;)V", false)); + toStringNode.instructions.add(new VarInsnNode(Opcodes.ASTORE, 1)); + toStringNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + + for (int i = 0; i < fieldList.size(); i++) { + TupleSchemaRegistry.SQLFieldInfo info = fieldList.get(i); + String fieldName = info.getColumnName(); + String fieldType = info.getType().getJavaType().getName(); + String fieldJavaType = getJavaType(fieldType); + + if (i != 0) { + toStringNode.instructions.add(new LdcInsnNode(", ")); + toStringNode.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)); + } + + toStringNode.instructions.add(new LdcInsnNode(fieldName + "=")); + toStringNode.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)); + toStringNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + toStringNode.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, fieldJavaType)); + + // There is no StringBuilder.append method for short and byte. It takes it as int. + if (fieldJavaType.equals("S") || fieldJavaType.equals("B")) { + fieldJavaType = "I"; + } + + Character pchar = PRIMITIVE_TYPES.get(fieldType); + if (pchar == null) { + // It's not a primitive type. StringBuilder.append method signature takes Object type. + fieldJavaType = "Ljava/lang/Object;"; + } + + toStringNode.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", + "(" + fieldJavaType + ")Ljava/lang/StringBuilder;", false)); + } + + toStringNode.instructions.add(new LdcInsnNode("}")); + toStringNode.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)); + + toStringNode.instructions.add(new InsnNode(Opcodes.POP)); + toStringNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + toStringNode.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", + "()Ljava/lang/String;", false)); + toStringNode.instructions.add(new InsnNode(Opcodes.ARETURN)); + + classNode.methods.add(toStringNode); + } + + /** + * This will add a hashCode method for class being generated. <br> + * Algorithm is as follows: <br> + * <i><p> + * int hashCode = 7; + * for (field: all fields) { + * hashCode = 23 * hashCode + field.hashCode() + * } + * </p></i> + * <br> + * <b> For primitive field, hashcode implemenented is similar to the one present in its wrapper class. </b> + * + * @param classNode + * @param fieldList + * @throws JSONException + */ + @SuppressWarnings("unchecked") + private static void addHashCodeMethod(ClassNode classNode, List<TupleSchemaRegistry.SQLFieldInfo> fieldList) + throws JSONException + { + MethodNode hashCodeNode = new MethodNode(Opcodes.ACC_PUBLIC, "hashCode", "()I", null, null); + hashCodeNode.visitAnnotation("Ljava/lang/Override;", true); + + hashCodeNode.instructions.add(new IntInsnNode(Opcodes.BIPUSH, 7)); + hashCodeNode.instructions.add(new VarInsnNode(Opcodes.ISTORE, 1)); + + for (TupleSchemaRegistry.SQLFieldInfo fieldInfo : fieldList) { + String fieldName = fieldInfo.getColumnName(); + String fieldType = fieldInfo.getType().getJavaType().getName(); + String fieldJavaType = getJavaType(fieldType); + + hashCodeNode.instructions.add(new IntInsnNode(Opcodes.BIPUSH, 23)); + hashCodeNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); + hashCodeNode.instructions.add(new InsnNode(Opcodes.IMUL)); + hashCodeNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + hashCodeNode.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, fieldJavaType)); + + switch (fieldType) { + case "boolean": + LabelNode falseNode = new LabelNode(); + LabelNode trueNode = new LabelNode(); + hashCodeNode.instructions.add(new JumpInsnNode(Opcodes.IFEQ, falseNode)); + hashCodeNode.instructions.add(new IntInsnNode(Opcodes.SIPUSH, 1231)); + hashCodeNode.instructions.add(new JumpInsnNode(Opcodes.GOTO, trueNode)); + hashCodeNode.instructions.add(falseNode); + hashCodeNode.instructions.add(new IntInsnNode(Opcodes.SIPUSH, 1237)); + hashCodeNode.instructions.add(trueNode); + break; + case "byte": + case "char": + case "short": + case "int": + break; + case "float": + hashCodeNode.instructions + .add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Float", "floatToIntBits", "(F)I", false)); + break; + case "long": + hashCodeNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + hashCodeNode.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, fieldJavaType)); + hashCodeNode.instructions.add(new IntInsnNode(Opcodes.BIPUSH, 32)); + hashCodeNode.instructions.add(new InsnNode(Opcodes.LUSHR)); + hashCodeNode.instructions.add(new InsnNode(Opcodes.LXOR)); + hashCodeNode.instructions.add(new InsnNode(Opcodes.L2I)); + break; + case "double": + hashCodeNode.instructions + .add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Double", "doubleToLongBits", "(D)J", false)); + hashCodeNode.instructions.add(new InsnNode(Opcodes.DUP2)); + hashCodeNode.instructions.add(new VarInsnNode(Opcodes.LSTORE, 2)); + hashCodeNode.instructions.add(new VarInsnNode(Opcodes.LLOAD, 2)); + hashCodeNode.instructions.add(new IntInsnNode(Opcodes.BIPUSH, 32)); + hashCodeNode.instructions.add(new InsnNode(Opcodes.LUSHR)); + hashCodeNode.instructions.add(new InsnNode(Opcodes.LXOR)); + hashCodeNode.instructions.add(new InsnNode(Opcodes.L2I)); + break; + default: + String objectOwnerType = fieldType.replace('.', '/'); + LabelNode nullNode = new LabelNode(); + LabelNode continueNode = new LabelNode(); + hashCodeNode.instructions.add(new JumpInsnNode(Opcodes.IFNULL, nullNode)); + hashCodeNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + hashCodeNode.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, fieldJavaType)); + hashCodeNode.instructions + .add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, objectOwnerType, "hashCode", "()I", false)); + hashCodeNode.instructions.add(new JumpInsnNode(Opcodes.GOTO, continueNode)); + hashCodeNode.instructions.add(nullNode); + hashCodeNode.instructions.add(new InsnNode(Opcodes.ICONST_0)); + hashCodeNode.instructions.add(continueNode); + break; + } + hashCodeNode.instructions.add(new InsnNode(Opcodes.IADD)); + hashCodeNode.instructions.add(new VarInsnNode(Opcodes.ISTORE, 1)); + } + hashCodeNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); + hashCodeNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + + classNode.methods.add(hashCodeNode); + } + + /** + * Adds a equals method to underlying class. <br> + * Algorithm is as follows: <br> + * <i><p> + * if (this == other) return true; + * if (other == null) return false; + * if (other is not instanceof <this class>) return false; + * for (field: all fields) { + * if (other.getField() != this.field) return false; + * } + * return true; + * </p></i> + * <br> + * + * @param classNode + * @param fieldList + * @throws JSONException + */ + @SuppressWarnings("unchecked") + private static void addEqualsMethod(ClassNode classNode, List<TupleSchemaRegistry.SQLFieldInfo> fieldList) + throws JSONException + { + MethodNode equalsNode = new MethodNode(Opcodes.ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null); + equalsNode.visitAnnotation("Ljava/lang/Override;", true); + + LabelNode l0 = new LabelNode(); + LabelNode l1 = new LabelNode(); + LabelNode l2 = new LabelNode(); + LabelNode l3 = new LabelNode(); + LabelNode l4 = new LabelNode(); + + equalsNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + + // if (this == other) return true; + equalsNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + equalsNode.instructions.add(new JumpInsnNode(Opcodes.IF_ACMPNE, l0)); + equalsNode.instructions.add(new InsnNode(Opcodes.ICONST_1)); + equalsNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + + equalsNode.instructions.add(l0); + // if (other == null) return false; + equalsNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + equalsNode.instructions.add(new JumpInsnNode(Opcodes.IFNULL, l1)); + // if (!(other instanceof <this class>)) return false; + equalsNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + equalsNode.instructions.add(new TypeInsnNode(Opcodes.INSTANCEOF, classNode.name)); + equalsNode.instructions.add(new JumpInsnNode(Opcodes.IFNE, l2)); + + equalsNode.instructions.add(l1); + equalsNode.instructions.add(new InsnNode(Opcodes.ICONST_0)); + equalsNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + + equalsNode.instructions.add(l2); + // Check if it other object can cast to <this class> + equalsNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + equalsNode.instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, classNode.name)); + equalsNode.instructions.add(new VarInsnNode(Opcodes.ASTORE, 2)); + + for (int i = 0; i < fieldList.size(); i++) { + boolean isLast = ((i + 1) == fieldList.size()); + TupleSchemaRegistry.SQLFieldInfo info = fieldList.get(i); + String fieldName = info.getColumnName(); + String fieldType = info.getType().getJavaType().getName(); + String fieldJavaType = getJavaType(fieldType); + + String getterMethodName = (fieldType.equals("boolean") ? "is" : "get") + + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + equalsNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); + equalsNode.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, getterMethodName, + "()" + fieldJavaType, false)); + equalsNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + equalsNode.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, fieldJavaType)); + + switch (fieldType) { + case "boolean": + case "byte": + case "char": + case "short": + case "int": + equalsNode.instructions + .add(new JumpInsnNode(isLast ? Opcodes.IF_ICMPEQ : Opcodes.IF_ICMPNE, isLast ? l4 : l3)); + break; + case "long": + equalsNode.instructions.add(new InsnNode(Opcodes.LCMP)); + equalsNode.instructions.add(new JumpInsnNode(isLast ? Opcodes.IFEQ : Opcodes.IFNE, isLast ? l4 : l3)); + break; + case "float": + equalsNode.instructions.add(new InsnNode(Opcodes.FCMPL)); + equalsNode.instructions.add(new JumpInsnNode(isLast ? Opcodes.IFEQ : Opcodes.IFNE, isLast ? l4 : l3)); + break; + case "double": + equalsNode.instructions.add(new InsnNode(Opcodes.DCMPL)); + equalsNode.instructions.add(new JumpInsnNode(isLast ? Opcodes.IFEQ : Opcodes.IFNE, isLast ? l4 : l3)); + break; + default: + String objectOwnerType = fieldType.replace('.', '/'); + + LabelNode nonNullNode = new LabelNode(); + LabelNode continueNode = new LabelNode(); + + equalsNode.instructions.add(new JumpInsnNode(Opcodes.IFNONNULL, nonNullNode)); + equalsNode.instructions.add(new JumpInsnNode(isLast ? Opcodes.IFNULL : Opcodes.IFNONNULL, isLast ? l4 : l3)); + + equalsNode.instructions.add(new JumpInsnNode(Opcodes.GOTO, continueNode)); + + equalsNode.instructions.add(nonNullNode); + equalsNode.instructions.add(new InsnNode(Opcodes.POP)); + equalsNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + equalsNode.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldName, fieldJavaType)); + equalsNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); + equalsNode.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, getterMethodName, + "()" + fieldJavaType, false)); + equalsNode.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, objectOwnerType, "equals", + "(Ljava/lang/Object;)Z", false)); + equalsNode.instructions.add(new JumpInsnNode(isLast ? Opcodes.IFNE : Opcodes.IFEQ, isLast ? l4 : l3)); + + equalsNode.instructions.add(continueNode); + break; + } + } + + equalsNode.instructions.add(l3); + equalsNode.instructions.add(new InsnNode(Opcodes.ICONST_0)); + equalsNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + + equalsNode.instructions.add(l4); + equalsNode.instructions.add(new InsnNode(Opcodes.ICONST_1)); + equalsNode.instructions.add(new InsnNode(Opcodes.IRETURN)); + + classNode.methods.add(equalsNode); + } + + private static String getJavaType(String fieldType) + { + Character pchar = PRIMITIVE_TYPES.get(fieldType); + if (pchar != null) { + //it is a primitive type + return Character.toString(pchar); + } + //non-primitive so find the internal name of the class. + return 'L' + fieldType.replace('.', '/') + ';'; + } + + /** + * Given the class name it reads and loads the class from the input stream. + * + * @param fqcn fully qualified class name. + * @param inputStream stream from which class is read. + * @return loaded class + * @throws IOException + */ + public static Class<?> readBeanClass(String fqcn, FSDataInputStream inputStream) throws IOException + { + byte[] bytes = IOUtils.toByteArray(inputStream); + inputStream.close(); + return new ByteArrayClassLoader().defineClass(fqcn, bytes); + } + + /** + * Given the class name it reads and loads the class from given byte array. + * + * @param fqcn fully qualified class name. + * @param inputClass byte[] from which class is read. + * @return loaded class + * @throws IOException + */ + public static Class<?> readBeanClass(String fqcn, byte[] inputClass) throws IOException + { + return new ByteArrayClassLoader().defineClass(fqcn, inputClass); + } + + private static class ByteArrayClassLoader extends ClassLoader + { + Class<?> defineClass(String name, byte[] ba) + { + return defineClass(name, ba, 0, ba.length); + } + } +} http://git-wip-us.apache.org/repos/asf/apex-malhar/blob/b968e4c3/sql/src/test/java/org/apache/apex/malhar/sql/codegen/BeanClassGeneratorTest.java ---------------------------------------------------------------------- diff --git a/sql/src/test/java/org/apache/apex/malhar/sql/codegen/BeanClassGeneratorTest.java b/sql/src/test/java/org/apache/apex/malhar/sql/codegen/BeanClassGeneratorTest.java new file mode 100644 index 0000000..8fcb7f8 --- /dev/null +++ b/sql/src/test/java/org/apache/apex/malhar/sql/codegen/BeanClassGeneratorTest.java @@ -0,0 +1,188 @@ +/** + * Copyright (c) 2015 DataTorrent, Inc. + * All rights reserved. + */ +package org.apache.apex.malhar.sql.codegen; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Date; + +import org.codehaus.jettison.json.JSONException; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +import org.apache.apex.malhar.sql.schema.TupleSchemaRegistry; + +import static org.junit.Assert.assertEquals; + +public class BeanClassGeneratorTest +{ + + protected class TestMeta extends TestWatcher + { + String generatedDir; + + @Override + protected void starting(Description description) + { + super.starting(description); + generatedDir = "target/" + description.getClassName() + "/" + description.getMethodName(); + } + + @Override + protected void finished(Description description) + { + super.finished(description); + } + } + + @Rule + public TestMeta testMeta = new TestMeta(); + + @Test + public void test() throws IOException, JSONException, IllegalAccessException, InstantiationException, + NoSuchFieldException, NoSuchMethodException, InvocationTargetException + { + String addressClassName = TupleSchemaRegistry.FQCN_PACKAGE + "Address_v1"; + + TupleSchemaRegistry.Schema schema = new TupleSchemaRegistry.Schema(); + schema.addField("streetNumber", long.class); + schema.addField("streetName", String.class); + schema.addField("city", String.class); + schema.addField("state", String.class); + schema.addField("zip", String.class); + + byte[] beanClass = BeanClassGenerator.createAndWriteBeanClass(addressClassName, schema.fieldList); + + Class<?> clazz = BeanClassGenerator.readBeanClass(addressClassName, beanClass); + + Object o = clazz.newInstance(); + Field f = clazz.getDeclaredField("streetNumber"); + Assert.assertNotNull(f); + + Method m = clazz.getDeclaredMethod("setStreetNumber", Long.class); + m.invoke(o, 343L); + + m = clazz.getMethod("getStreetNumber"); + Long result = (Long)m.invoke(o); + + assertEquals("reflect getVal invoke", 343, result.longValue()); + } + + @Test + public void testPrimitive() throws IOException, JSONException, IllegalAccessException, InstantiationException, + NoSuchFieldException, NoSuchMethodException, InvocationTargetException + { + String addressClassName = TupleSchemaRegistry.FQCN_PACKAGE + "Energy_v1"; + + TupleSchemaRegistry.Schema schema = new TupleSchemaRegistry.Schema(); + schema.addField("streetNumber", Integer.class); + schema.addField("houseNumber", Long.class); + schema.addField("condo", Boolean.class); + schema.addField("water-usage", Float.class); + schema.addField("electricity-usage", Double.class); + schema.addField("startDate", Date.class); + + byte[] beanClass = BeanClassGenerator.createAndWriteBeanClass(addressClassName, schema.fieldList); + + Class<?> clazz = BeanClassGenerator.readBeanClass(addressClassName, beanClass); + + Object o = clazz.newInstance(); + Field f = clazz.getDeclaredField("streetNumber"); + Assert.assertNotNull(f); + + //int setter and getter + Method m = clazz.getDeclaredMethod("setStreetNumber", Integer.class); + m.invoke(o, 343); + m = clazz.getMethod("getStreetNumber"); + Integer result = (Integer)m.invoke(o); + + assertEquals("reflect getStreetNumber invoke", 343, result.intValue()); + + //long setter and getter + m = clazz.getDeclaredMethod("setHouseNumber", Long.class); + m.invoke(o, 123L); + m = clazz.getMethod("getHouseNumber"); + Long houseNum = (Long)m.invoke(o); + + assertEquals("reflect getHouseNumber invoke", 123L, houseNum.longValue()); + + //boolean setter and getter + m = clazz.getDeclaredMethod("setCondo", Boolean.class); + m.invoke(o, true); + m = clazz.getMethod("getCondo"); + Boolean isCondo = (Boolean)m.invoke(o); + + assertEquals("reflect getCondo invoke", true, isCondo); + + //float setter and getter + m = clazz.getDeclaredMethod("setWater-usage", Float.class); + m.invoke(o, 88.34F); + m = clazz.getMethod("getWater-usage"); + Float waterUsage = (Float)m.invoke(o); + + assertEquals("reflect getWaterUsage invoke", 88.34F, waterUsage.floatValue(), 0); + + //double setter and getter + m = clazz.getDeclaredMethod("setElectricity-usage", Double.class); + m.invoke(o, 88.343243); + m = clazz.getMethod("getElectricity-usage"); + Double electricityUsage = (Double)m.invoke(o); + + assertEquals("reflect getWaterUsage invoke", 88.343243, electricityUsage, 0); + + Date now = new Date(); + m = clazz.getDeclaredMethod("setStartDate", Date.class); + m.invoke(o, now); + + m = clazz.getMethod("getStartDate"); + Date startDate = (Date)m.invoke(o); + assertEquals("reflect getStartDate invoke", now, startDate); + + m = clazz.getMethod("getStartDateMs"); + long startDateMs = (long)m.invoke(o); + assertEquals("reflect getStartDateMs invoke", now.getTime(), startDateMs, 0); + + m = clazz.getMethod("getStartDateSec"); + int startDateSec = (int)m.invoke(o); + assertEquals("reflect getStartDateSec invoke", now.getTime() / 1000, startDateSec, 0); + + m = clazz.getDeclaredMethod("setStartDateMs", Long.class); + m.invoke(o, now.getTime()); + + m = clazz.getMethod("getStartDate"); + startDate = (Date)m.invoke(o); + assertEquals("reflect getStartDate invoke", now, startDate); + + m = clazz.getMethod("getStartDateMs"); + startDateMs = (long)m.invoke(o); + assertEquals("reflect getStartDateMs invoke", now.getTime(), startDateMs, 0); + + m = clazz.getMethod("getStartDateSec"); + startDateSec = (int)m.invoke(o); + assertEquals("reflect getStartDateSec invoke", now.getTime() / 1000, startDateSec, 0); + + m = clazz.getDeclaredMethod("setStartDateSec", Integer.class); + m.invoke(o, (int)(now.getTime() / 1000)); + + now = new Date(now.getTime() / 1000 * 1000); + m = clazz.getMethod("getStartDate"); + startDate = (Date)m.invoke(o); + assertEquals("reflect getStartDate invoke", now, startDate); + + m = clazz.getMethod("getStartDateMs"); + startDateMs = (long)m.invoke(o); + assertEquals("reflect getStartDateMs invoke", now.getTime(), startDateMs, 0); + + m = clazz.getMethod("getStartDateSec"); + startDateSec = (int)m.invoke(o); + assertEquals("reflect getStartDateSec invoke", now.getTime() / 1000, startDateSec, 0); + + } +}
