From c39ae8c7ca1b9e51cb4eb9239f272c01317669c7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nemanja=20Zbilji=C4=87?= <nemanja.zbiljic@gmail.com>
Date: Sun, 15 Oct 2017 13:27:17 +0200
Subject: [PATCH] Add tests for polymorphic type handling

---
 .../PolymorphicWithDefaultImpl.java           |  45 +++++
 .../PolymorphicWithDefaultImplConfigs.java    |  65 ++++++
 .../java/beanconfig/polymorphic/Subtypes.java | 143 +++++++++++++
 .../polymorphic/SubtypesConfigs.java          |  75 +++++++
 .../beanconfig/polymorphic/TypeNames.java     | 132 ++++++++++++
 .../polymorphic/TypeNamesConfigs.java         |  53 +++++
 .../beanconfig/polymorphic/VisibleTypeId.java |  33 +++
 .../polymorphic/VisibleTypeIdConfigs.java     |  20 ++
 .../beanconfig/polymorphic/WithGenerics.java  |  56 ++++++
 .../polymorphic/WithGenericsConfigs.java      |  21 ++
 .../resources/beanconfig/polymorphic01.conf   | 108 ++++++++++
 .../impl/ConfigPolymorphicBeanTest.scala      | 189 ++++++++++++++++++
 12 files changed, 940 insertions(+)
 create mode 100644 config/src/test/java/beanconfig/polymorphic/PolymorphicWithDefaultImpl.java
 create mode 100644 config/src/test/java/beanconfig/polymorphic/PolymorphicWithDefaultImplConfigs.java
 create mode 100644 config/src/test/java/beanconfig/polymorphic/Subtypes.java
 create mode 100644 config/src/test/java/beanconfig/polymorphic/SubtypesConfigs.java
 create mode 100644 config/src/test/java/beanconfig/polymorphic/TypeNames.java
 create mode 100644 config/src/test/java/beanconfig/polymorphic/TypeNamesConfigs.java
 create mode 100644 config/src/test/java/beanconfig/polymorphic/VisibleTypeId.java
 create mode 100644 config/src/test/java/beanconfig/polymorphic/VisibleTypeIdConfigs.java
 create mode 100644 config/src/test/java/beanconfig/polymorphic/WithGenerics.java
 create mode 100644 config/src/test/java/beanconfig/polymorphic/WithGenericsConfigs.java
 create mode 100644 config/src/test/resources/beanconfig/polymorphic01.conf
 create mode 100644 config/src/test/scala/com/typesafe/config/impl/ConfigPolymorphicBeanTest.scala

diff --git a/config/src/test/java/beanconfig/polymorphic/PolymorphicWithDefaultImpl.java b/config/src/test/java/beanconfig/polymorphic/PolymorphicWithDefaultImpl.java
new file mode 100644
index 00000000..8a923df0
--- /dev/null
+++ b/config/src/test/java/beanconfig/polymorphic/PolymorphicWithDefaultImpl.java
@@ -0,0 +1,45 @@
+package beanconfig.polymorphic;
+
+import com.typesafe.config.ConfigSubTypes;
+import com.typesafe.config.ConfigSubTypes.Type;
+import com.typesafe.config.ConfigTypeInfo;
+
+import java.util.List;
+
+public class PolymorphicWithDefaultImpl {
+
+    @ConfigTypeInfo(defaultImpl = LegacyInter.class)
+    @ConfigSubTypes(value = {
+        @Type(value = MyInter.class, name = "mine")
+    })
+    public interface Inter {}
+
+    public static class MyInter implements Inter {
+
+        private List<String> blah;
+
+        public List<String> getBlah() {
+            return blah;
+        }
+
+        public void setBlah(List<String> blah) {
+            this.blah = blah;
+        }
+    }
+
+    public static class LegacyInter extends MyInter {
+    }
+
+    /*
+     * can use non-deprecated value for the same
+     */
+    @ConfigTypeInfo(defaultImpl = Void.class)
+    public static class DefaultWithVoidAsDefault {}
+
+    /*
+     * one with no defaultImpl nor listed subtypes
+     */
+    @ConfigTypeInfo
+    public abstract static class MysteryPolymorphic {}
+
+}
diff --git a/config/src/test/java/beanconfig/polymorphic/PolymorphicWithDefaultImplConfigs.java b/config/src/test/java/beanconfig/polymorphic/PolymorphicWithDefaultImplConfigs.java
new file mode 100644
index 00000000..4e3a76be
--- /dev/null
+++ b/config/src/test/java/beanconfig/polymorphic/PolymorphicWithDefaultImplConfigs.java
@@ -0,0 +1,65 @@
+package beanconfig.polymorphic;
+
+import beanconfig.polymorphic.PolymorphicWithDefaultImpl.DefaultWithVoidAsDefault;
+import beanconfig.polymorphic.PolymorphicWithDefaultImpl.Inter;
+import beanconfig.polymorphic.PolymorphicWithDefaultImpl.MysteryPolymorphic;
+
+public class PolymorphicWithDefaultImplConfigs {
+
+    public static class InnerConfig {
+
+        private Inter object;
+        private Inter array;
+
+        public Inter getObject() {
+            return object;
+        }
+
+        public void setObject(Inter object) {
+            this.object = object;
+        }
+
+        public Inter getArray() {
+            return array;
+        }
+
+        public void setArray(Inter array) {
+            this.array = array;
+        }
+    }
+
+    public static class DefaultWithVoidAsDefaultConfig {
+
+        private DefaultWithVoidAsDefault defaultAsVoid1;
+        private DefaultWithVoidAsDefault defaultAsVoid2;
+
+        public DefaultWithVoidAsDefault getDefaultAsVoid1() {
+            return defaultAsVoid1;
+        }
+
+        public void setDefaultAsVoid1(DefaultWithVoidAsDefault defaultAsVoid1) {
+            this.defaultAsVoid1 = defaultAsVoid1;
+        }
+
+        public DefaultWithVoidAsDefault getDefaultAsVoid2() {
+            return defaultAsVoid2;
+        }
+
+        public void setDefaultAsVoid2(DefaultWithVoidAsDefault defaultAsVoid2) {
+            this.defaultAsVoid2 = defaultAsVoid2;
+        }
+    }
+
+    public static class MysteryPolymorphicConfig {
+
+        private MysteryPolymorphic badType;
+
+        public MysteryPolymorphic getBadType() {
+            return badType;
+        }
+
+        public void setBadType(MysteryPolymorphic badType) {
+            this.badType = badType;
+        }
+    }
+}
diff --git a/config/src/test/java/beanconfig/polymorphic/Subtypes.java b/config/src/test/java/beanconfig/polymorphic/Subtypes.java
new file mode 100644
index 00000000..f4f36dbf
--- /dev/null
+++ b/config/src/test/java/beanconfig/polymorphic/Subtypes.java
@@ -0,0 +1,143 @@
+package beanconfig.polymorphic;
+
+import com.typesafe.config.ConfigSubTypes;
+import com.typesafe.config.ConfigSubTypes.Type;
+import com.typesafe.config.ConfigTypeInfo;
+import com.typesafe.config.ConfigTypeName;
+import com.typesafe.config.Optional;
+
+public class Subtypes {
+
+    @ConfigTypeInfo
+    @ConfigSubTypes({
+        @Type(value = SubB.class),
+        @Type(value = SubC.class),
+        @Type(value = SubD.class)
+    })
+    public static abstract class SuperType {
+    }
+
+    @ConfigTypeName("TypeB")
+    public static class SubB extends SuperType {
+
+        private int b = 1;
+
+        public int getB() {
+            return b;
+        }
+
+        public void setB(int b) {
+            this.b = b;
+        }
+    }
+
+    public static class SubC extends SuperType {
+
+        private int c = 2;
+
+        public int getC() {
+            return c;
+        }
+
+        public void setC(int c) {
+            this.c = c;
+        }
+    }
+
+    public static class SubD extends SuperType {
+
+        private int d;
+
+        public int getD() {
+            return d;
+        }
+
+        public void setD(int d) {
+            this.d = d;
+        }
+    }
+
+    @ConfigTypeInfo(defaultImpl = DefaultImpl.class)
+    public static abstract class SuperTypeWithDefault {}
+
+    public static class DefaultImpl extends SuperTypeWithDefault {
+
+        @Optional
+        private int a;
+
+        public int getA() {
+            return a;
+        }
+
+        public void setA(int a) {
+            this.a = a;
+        }
+    }
+
+    @ConfigTypeInfo
+    @ConfigSubTypes({
+        @Type(ImplX.class),
+        @Type(ImplY.class)
+    })
+    public static abstract class BaseX {}
+
+    @ConfigTypeName("x")
+    public static class ImplX extends BaseX {
+
+        private int x;
+
+        public ImplX() {
+        }
+
+        public ImplX(int x) {
+            this.x = x;
+        }
+
+        public int getX() {
+            return x;
+        }
+
+        public void setX(int x) {
+            this.x = x;
+        }
+    }
+
+    @ConfigTypeName("y")
+    public static class ImplY extends BaseX {
+
+        private int y;
+
+        public int getY() {
+            return y;
+        }
+
+        public void setY(int y) {
+            this.y = y;
+        }
+    }
+
+    public static class AtomicWrapper {
+
+        private BaseX value;
+
+        public AtomicWrapper() {
+        }
+
+        public AtomicWrapper(int x) {
+            value = new ImplX(x);
+        }
+
+        public BaseX getDirectValue() {
+            return value;
+        }
+
+        public int getValue() {
+            return ((ImplX) value).getX();
+        }
+
+        public void setValue(int value) {
+            this.value = new ImplX(value);
+        }
+    }
+
+}
diff --git a/config/src/test/java/beanconfig/polymorphic/SubtypesConfigs.java b/config/src/test/java/beanconfig/polymorphic/SubtypesConfigs.java
new file mode 100644
index 00000000..3df6cbe0
--- /dev/null
+++ b/config/src/test/java/beanconfig/polymorphic/SubtypesConfigs.java
@@ -0,0 +1,75 @@
+package beanconfig.polymorphic;
+
+import beanconfig.polymorphic.Subtypes.AtomicWrapper;
+import beanconfig.polymorphic.Subtypes.SuperType;
+import beanconfig.polymorphic.Subtypes.SuperTypeWithDefault;
+
+public class SubtypesConfigs {
+
+    public static class SuperTypeConfig {
+
+        private SuperType bean1;
+        private SuperType bean2;
+
+        public SuperType getBean1() {
+            return bean1;
+        }
+
+        public void setBean1(SuperType bean1) {
+            this.bean1 = bean1;
+        }
+
+        public SuperType getBean2() {
+            return bean2;
+        }
+
+        public void setBean2(SuperType bean2) {
+            this.bean2 = bean2;
+        }
+    }
+
+    public static class DefaultImplConfig {
+
+        private SuperTypeWithDefault defaultImpl1;
+        private SuperTypeWithDefault defaultImpl2;
+        private SuperTypeWithDefault defaultImpl3;
+
+        public SuperTypeWithDefault getDefaultImpl1() {
+            return defaultImpl1;
+        }
+
+        public void setDefaultImpl1(SuperTypeWithDefault defaultImpl1) {
+            this.defaultImpl1 = defaultImpl1;
+        }
+
+        public SuperTypeWithDefault getDefaultImpl2() {
+            return defaultImpl2;
+        }
+
+        public void setDefaultImpl2(SuperTypeWithDefault defaultImpl2) {
+            this.defaultImpl2 = defaultImpl2;
+        }
+
+        public SuperTypeWithDefault getDefaultImpl3() {
+            return defaultImpl3;
+        }
+
+        public void setDefaultImpl3(SuperTypeWithDefault defaultImpl3) {
+            this.defaultImpl3 = defaultImpl3;
+        }
+    }
+
+    public static class AtomicWrapperConfig {
+
+        private AtomicWrapper atomicWrapper;
+
+        public AtomicWrapper getAtomicWrapper() {
+            return atomicWrapper;
+        }
+
+        public void setAtomicWrapper(AtomicWrapper atomicWrapper) {
+            this.atomicWrapper = atomicWrapper;
+        }
+    }
+
+}
diff --git a/config/src/test/java/beanconfig/polymorphic/TypeNames.java b/config/src/test/java/beanconfig/polymorphic/TypeNames.java
new file mode 100644
index 00000000..87abb33b
--- /dev/null
+++ b/config/src/test/java/beanconfig/polymorphic/TypeNames.java
@@ -0,0 +1,132 @@
+package beanconfig.polymorphic;
+
+import com.typesafe.config.ConfigSubTypes;
+import com.typesafe.config.ConfigSubTypes.Type;
+import com.typesafe.config.ConfigTypeInfo;
+import com.typesafe.config.ConfigTypeName;
+
+public class TypeNames {
+
+    @ConfigTypeInfo
+    @ConfigSubTypes({
+        @Type(value = Dog.class, name = "doggy"),
+        @Type(Cat.class) /* defaults to "Cat" then */
+    })
+    public static class Animal {
+
+        private String name;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+
+    @ConfigSubTypes({
+        @Type(MaineCoon.class),
+        @Type(Persian.class)
+    })
+    public static abstract class Cat extends Animal {
+
+        private boolean purrs;
+
+        public boolean isPurrs() {
+            return purrs;
+        }
+
+        public void setPurrs(boolean purrs) {
+            this.purrs = purrs;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            Cat cat = (Cat) o;
+            return purrs == cat.purrs;
+        }
+
+        @Override
+        public int hashCode() {
+            return (purrs ? 1 : 0);
+        }
+    }
+
+    public static class Dog extends Animal {
+
+        private int ageInYears;
+
+        public int getAgeInYears() {
+            return ageInYears;
+        }
+
+        public void setAgeInYears(int ageInYears) {
+            this.ageInYears = ageInYears;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            Dog dog = (Dog) o;
+            return ageInYears == dog.ageInYears;
+        }
+
+        @Override
+        public int hashCode() {
+            return ageInYears;
+        }
+
+        @Override
+        public String toString() {
+            return "Dog{" +
+                "ageInYears=" + ageInYears +
+                '}';
+        }
+    }
+
+    /*
+     * Uses default name ("MaineCoon") since there's no @ConfigTypeName,
+     * nor did supertype specify name.
+     */
+    public static class MaineCoon extends Cat {
+
+        public MaineCoon() {
+            super();
+        }
+
+        @Override
+        public String toString() {
+            return "Cat{" +
+                "purrs=" + isPurrs() +
+                '}';
+        }
+    }
+
+    @ConfigTypeName("persialaisKissa")
+    public static class Persian extends Cat {
+
+        public Persian() {
+            super();
+        }
+
+        @Override
+        public String toString() {
+            return "Cat{" +
+                "purrs=" + isPurrs() +
+                '}';
+        }
+    }
+
+}
diff --git a/config/src/test/java/beanconfig/polymorphic/TypeNamesConfigs.java b/config/src/test/java/beanconfig/polymorphic/TypeNamesConfigs.java
new file mode 100644
index 00000000..0794029e
--- /dev/null
+++ b/config/src/test/java/beanconfig/polymorphic/TypeNamesConfigs.java
@@ -0,0 +1,53 @@
+package beanconfig.polymorphic;
+
+import beanconfig.polymorphic.TypeNames.Animal;
+
+import java.util.List;
+
+public class TypeNamesConfigs {
+
+    public static class SingleTypeConfig {
+
+        private Animal dog;
+
+        public Animal getDog() {
+            return dog;
+        }
+
+        public void setDog(Animal dog) {
+            this.dog = dog;
+        }
+    }
+
+    public static class TypeNamesListConfig {
+
+        List<Animal> dogs;
+        List<Animal> cats;
+        List<Animal> animals;
+
+        public List<Animal> getDogs() {
+            return dogs;
+        }
+
+        public void setDogs(List<Animal> dogs) {
+            this.dogs = dogs;
+        }
+
+        public List<Animal> getCats() {
+            return cats;
+        }
+
+        public void setCats(List<Animal> cats) {
+            this.cats = cats;
+        }
+
+        public List<Animal> getAnimals() {
+            return animals;
+        }
+
+        public void setAnimals(List<Animal> animals) {
+            this.animals = animals;
+        }
+    }
+
+}
diff --git a/config/src/test/java/beanconfig/polymorphic/VisibleTypeId.java b/config/src/test/java/beanconfig/polymorphic/VisibleTypeId.java
new file mode 100644
index 00000000..1f24dd86
--- /dev/null
+++ b/config/src/test/java/beanconfig/polymorphic/VisibleTypeId.java
@@ -0,0 +1,33 @@
+package beanconfig.polymorphic;
+
+import com.typesafe.config.ConfigTypeInfo;
+import com.typesafe.config.ConfigTypeName;
+
+public class VisibleTypeId {
+
+    @ConfigTypeInfo(visible = true)
+    @ConfigTypeName("BaseType")
+    public static class PropertyBean {
+
+        private int a = 3;
+
+        private String type;
+
+        public int getA() {
+            return a;
+        }
+
+        public void setA(int a) {
+            this.a = a;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public void setType(String type) {
+            this.type = type;
+        }
+    }
+
+}
diff --git a/config/src/test/java/beanconfig/polymorphic/VisibleTypeIdConfigs.java b/config/src/test/java/beanconfig/polymorphic/VisibleTypeIdConfigs.java
new file mode 100644
index 00000000..17e73b4b
--- /dev/null
+++ b/config/src/test/java/beanconfig/polymorphic/VisibleTypeIdConfigs.java
@@ -0,0 +1,20 @@
+package beanconfig.polymorphic;
+
+import beanconfig.polymorphic.VisibleTypeId.PropertyBean;
+
+public class VisibleTypeIdConfigs {
+
+    public static class VisibleTypeConfig {
+
+        private PropertyBean bean;
+
+        public PropertyBean getBean() {
+            return bean;
+        }
+
+        public void setBean(PropertyBean bean) {
+            this.bean = bean;
+        }
+    }
+
+}
diff --git a/config/src/test/java/beanconfig/polymorphic/WithGenerics.java b/config/src/test/java/beanconfig/polymorphic/WithGenerics.java
new file mode 100644
index 00000000..2ea4f944
--- /dev/null
+++ b/config/src/test/java/beanconfig/polymorphic/WithGenerics.java
@@ -0,0 +1,56 @@
+package beanconfig.polymorphic;
+
+import com.typesafe.config.ConfigSubTypes;
+import com.typesafe.config.ConfigSubTypes.Type;
+import com.typesafe.config.ConfigTypeInfo;
+
+public class WithGenerics {
+
+    @ConfigTypeInfo(property = "object-type")
+    @ConfigSubTypes({
+        @Type(value = Dog.class, name = "doggy")
+    })
+    public static abstract class Animal {
+
+        private String name;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+
+    public static class Dog extends Animal {
+
+        private int boneCount;
+
+        public Dog() {
+            super();
+        }
+
+        public int getBoneCount() {
+            return boneCount;
+        }
+
+        public void setBoneCount(int boneCount) {
+            this.boneCount = boneCount;
+        }
+    }
+
+    public static class ContainerWithAnimal<T extends Animal> {
+
+        private T animal;
+
+        public void setAnimal(T animal) {
+            this.animal = animal;
+        }
+
+        public T getAnimal() {
+            return animal;
+        }
+    }
+
+}
diff --git a/config/src/test/java/beanconfig/polymorphic/WithGenericsConfigs.java b/config/src/test/java/beanconfig/polymorphic/WithGenericsConfigs.java
new file mode 100644
index 00000000..4b48866c
--- /dev/null
+++ b/config/src/test/java/beanconfig/polymorphic/WithGenericsConfigs.java
@@ -0,0 +1,21 @@
+package beanconfig.polymorphic;
+
+import beanconfig.polymorphic.WithGenerics.Animal;
+import beanconfig.polymorphic.WithGenerics.ContainerWithAnimal;
+
+public class WithGenericsConfigs {
+
+    public static class WrapperWithGenericsConfig {
+
+        private ContainerWithAnimal<Animal> wrapperWithDog;
+
+        public ContainerWithAnimal<Animal> getWrapperWithDog() {
+            return wrapperWithDog;
+        }
+
+        public void setWrapperWithDog(ContainerWithAnimal<Animal> wrapperWithDog) {
+            this.wrapperWithDog = wrapperWithDog;
+        }
+    }
+
+}
diff --git a/config/src/test/resources/beanconfig/polymorphic01.conf b/config/src/test/resources/beanconfig/polymorphic01.conf
new file mode 100644
index 00000000..59ec9445
--- /dev/null
+++ b/config/src/test/resources/beanconfig/polymorphic01.conf
@@ -0,0 +1,108 @@
+{
+    "polymorphicWithDefaultImpl": {
+        "object": {
+            "type": "mine"
+            "blah": ["a", "b", "c"]
+        }
+        "array": {
+            "blah": ["a", "b", "c", "d"]
+        }
+        "defaultAsVoid1": {}
+        "defaultAsVoid2": {
+            "bogus": 3
+        }
+        "badType": {
+            "whatever": 13
+        }
+    }
+
+    "subtypes": {
+        "bean1": {
+            "type": "TypeB"
+            "b": 13
+        }
+        "bean2": {
+            "type": "SubD"
+            "d": -4
+        }
+        "defaultImpl1": {
+            "a": 13
+        }
+        "defaultImpl2": {
+            "a": 14
+            "type": "foobar"
+        }
+        "defaultImpl3": {
+            "type": "foobar"
+        }
+        "atomicWrapper": {
+            "value": 3
+        }
+    }
+
+    "typeNames": {
+        "dog": {
+            "type": "doggy"
+            "name": "Smiley"
+            "ageInYears": 1
+        }
+        "dogs": [
+            {
+                "type": "doggy"
+                "name": "Spot"
+                "ageInYears": 3
+            }
+            {
+                "type": "doggy"
+                "name": "Odie"
+                "ageInYears": 7
+            }
+        ]
+        "cats": [
+            {
+                "type": "MaineCoon"
+                "name": "Belzebub"
+                "purrs": true
+            }
+            {
+                "type": "MaineCoon"
+                "name": "Piru"
+                "purrs": false
+            }
+            {
+                "type": "persialaisKissa"
+                "name": "Khomeini"
+                "purrs": true
+            }
+        ]
+        "animals": [
+            {
+                "type": "MaineCoon"
+                "name": "Venla"
+                "purrs": true
+            }
+            {
+                "type": "doggy"
+                "name": "Amadeus"
+                "ageInYears": 13
+            }
+        ]
+    }
+
+    "visibleTypeId": {
+        "bean" {
+            "type": "BaseType"
+            "a": 3
+        }
+    }
+
+    "withGenerics": {
+        "wrapperWithDog": {
+            "animal": {
+                "object-type": "doggy"
+                "name": "Fluffy"
+                "boneCount": 3
+            }
+        }
+    }
+}
diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigPolymorphicBeanTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigPolymorphicBeanTest.scala
new file mode 100644
index 00000000..4cb19a38
--- /dev/null
+++ b/config/src/test/scala/com/typesafe/config/impl/ConfigPolymorphicBeanTest.scala
@@ -0,0 +1,189 @@
+package com.typesafe.config.impl
+
+import java.io.{InputStream, InputStreamReader}
+
+import beanconfig.polymorphic.PolymorphicWithDefaultImpl._
+import beanconfig.polymorphic.PolymorphicWithDefaultImplConfigs._
+import beanconfig.polymorphic.Subtypes._
+import beanconfig.polymorphic.SubtypesConfigs._
+import beanconfig.polymorphic.TypeNames._
+import beanconfig.polymorphic.TypeNamesConfigs._
+import beanconfig.polymorphic.VisibleTypeId._
+import beanconfig.polymorphic.VisibleTypeIdConfigs._
+import beanconfig.polymorphic.WithGenericsConfigs._
+import beanconfig.polymorphic.{TypeNames, WithGenerics}
+import com.typesafe.config._
+import org.junit.Assert._
+import org.junit._
+
+import scala.collection.JavaConverters._
+
+class ConfigPolymorphicBeanTest extends TestUtils {
+
+    @Test
+    def testDeserializationWithObject() {
+        val beanConfig: InnerConfig =
+            ConfigBeanFactory.create(loadConfig().getConfig("polymorphicWithDefaultImpl"), classOf[InnerConfig])
+        assertNotNull(beanConfig)
+
+        assertTrue(beanConfig.getObject.isInstanceOf[MyInter])
+        assertFalse(beanConfig.getObject.isInstanceOf[LegacyInter])
+        assertEquals(List("a", "b", "c").asJava, beanConfig.getObject.asInstanceOf[MyInter].getBlah)
+    }
+
+    @Test
+    def testDeserializationWithArray() {
+        val beanConfig: InnerConfig =
+            ConfigBeanFactory.create(loadConfig().getConfig("polymorphicWithDefaultImpl"), classOf[InnerConfig])
+        assertNotNull(beanConfig)
+
+        assertTrue(beanConfig.getArray.isInstanceOf[LegacyInter])
+        assertEquals(List("a", "b", "c", "d").asJava, beanConfig.getArray.asInstanceOf[LegacyInter].getBlah)
+    }
+
+    @Test
+    def testDefaultAsVoid() {
+        val beanConfig: DefaultWithVoidAsDefaultConfig =
+            ConfigBeanFactory.create(loadConfig().getConfig("polymorphicWithDefaultImpl"),
+                classOf[DefaultWithVoidAsDefaultConfig])
+        assertNotNull(beanConfig)
+
+        assertNull(beanConfig.getDefaultAsVoid1)
+        assertNull(beanConfig.getDefaultAsVoid2)
+    }
+
+    @Test
+    def testBadTypeAsNull() {
+        val e = intercept[ConfigException.BadBean] {
+            ConfigBeanFactory.create(loadConfig().getConfig("polymorphicWithDefaultImpl"), classOf[MysteryPolymorphicConfig])
+        }
+        assertTrue("no default implementation", e.getMessage.contains("has no default implementation"))
+    }
+
+    @Test
+    def testDeserialization() {
+        val beanConfig: SuperTypeConfig = ConfigBeanFactory.create(loadConfig().getConfig("subtypes"), classOf[SuperTypeConfig])
+        assertNotNull(beanConfig)
+
+        assertTrue(beanConfig.getBean1.isInstanceOf[SubB])
+        assertEquals(13, beanConfig.getBean1.asInstanceOf[SubB].getB)
+
+        assertTrue(beanConfig.getBean2.isInstanceOf[SubD])
+        assertEquals(-4, beanConfig.getBean2.asInstanceOf[SubD].getD)
+    }
+
+    @Test
+    def testDefaultImpl() {
+        val beanConfig: DefaultImplConfig =
+            ConfigBeanFactory.create(loadConfig().getConfig("subtypes"), classOf[DefaultImplConfig])
+        assertNotNull(beanConfig)
+
+        // first, test with no type information
+        assertTrue(beanConfig.getDefaultImpl1.isInstanceOf[SuperTypeWithDefault])
+        assertEquals(13, beanConfig.getDefaultImpl1.asInstanceOf[DefaultImpl].getA)
+
+        // and then with unmapped info
+        assertTrue(beanConfig.getDefaultImpl2.isInstanceOf[SuperTypeWithDefault])
+        assertEquals(14, beanConfig.getDefaultImpl2.asInstanceOf[DefaultImpl].getA)
+
+        assertTrue(beanConfig.getDefaultImpl3.isInstanceOf[SuperTypeWithDefault])
+        assertEquals(0, beanConfig.getDefaultImpl3.asInstanceOf[DefaultImpl].getA)
+    }
+
+    @Test
+    def testViaAtomic() {
+        val beanConfig: AtomicWrapperConfig =
+            ConfigBeanFactory.create(loadConfig().getConfig("subtypes"), classOf[AtomicWrapperConfig])
+        assertNotNull(beanConfig)
+
+        assertTrue(beanConfig.getAtomicWrapper.getDirectValue.isInstanceOf[ImplX])
+        assertEquals(3, beanConfig.getAtomicWrapper.getValue)
+    }
+
+    @Test
+    def testCreateSpecificType() {
+        val beanConfig: SingleTypeConfig =
+            ConfigBeanFactory.create(loadConfig().getConfig("typeNames"), classOf[SingleTypeConfig])
+        assertNotNull(beanConfig)
+        assertNotNull(beanConfig.getDog)
+
+        assertTrue(beanConfig.getDog.isInstanceOf[TypeNames.Dog])
+        assertEquals("Smiley", beanConfig.getDog.getName)
+        assertEquals(1, beanConfig.getDog.asInstanceOf[TypeNames.Dog].getAgeInYears)
+    }
+
+    @Test
+    def testCreateList() {
+        val beanConfig: TypeNamesListConfig =
+            ConfigBeanFactory.create(loadConfig().getConfig("typeNames"), classOf[TypeNamesListConfig])
+        assertNotNull(beanConfig)
+
+        assertEquals(2, beanConfig.getDogs.size)
+        assertEquals(3, beanConfig.getCats.size)
+        assertEquals(2, beanConfig.getAnimals.size)
+
+        val dogsConfigOne = new Dog()
+        dogsConfigOne.setName("Spot")
+        dogsConfigOne.setAgeInYears(3)
+        val dogsConfigTwo = new Dog()
+        dogsConfigTwo.setName("Odie")
+        dogsConfigTwo.setAgeInYears(7)
+
+        assertEquals(List(dogsConfigOne, dogsConfigTwo).asJava, beanConfig.getDogs)
+
+        val catsConfigOne = new MaineCoon()
+        catsConfigOne.setName("Belzebub")
+        catsConfigOne.setPurrs(true)
+        val catsConfigTwo = new MaineCoon()
+        catsConfigTwo.setName("Piru")
+        catsConfigTwo.setPurrs(false)
+        val catsConfigThree = new Persian()
+        catsConfigThree.setName("Khomeini")
+        catsConfigThree.setPurrs(true)
+
+        assertEquals(List(catsConfigOne, catsConfigTwo, catsConfigThree).asJava, beanConfig.getCats)
+
+        val animalsConfigOne = new MaineCoon()
+        animalsConfigOne.setName("Venla")
+        animalsConfigOne.setPurrs(true)
+        val animalsConfigTwo = new Dog()
+        animalsConfigTwo.setName("Amadeus")
+        animalsConfigTwo.setAgeInYears(13)
+
+        assertEquals(List(animalsConfigOne, animalsConfigTwo).asJava, beanConfig.getAnimals)
+    }
+
+    @Test
+    def testVisible() {
+        val beanConfig: VisibleTypeConfig =
+            ConfigBeanFactory.create(loadConfig().getConfig("visibleTypeId"), classOf[VisibleTypeConfig])
+        assertNotNull(beanConfig)
+
+        assertTrue(beanConfig.getBean.isInstanceOf[PropertyBean])
+        assertEquals("BaseType", beanConfig.getBean.getType)
+        assertEquals(3, beanConfig.getBean.getA)
+    }
+
+    @Test
+    def testWrapperWithGenerics() {
+        val beanConfig: WrapperWithGenericsConfig =
+            ConfigBeanFactory.create(loadConfig().getConfig("withGenerics"), classOf[WrapperWithGenericsConfig])
+        assertNotNull(beanConfig)
+
+        assertTrue(beanConfig.getWrapperWithDog.getAnimal.isInstanceOf[WithGenerics.Dog])
+        assertEquals("Fluffy", beanConfig.getWrapperWithDog.getAnimal.getName)
+        assertEquals(3, beanConfig.getWrapperWithDog.getAnimal.asInstanceOf[WithGenerics.Dog].getBoneCount)
+    }
+
+    private def loadConfig(): Config = {
+        val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/polymorphic01.conf")
+        try {
+            val config: Config = ConfigFactory.parseReader(new InputStreamReader(configIs),
+                ConfigParseOptions.defaults.setSyntax(ConfigSyntax.CONF)).resolve
+            config
+        } finally {
+            configIs.close()
+        }
+    }
+
+}