/*
 * Configurate
 * Copyright (C) zml and Configurate contributors
 *
 * Licensed 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.spongepowered.configurate.objectmapping.serialize;

import com.google.common.reflect.TypeToken;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ScopedConfigurationNode;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.objectmapping.ObjectMappingException;

import java.lang.reflect.Modifier;

class AnnotatedObjectSerializer implements TypeSerializer<Object> {
    @Override
    public <Node extends ScopedConfigurationNode<Node>> Object deserialize(@NonNull TypeToken<?> type, @NonNull Node value) throws ObjectMappingException {
        Class<?> clazz = getInstantiableType(type, value.getNode("__class__").getString());
        return value.getOptions().getObjectMapperFactory().getMapper(clazz).bindToNew().populate(value);
    }

    private Class<?> getInstantiableType(TypeToken<?> type, String configuredName) throws ObjectMappingException {
        Class<?> retClass;
        if (type.getRawType().isInterface() || Modifier.isAbstract(type.getRawType().getModifiers())) {
            if (configuredName == null) {
                throw new ObjectMappingException("No available configured type for instances of " + type);
            } else {
                try {
                    retClass = Class.forName(configuredName);
                } catch (ClassNotFoundException e) {
                    throw new ObjectMappingException("Unknown class of object " + configuredName, e);
                }
                if (!type.getRawType().isAssignableFrom(retClass)) {
                    throw new ObjectMappingException("Configured type " + configuredName + " does not extend "
                            + type.getRawType().getCanonicalName());
                }
            }
        } else {
            retClass = type.getRawType();
        }
        return retClass;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T extends ScopedConfigurationNode<T>> void serialize(@NonNull TypeToken<?> type, @Nullable Object obj, @NonNull T value) throws ObjectMappingException {
        if (type.getRawType().isInterface() || Modifier.isAbstract(type.getRawType().getModifiers())) {
            // serialize obj's concrete type rather than the interface/abstract class
            value.getNode("__class__").setValue(obj.getClass().getName());
        }
        ((ObjectMapper<Object>) value.getOptions().getObjectMapperFactory().getMapper(obj.getClass())).bind(obj).serialize(value);
    }
}

@@ -1156,16 +1156,74 @@
 ject%3E %7B%0A
+%0A    public static final String CLASS_KEY = %22__class__%22;%0A%0A
     @Ove
@@ -1388,29 +1388,33 @@
 n %7B%0A        
-Class
+TypeToken
 %3C?%3E clazz = 
@@ -1453,27 +1453,25 @@
 getNode(
-%22__class__%22
+CLASS_KEY
 ).getStr
@@ -1602,21 +1602,25 @@
 private 
-Class
+TypeToken
 %3C?%3E getI
@@ -1717,21 +1717,25 @@
         
-Class
+TypeToken
 %3C?%3E retC
@@ -1748,37 +1748,73 @@
         
-if (type.getRawType()
+Class%3C?%3E rawType = type.getRawType();%0A        if (rawType
 .isInter
@@ -1843,33 +1843,23 @@
 bstract(
-type.getR
+r
 awType
-()
 .getModi
@@ -2093,16 +2093,29 @@
 Class = 
+TypeToken.of(
 Class.fo
@@ -2135,16 +2135,17 @@
 redName)
+)
 ;%0A      
@@ -2336,51 +2336,33 @@
 f (!
-type.getRawType().isAssignableFrom(retClass
+retClass.isSubtypeOf(type
 )) %7B
@@ -2503,33 +2503,23 @@
       + 
-type.getR
+r
 awType
-()
 .getCano
@@ -2604,37 +2604,24 @@
 Class = type
-.getRawType()
 ;%0A        %7D%0A
@@ -2878,33 +2878,350 @@
     if (
-type.getRawType()
+obj == null) %7B%0A            T clazz = value.getNode(CLASS_KEY);%0A            value.setValue(null);%0A            if (!clazz.isVirtual()) %7B%0A                value.getNode(CLASS_KEY).setValue(clazz);%0A            %7D%0A            return;%0A        %7D%0A        Class%3C?%3E rawType = type.getRawType();%0A        ObjectMapper%3C?%3E mapper;%0A        if (rawType
 .isInter
@@ -3250,33 +3250,23 @@
 bstract(
-type.getR
+r
 awType
-()
 .getModi
@@ -3389,27 +3389,25 @@
 getNode(
-%22__class__%22
+CLASS_KEY
 ).setVal
@@ -3448,111 +3448,241 @@
     
-%7D%0A        ((ObjectMapper%3CObject%3E) value.getOptions().getObjectMapperFactory().getMapper(obj.getClass())
+    mapper = value.getOptions().getObjectMapperFactory().getMapper((obj.getClass()));%0A        %7D else %7B%0A            mapper = value.getOptions().getObjectMapperFactory().getMapper(type);%0A        %7D%0A        ((ObjectMapper%3CObject%3E) mapper
 ).bi

Handle parameterized types within AnnotatedObjectSerializer