From 1bc97d88eeb3872b5f89eac9ca466ac3d1c5b27a Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Mon, 19 Jul 2021 23:50:48 +0200 Subject: [PATCH] Added utility method to return all declared super class fields --- src/zutil/ClassUtil.java | 31 ++++++++- src/zutil/db/bean/DBBeanConfig.java | 47 +++++++------- src/zutil/parser/Templator.java | 3 +- .../parser/json/JSONObjectInputStream.java | 63 ++++++++++++------- .../parser/json/JSONObjectOutputStream.java | 38 ++++++----- test/zutil/ClassUtilTest.java | 57 +++++++++++++++++ 6 files changed, 179 insertions(+), 60 deletions(-) create mode 100644 test/zutil/ClassUtilTest.java diff --git a/src/zutil/ClassUtil.java b/src/zutil/ClassUtil.java index 6c86977..cb23216 100755 --- a/src/zutil/ClassUtil.java +++ b/src/zutil/ClassUtil.java @@ -28,12 +28,12 @@ import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; +import java.util.List; /** * This class include some utility functions for classes - * - * User: Ziver */ public class ClassUtil { /** A Set that contains possible wrapper objects for primitives **/ @@ -217,4 +217,31 @@ public class ClassUtil { } return c; } + + + /** + * Traverses the class hierarchy and collects all fields. + * + * @param clazz is the class to return fields from. + * @return a List including all fields contained from the class and its super classes. + */ + public static List getAllDeclaredFields(Class clazz) { + return getAllDeclaredFields(clazz, Object.class); + } + /** + * Traverses the class hierarchy and collects all fields. + * + * @param clazz is the class to return fields from. + * @param upToClass a top limit class where traversal will end not including fields from this class. + * @return a List including all fields contained from the class and its super classes. + */ + public static List getAllDeclaredFields(Class clazz, Class upToClass) { + List fields = new ArrayList<>(); + + for (Class currentClass = clazz; currentClass != upToClass; currentClass = currentClass.getSuperclass()) { + Collections.addAll(fields, currentClass.getDeclaredFields()); + } + + return fields; + } } diff --git a/src/zutil/db/bean/DBBeanConfig.java b/src/zutil/db/bean/DBBeanConfig.java index 60fed4f..e6cd9a5 100755 --- a/src/zutil/db/bean/DBBeanConfig.java +++ b/src/zutil/db/bean/DBBeanConfig.java @@ -29,6 +29,7 @@ import zutil.ClassUtil; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -53,7 +54,6 @@ class DBBeanConfig{ private DBBeanConfig() { } - /** * @return the configuration object for the specified class */ @@ -66,39 +66,44 @@ class DBBeanConfig{ /** * Caches the fields */ - private static void initBeanConfig(Class c) { + private static void initBeanConfig(Class clazz) { DBBeanConfig config = new DBBeanConfig(); + // Find the table name - DBBean.DBTable tableAnn = c.getAnnotation(DBBean.DBTable.class); + + DBBean.DBTable tableAnn = clazz.getAnnotation(DBBean.DBTable.class); if (tableAnn != null) { config.tableName = tableAnn.value(); config.idColumnName = tableAnn.idColumn(); } else { - config.tableName = c.getSimpleName(); + config.tableName = clazz.getSimpleName(); config.idColumnName = "id"; } + // Get fields + + List fields; + if (tableAnn != null && tableAnn.superBean()) + fields = ClassUtil.getAllDeclaredFields(clazz, DBBean.class); + else + fields = Arrays.asList(clazz.getDeclaredFields()); + // Add the fields in the bean and all the super classes fields - for (Class cc = c; cc != DBBean.class; cc = cc.getSuperclass()) { - Field[] fields = cc.getDeclaredFields(); - for (Field field : fields) { - int mod = field.getModifiers(); - if (!Modifier.isTransient(mod) && - !Modifier.isFinal(mod) && - !Modifier.isStatic(mod) && - !config.fields.contains(field)) { - if (List.class.isAssignableFrom(field.getType()) && - field.getAnnotation(DBBean.DBLinkTable.class) != null) - config.subBeanFields.add(new DBBeanSubBeanConfig(field)); - else - config.fields.add(new DBBeanFieldConfig(field)); - } + for (Field field : fields) { + int mod = field.getModifiers(); + if (!Modifier.isTransient(mod) && + !Modifier.isFinal(mod) && + !Modifier.isStatic(mod) && + !config.fields.contains(field)) { + if (List.class.isAssignableFrom(field.getType()) && + field.getAnnotation(DBBean.DBLinkTable.class) != null) + config.subBeanFields.add(new DBBeanSubBeanConfig(field)); + else + config.fields.add(new DBBeanFieldConfig(field)); } - if (tableAnn == null || !tableAnn.superBean()) - break; } - beanConfigs.put(c.getName(), config); + beanConfigs.put(clazz.getName(), config); } diff --git a/src/zutil/parser/Templator.java b/src/zutil/parser/Templator.java index eb9b9ca..9e0ffce 100755 --- a/src/zutil/parser/Templator.java +++ b/src/zutil/parser/Templator.java @@ -24,6 +24,7 @@ package zutil.parser; +import zutil.ClassUtil; import zutil.io.file.FileUtil; import zutil.log.LogUtil; import zutil.struct.MutableInt; @@ -412,7 +413,7 @@ public class Templator { else { // Using a loop as the direct lookup throws a exception if no field was found // So this is probably a bit faster - for (Field field : obj.getClass().getDeclaredFields()) { // Only look for public fields + for (Field field : ClassUtil.getAllDeclaredFields(obj.getClass())) { // Only look for public fields if (field.getName().equals(attrib)) { field.setAccessible(true); return field.get(obj); diff --git a/src/zutil/parser/json/JSONObjectInputStream.java b/src/zutil/parser/json/JSONObjectInputStream.java index f519b70..0d4ee98 100755 --- a/src/zutil/parser/json/JSONObjectInputStream.java +++ b/src/zutil/parser/json/JSONObjectInputStream.java @@ -209,21 +209,23 @@ public class JSONObjectInputStream extends InputStream implements ObjectInput, C if (json.getString("@object_id") != null && objectCache.containsKey(json.getInt(MD_OBJECT_ID))) return objectCache.get(json.getInt(MD_OBJECT_ID)); + // ------------------------------------------------ // Resolve the class - Object obj; - // Try using explicit class + // ------------------------------------------------ + + Class objClass; + + // Try using explicit class from target if (type != null) { - obj = type.getDeclaredConstructor().newInstance(); + objClass = type; } - // Try using metadata + // Try using JSON metadata else if (json.getString(MD_CLASS) != null) { - Class objClass = Class.forName(json.getString(MD_CLASS)); - obj = objClass.getDeclaredConstructor().newInstance(); + objClass = Class.forName(json.getString(MD_CLASS)); } // Search for registered classes else if (registeredClasses.containsKey(key)) { - Class objClass = registeredClasses.get(key); - obj = objClass.getDeclaredConstructor().newInstance(); + objClass = registeredClasses.get(key); } // Unknown class else { @@ -231,19 +233,38 @@ public class JSONObjectInputStream extends InputStream implements ObjectInput, C return null; } - // Read all fields from the new object instance - for (Field field : obj.getClass().getDeclaredFields()) { - if ((field.getModifiers() & Modifier.STATIC) == 0 && - (field.getModifiers() & Modifier.TRANSIENT) == 0 && - json.get(field.getName()) != null) { - // Parse field - field.setAccessible(true); - field.set(obj, readType( - field.getType(), - ClassUtil.getGenericClasses(field), - field.get(obj), - field.getName(), - json.get(field.getName()))); + // ------------------------------------------------ + // Instantiate object + // ------------------------------------------------ + + Object obj = null; + + // Date and time objects + if (Date.class.isAssignableFrom(objClass)) { + obj = new Date(json.getLong("timestamp")); + } + else if (Calendar.class.isAssignableFrom(objClass)) { + obj = Calendar.getInstance(); + ((Calendar) obj).setTimeInMillis(json.getLong("timestamp")); + } + // Instantiate generic object + else{ + obj = objClass.getDeclaredConstructor().newInstance(); + + // Read all fields from the new object instance + for (Field field : ClassUtil.getAllDeclaredFields(obj.getClass())) { + if ((field.getModifiers() & Modifier.STATIC) == 0 && + (field.getModifiers() & Modifier.TRANSIENT) == 0 && + json.get(field.getName()) != null) { + // Parse field + field.setAccessible(true); + field.set(obj, readType( + field.getType(), + ClassUtil.getGenericClasses(field), + field.get(obj), + field.getName(), + json.get(field.getName()))); + } } } // Add object to the cache diff --git a/src/zutil/parser/json/JSONObjectOutputStream.java b/src/zutil/parser/json/JSONObjectOutputStream.java index e684511..ab6789f 100755 --- a/src/zutil/parser/json/JSONObjectOutputStream.java +++ b/src/zutil/parser/json/JSONObjectOutputStream.java @@ -34,13 +34,12 @@ import java.io.*; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import static zutil.parser.json.JSONObjectInputStream.MD_CLASS; import static zutil.parser.json.JSONObjectInputStream.MD_OBJECT_ID; + public class JSONObjectOutputStream extends OutputStream implements ObjectOutput, Closeable{ /** If the generated JSON should contain class def meta-data **/ private boolean generateMetaData = true; @@ -148,23 +147,32 @@ public class JSONObjectOutputStream extends OutputStream implements ObjectOutput return root; } else { // Miss - objectCache.put(obj, objectCache.size()+1); + objectCache.put(obj, objectCache.size() + 1); root.set(MD_OBJECT_ID, objectCache.size()); } root.set(MD_CLASS, obj.getClass().getName()); } - // Add all the fields to the DataNode - for (Field field : obj.getClass().getDeclaredFields()) { - if (! Modifier.isStatic(field.getModifiers()) && - ! Modifier.isTransient(field.getModifiers())) { - field.setAccessible(true); - Object fieldObj = field.get(obj); - // has object a value? - if (ignoreNullFields && fieldObj == null) - continue; - else - root.set(field.getName(), getDataNode(fieldObj)); + // Date and time objects + if (Date.class.isAssignableFrom(objClass)) { + root.set("timestamp", ((Date) obj).getTime()); + } + else if (Calendar.class.isAssignableFrom(objClass)) { + root.set("timestamp", ((Calendar) obj).getTimeInMillis()); + } + else { // Generic class, Add all the fields to the DataNode + for (Field field : ClassUtil.getAllDeclaredFields(obj.getClass())) { + if (!Modifier.isStatic(field.getModifiers()) && + !Modifier.isTransient(field.getModifiers())) { + field.setAccessible(true); + Object fieldObj = field.get(obj); + + // has object a value? + if (ignoreNullFields && fieldObj == null) + continue; + else + root.set(field.getName(), getDataNode(fieldObj)); + } } } } diff --git a/test/zutil/ClassUtilTest.java b/test/zutil/ClassUtilTest.java new file mode 100644 index 0000000..ade2261 --- /dev/null +++ b/test/zutil/ClassUtilTest.java @@ -0,0 +1,57 @@ +package zutil; + +import junit.framework.TestCase; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + + +public class ClassUtilTest extends TestCase { + + public void testGetAllDeclaredFields() { + List fields = ClassUtil.getAllDeclaredFields(TestClass.class); + List fieldNames = getFieldNames(fields); + + assertEquals(4, fields.size()); + assertTrue(fieldNames.contains("superPrivateInt")); + assertTrue(fieldNames.contains("superPublicInt")); + assertTrue(fieldNames.contains("protectedInt")); + assertTrue(fieldNames.contains("publicInt")); + + fields = ClassUtil.getAllDeclaredFields(TestClass.class, TestSuperClass.class); + fieldNames = getFieldNames(fields); + + assertEquals(2, fields.size()); + assertFalse(fieldNames.contains("superPrivateInt")); + assertFalse(fieldNames.contains("superPublicInt")); + assertTrue(fieldNames.contains("protectedInt")); + assertTrue(fieldNames.contains("publicInt")); + } + + // ---------------------------------------------------- + // Utilities + // ---------------------------------------------------- + + private List getFieldNames(List fields) { + List names = new ArrayList<>(); + + for (Field field : fields) + names.add(field.getName()); + return names; + } + + // ---------------------------------------------------- + // Test Classes + // ---------------------------------------------------- + + public static class TestSuperClass { + private int superPrivateInt = 10; + public int superPublicInt = 11; + } + + public static class TestClass extends TestSuperClass { + protected int protectedInt = 20; + public int publicInt = 21; + } +} \ No newline at end of file