From 72648e757f648cd152bc00dfb83f895260f037a0 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Mon, 18 Jun 2018 08:55:04 +0200 Subject: [PATCH] Serial: support for anonymous inner classes --- src/be/nikiroo/utils/serial/SerialUtils.java | 60 +++++++++++++++----- src/be/nikiroo/utils/test/SerialTest.java | 18 ++++++ src/be/nikiroo/utils/test/TestCase.java | 23 ++++++++ 3 files changed, 88 insertions(+), 13 deletions(-) diff --git a/src/be/nikiroo/utils/serial/SerialUtils.java b/src/be/nikiroo/utils/serial/SerialUtils.java index fe62d283..61bca4f6 100644 --- a/src/be/nikiroo/utils/serial/SerialUtils.java +++ b/src/be/nikiroo/utils/serial/SerialUtils.java @@ -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 args = new ArrayList(); + List> classes = new ArrayList>(); 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())// diff --git a/src/be/nikiroo/utils/test/SerialTest.java b/src/be/nikiroo/utils/test/SerialTest.java index b2728042..26571dfc 100644 --- a/src/be/nikiroo/utils/test/SerialTest.java +++ b/src/be/nikiroo/utils/test/SerialTest.java @@ -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 { diff --git a/src/be/nikiroo/utils/test/TestCase.java b/src/be/nikiroo/utils/test/TestCase.java index 4e7d3800..0479370f 100644 --- a/src/be/nikiroo/utils/test/TestCase.java +++ b/src/be/nikiroo/utils/test/TestCase.java @@ -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. * -- 2.27.0