Serial: support for anonymous inner classes
authorNiki Roo <roo.niki@gmail.com>
Mon, 18 Jun 2018 06:55:04 +0000 (08:55 +0200)
committerNiki Roo <roo.niki@gmail.com>
Mon, 18 Jun 2018 06:55:04 +0000 (08:55 +0200)
src/be/nikiroo/utils/serial/SerialUtils.java
src/be/nikiroo/utils/test/SerialTest.java
src/be/nikiroo/utils/test/TestCase.java

index fe62d283cdceb48afc530ce239dde40ac774f06e..61bca4f6d1a9b0e238c44b72c4589327d53d09e8 100644 (file)
@@ -7,7 +7,9 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.UnknownFormatConversionException;
 
@@ -170,27 +172,59 @@ public class SerialUtils {
        public static Object createObject(String type)
                        throws ClassNotFoundException, NoSuchMethodException {
 
+               String desc = null;
+
                try {
                        Class<?> clazz = getClass(type);
                        String className = clazz.getName();
-                       Object[] args = null;
+                       List<Object> args = new ArrayList<Object>();
+                       List<Class<?>> classes = new ArrayList<Class<?>>();
                        Constructor<?> ctor = null;
                        if (className.contains("$")) {
-                               Object javaParent = createObject(className.substring(0,
-                                               className.lastIndexOf('$')));
-                               args = new Object[] { javaParent };
-                               ctor = clazz.getDeclaredConstructor(new Class[] { javaParent
-                                               .getClass() });
+                               for (String parentName = className.substring(0,
+                                               className.lastIndexOf('$'));; parentName = parentName
+                                                               .substring(0, parentName.lastIndexOf('$'))) {
+                                       Object parent = createObject(parentName);
+                                       args.add(parent);
+                                       classes.add(parent.getClass());
+                                       
+                                       if (!parentName.contains("$")) {
+                                               break;
+                                       }
+                               }
+
+                               // Better error description in case there is no empty
+                               // constructor:
+                               desc = "";
+                               String end = "";
+                               for (Class<?> parent = clazz; parent != null
+                                               && !parent.equals(Object.class); parent = parent
+                                                               .getSuperclass()) {
+                                       if (!desc.isEmpty()) {
+                                               desc += " [:";
+                                               end += "]";
+                                       }
+                                       desc += parent;
+                               }
+                               desc += end;
+                               //
+
+                               ctor = clazz.getDeclaredConstructor(
+                                               classes.toArray(new Class[] {}));
+                               desc = null;
                        } else {
-                               args = new Object[] {};
                                ctor = clazz.getDeclaredConstructor();
                        }
 
                        ctor.setAccessible(true);
-                       return ctor.newInstance(args);
+                       return ctor.newInstance(args.toArray());
                } catch (ClassNotFoundException e) {
                        throw e;
                } catch (NoSuchMethodException e) {
+                       if (desc != null) {
+                               throw new NoSuchMethodException(
+                                               "Empty constructor not found: " + desc);
+                       }
                        throw e;
                } catch (Exception e) {
                        throw new NoSuchMethodException("Cannot instantiate: " + type);
@@ -241,10 +275,8 @@ public class SerialUtils {
                        fields = o.getClass().getDeclaredFields();
                        type = o.getClass().getCanonicalName();
                        if (type == null) {
-                               throw new NotSerializableException(
-                                               String.format(
-                                                               "Cannot find the class for this object: %s (it could be an inner class, which is not supported)",
-                                                               o));
+                               // Anonymous inner classes support
+                               type = o.getClass().getName();
                        }
                        id = Integer.toString(hash);
                        if (map.containsKey(hash)) {
@@ -307,7 +339,9 @@ public class SerialUtils {
        static boolean encode(StringBuilder builder, Object value) {
                if (value == null) {
                        builder.append("NULL");
-               } else if (value.getClass().getCanonicalName().endsWith("[]")) {
+               } else if (value.getClass().getSimpleName().endsWith("[]")) {
+                       // Simple name does support [] suffix and do not return NULL for
+                       // inner anonymous classes
                        return customTypes.get("[]").encode(builder, value);
                } else if (customTypes.containsKey(value.getClass().getCanonicalName())) {
                        return customTypes.get(value.getClass().getCanonicalName())//
index b27280420eb4aaf1552554d92dbe544bf7e4cd28..26571dfcb9755653d0208558548a720c60d84b4f 100644 (file)
@@ -30,6 +30,24 @@ class SerialTest extends TestLauncher {
                        }
                });
 
+               addTest(new TestCase() {
+                       private TestCase me = setName("Anonymous inner class");
+
+                       @Override
+                       public void test() throws Exception {
+                               Data data = new Data() {
+                               };
+
+                               String encoded = new Exporter().append(data).toString(false);
+                               Object redata = new Importer().read(encoded).getValue();
+                               String reencoded = new Exporter().append(redata)
+                                               .toString(false);
+
+                               assertEquals(encoded.replaceAll("@[0-9]*", "@REF"),
+                                               reencoded.replaceAll("@[0-9]*", "@REF"));
+                       }
+               });
+
                addTest(new TestCase("URL Import/Export") {
                        @Override
                        public void test() throws Exception {
index 4e7d3800c5a588bf6a1b06f341bfc5d3c2c8c2ac..0479370f64df57bc5e27a809ee706b0908005f85 100644 (file)
@@ -38,6 +38,15 @@ abstract public class TestCase {
                this.name = name;
        }
 
+       /**
+        * This constructor can be used if you require a no-param constructor. In
+        * this case, you are allowed to set the name manually via
+        * {@link TestCase#setName}.
+        */
+       protected TestCase() {
+               this("no name");
+       }
+
        /**
         * Setup the test (called before the test is run).
         * 
@@ -65,6 +74,20 @@ abstract public class TestCase {
                return name;
        }
 
+       /**
+        * The test name.
+        * 
+        * @param name
+        *            the new name (internal use only)
+        * 
+        * @return this (so we can chain and so we can initialize it in a member
+        *         variable if this is an anonymous inner class)
+        */
+       protected TestCase setName(String name) {
+               this.name = name;
+               return this;
+       }
+
        /**
         * Actually do the test.
         *