public class ConversionException extends Exception
{
    private ConversionException(String message)
    {
        super(message);
    }

    private ConversionException(String message, Throwable cause)
    {
        super(message, cause);
    }

    public static ConversionException of(Object converter, Object toConvert, String message, Throwable cause)
    {
        String msg = message + "\nConverter: " + converter.getClass().getName();
        if (toConvert != null)
        {
            msg += "\nConverting: " + toConvert.toString();
        }
        return new ConversionException(msg, cause);
    }

    public static ConversionException of(Object converter, Object toConvert, String message)
    {
        String msg = message + "\nConverter: " + converter.getClass().getName();
        if (toConvert != null)
        {
            msg += "\nConverting: " + toConvert.toString();
        }
        return new ConversionException(msg);
    }
} public class ConverterManager
{
    private ConverterManager parent;

    private Map<Class<?>, Converter> converters = new ConcurrentHashMap<Class<?>, Converter>();
    private Map<Class, Converter> convertersByClass = new ConcurrentHashMap<Class, Converter>();

    protected ConverterManager(ConverterManager fallbackManager)
    {
        this.parent = fallbackManager;
    }

    /**
     * Returns a ConverterManager with the default converters
     *
     * @return the manager
     */
    public static ConverterManager defaultManager()
    {
        ConverterManager convert = new ConverterManager(null);
        // Register Default Converters
        convert.registerDefaultConverters();
        return convert;
    }

    /**
     * Returns a new ConverterManager using this manager as fallback
     *
     * @return the new ConverterManager
     */
    public final ConverterManager subManager()
    {
        return new ConverterManager(this);
    }

    private void registerDefaultConverters()
    {
        this.registerConverter(new IntegerConverter(), Integer.class, int.class);
        this.registerConverter(new ShortConverter(), Short.class, short.class);
        this.registerConverter(new ByteConverter(), Byte.class, byte.class);
        this.registerConverter(new DoubleConverter(), Double.class, double.class);
        this.registerConverter(new FloatConverter(), Float.class, float.class);
        this.registerConverter(new LongConverter(), Long.class, long.class);
        this.registerConverter(new BooleanConverter(), Boolean.class, boolean.class);
        this.registerConverter(new StringConverter(), String.class);
        this.registerConverter(new DateConverter(), Date.class);
        this.registerConverter(new UUIDConverter(), UUID.class);
        this.registerConverter(new LocaleConverter(), Locale.class);
        this.registerConverter(new LevelConverter(), Level.class);
        this.registerConverter(new ClassConverter(), Class.class);
        this.registerConverter(new EnumConverter(), Enum.class);
        this.registerConverter(new FileConverter(), File.class);

        // Generic Converters:
        this.registerConverter(new MapConverter(), Map.class);
        this.registerConverter(new CollectionConverter(), Collection.class);
        this.registerConverter(new ArrayConverter());
    }

    /**
     * Registers a Converter for given Class
     *
     * @param converter the converter
     * @param classes   the class
     */
    public final ConverterManager registerConverter(Converter converter, Class... classes)
    {
        if (classes == null || converter == null)
        {
            return this;
        }
        for (Class aClass : classes)
        {
            converters.put(aClass, converter);
        }
        convertersByClass.put(converter.getClass(), converter);
        return this;
    }

    /**
     * Removes a Converter from this manager
     *
     * @param clazz the class of the converter to remove
     */
    public final ConverterManager removeConverter(Class clazz)
    {
        Iterator<Entry<Class<?>, Converter>> it = converters.entrySet().iterator();
        Entry<Class<?>, Converter> entry;
        while (it.hasNext())
        {
            entry = it.next();
            if (entry.getKey() == clazz || entry.getValue().getClass() == clazz)
            {
                it.remove();
            }
        }
        convertersByClass.remove(clazz);
        return this;
    }

    /**
     * Removes all registered converters
     */
    public final ConverterManager removeConverters()
    {
        converters.clear();
        convertersByClass.clear();
        return this;
    }

    /**
     * Matches a registered Converter
     *
     * @param clazz the class to match for
     *
     * @return a matching converter
     */
    @SuppressWarnings("unchecked")
    public final Converter matchConverter(Class clazz) throws ConverterNotFoundException
    {
        if (clazz == null)
        {
            return null;
        }
        Converter converter = this.getConverter(clazz);
        if (converter == null)
        {
            converter = this.findConverter(clazz);
        }
        if (converter == null)
        {
            throw new ConverterNotFoundException("Converter not found for: " + clazz.getName());
        }
        return converter;
    }

    private Converter getConverter(Class clazz)
    {
        Converter converter = this.converters.get(clazz);
        if (converter == null && this.parent != null)
        {
            converter = this.parent.getConverter(clazz);
        }
        return converter;
    }

    private Converter findConverter(Class clazz)
    {
        // TODO get for each superclass instead

        for (Entry<Class<?>, Converter> entry : converters.entrySet())
        {
            if (entry.getKey().isAssignableFrom(clazz))
            {
                Converter converter = entry.getValue();
                registerConverter(converter, clazz);
                return converter;
            }
        }
        if (this.parent != null)
        {
            return this.parent.findConverter(clazz);
        }
        return null;
    }

    /**
     * Converts a convertible Object into a Node
     *
     * @param object the Object
     *
     * @return the serialized Node
     */
    @SuppressWarnings("unchecked")
    public final <T> Node convertToNode(T object) throws ConversionException
    {
        if (object == null)
        {
            return NullNode.emptyNode();
        }
        try
        {
            return matchConverter(object.getClass()).toNode(object, this);
        }
        catch (ConverterNotFoundException e)
        {
            Node node = toNode(object);
            if (node != null)
            {
                return node;
            }
            throw e;
        }
    }

    /**
     * When no converter was found directly this method is called in order to try to convert the object anyways.
     * Primarily used to convert Arrays.
     *
     * @param object the object to convert
     *
     * @return the converted object or null if not converted
     */
    protected <T> Node toNode(T object) throws ConversionException
    {
        if (object.getClass().isArray())
        {
            return getConverterByClass(ArrayConverter.class).toNode(object, this);
        }
        return null;
    }

    /**
     * Converts a Node into an Object of given Type
     *
     * @param node the node
     * @param type the type of the object
     *
     * @return the converted Node
     */
    @SuppressWarnings("unchecked")
    public final <T> T convertFromNode(Node node, Type type) throws ConversionException
    {
        if (node == null || node instanceof NullNode || type == null)
        {
            return null;
        }
        try
        {
            Class<T> clazz;
            if (type instanceof ParameterizedType)
            {
                clazz = (Class<T>)((ParameterizedType)type).getRawType();
            }
            else
            {
                clazz = (Class<T>)type;
            }
            Converter converter = matchConverter(clazz);
            if (type instanceof ParameterizedType && !(converter instanceof GenericConverter))
            {
                type = ((ParameterizedType)type).getRawType();
            }
            return (T)converter.fromNode(node, type, this);
        }
        catch (ConverterNotFoundException ignored)
        {
            return (T)fromNode(node, type);
        }
    }

    /**
     * When no converter was found directly this method is called in order to try to convert the object anyways.
     * Primarily used to convert Arrays.
     *
     * @param node the node to convert
     * @param type the type to convert to
     *
     * @return the converted node or null if not converted
     */
    protected Object fromNode(Node node, Type type) throws ConversionException
    {
        if (type instanceof Class && ((Class)type).isArray())
        {
            return getConverterByClass(ArrayConverter.class).fromNode(node, (Class)type, this);
        }
        return null;
    }

    /**
     * Changes the fallback ConverterManager of this converter
     *
     * @param defaultManager the ConverterManager to fallback to
     *
     * @return fluent interface
     */
    public ConverterManager withFallback(ConverterManager defaultManager)
    {
        this.parent = defaultManager;
        return this;
    }

    /**
     * Returns the converter of given class
     *
     * @param clazz        the class
     * @param <ConverterT> the converter Type
     *
     * @return the converter for given class or null if not registered or found
     */
    @SuppressWarnings("unchecked")
    public final <ConverterT> ConverterT getConverterByClass(Class<ConverterT> clazz)
    {
        ConverterT converter = (ConverterT)this.convertersByClass.get(clazz);
        if (converter == null && parent != null)
        {
            return parent.getConverterByClass(clazz);
        }
        return converter;
    }
} public interface ClassedConverter<ConverterT> extends Converter<ConverterT, Class<? extends ConverterT>>
{
} public class MapNode extends ContainerNode<Map<String, Node>>
{
    private Map<String, Node> mappedNodes = new LinkedHashMap<String, Node>();
    /**
     * LowerCase trimmed -> Original
     */
    private Map<String, String> keys = new HashMap<String, String>();
    private Map<Node, String> reverseMappedNodes = new LinkedHashMap<Node, String>();

    public MapNode()
    {
    }

    @Override
    public Map<String, Node> getValue()
    {
        return this.getMappedNodes();
    }

    /**
     * Creates an empty MapNode
     *
     * @return an empty MapNode
     */
    public static MapNode emptyMap()
    {
        return new MapNode();
    }

    @Override
    public Node get(String key)
    {
        return this.mappedNodes.get(key.trim().toLowerCase());
    }

    @Override
    public final Node set(String key, Node node)
    {
        String loweredKey = key.trim().toLowerCase();
        if (loweredKey.isEmpty())
        {
            throw new IllegalArgumentException("The key for the following node is empty!" + node.toString());
        }
        this.keys.put(loweredKey, key);
        this.reverseMappedNodes.put(node, loweredKey);
        return this.mappedNodes.put(loweredKey, node);
    }

    @Override
    protected final Node remove(String key)
    {
        Node node = this.mappedNodes.remove(key);
        if (node instanceof NullNode)
        {
            this.reverseMappedNodes.remove(node);
            this.mappedNodes.remove(key);
            return null;
        }
        return node;
    }

    public String getOriginalKey(String lowerCasedKey)
    {
        return this.keys.get(lowerCasedKey);
    }

    public Map<String, Node> getMappedNodes()
    {
        return mappedNodes;
    }

    @Override
    public boolean isEmpty()
    {
        return this.mappedNodes.isEmpty();
    }

    @Override
    public void cleanUpEmptyNodes()
    {
        Set<String> nodesToRemove = new HashSet<String>();
        for (String key : this.mappedNodes.keySet())
        {
            if (this.mappedNodes.get(key) instanceof ContainerNode)
            {
                ((ContainerNode)this.mappedNodes.get(key)).cleanUpEmptyNodes();
                if (((ContainerNode)this.mappedNodes.get(key)).isEmpty())
                {
                    nodesToRemove.add(key);
                }
            }
        }
        for (String key : nodesToRemove)
        {
            this.mappedNodes.remove(key);
        }
    }

    @Override
    public String asString()
    {
        StringBuilder sb = new StringBuilder("MapNode=[");
        for (Entry<String, Node> entry : this.mappedNodes.entrySet())
        {
            sb.append("\n").append(entry.getKey()).append(": ").append(entry.getValue().asString());
        }
        sb.append("]MapEnd");
        return sb.toString();
    }

    /**
     * Returns the first key of this MapNode or null if the map is empty
     *
     * @return the first key or null
     */
    public String getFirstKey()
    {
        if (this.mappedNodes.isEmpty())
        {
            return null;
        }
        return this.mappedNodes.keySet().iterator().next();
    }

    public void inheritFrom(Node node)
    {
        if (node instanceof MapNode)
        {
            Map<String, Node> inheritFrom = ((MapNode)node).getMappedNodes();
            for (Entry<String, Node> entry : inheritFrom.entrySet())
            {
                Node mapped = mappedNodes.get(entry.getKey());
                if (mapped == null || mapped instanceof NullNode)
                {
                    Node inherited = entry.getValue();
                    inherited.setInherited(true);
                    this.mappedNodes.put(entry.getKey(), inherited);
                }
                if (mapped instanceof MapNode)
                {
                    ((MapNode)mapped).inheritFrom(entry.getValue());
                }
            }
        }
    }
} public abstract class Node<V> extends Commentable
{
    private boolean inherited = false;

    /**
     * Tries to convert the value of the Node into a string
     *
     * @return the converted Node value
     */
    public abstract String asText();

    /**
     * Gets the Value contained in this Node
     *
     * @return the NodeValue
     */
    public abstract V getValue();

    public abstract String asString();

    /**
     * Wraps a serialized Object into a Node
     *
     * @param o a serialized Object
     *
     * @return the wrapped object
     */
    /*
    public static Node wrapIntoNode(Object o)
    {
        if (o == null)
        {
            return NullNode.emptyNode();
        }
        if (o instanceof Map)
        {
            return new MapNode((Map<?, ?>)o);
        }
        if (o instanceof Collection)
        {
            return new ListNode((Iterable<?>)o);
        }
        if (o.getClass().isArray())
        {
            return new ListNode((Object[])o);
        }
        if (o instanceof String)
        {
            return new StringNode((String)o);
        }
        if (o instanceof Byte || o.getClass() == byte.class)
        {
            return new ByteNode((Byte)o);
        }
        if (o instanceof Short || o.getClass() == short.class)
        {
            return new ShortNode((Short)o);
        }
        if (o instanceof Integer || o.getClass() == int.class)
        {
            return new IntNode((Integer)o);
        }
        if (o instanceof Long || o.getClass() == long.class)
        {
            return new LongNode((Long)o);
        }
        if (o instanceof Float || o.getClass() == float.class)
        {
            return new FloatNode((Float)o);
        }
        if (o instanceof Double || o.getClass() == double.class)
        {
            return new DoubleNode((Double)o);
        }
        if (o instanceof Boolean || o.getClass() == boolean.class)
        {
            return BooleanNode.of((Boolean)o);
        }
        if (o instanceof Character || o.getClass() == char.class)
        {
            return new CharNode((Character)o);
        }
        throw new IllegalArgumentException("Cannot wrap into Node: " + o.getClass());
    }
*/

    public boolean isInherited()
    {
        return inherited;
    }

    public void setInherited(boolean inherited)
    {
        this.inherited = inherited;
    }
} public class NullNode extends Node
{
    private NullNode()
    {
    }

    /**
     * Creates a NullNode
     *
     * @return the NullNode
     */
    public static NullNode emptyNode()
    {
        return new NullNode();
    }

    @Override
    public Object getValue()
    {
        return null;
    }

    @Override
    public String asText()
    {
        return "";
    }

    @Override
    public String asString()
    {
        return "NullNode";
    }
} public class Path
{
    private final List<String> parts;

    /**
     * Constructs a path using given parts.
     *
     * @param parts the parts
     */
    public Path(String... parts)
    {
        this(Arrays.asList(parts));
    }

    /**
     * Constructs a path using given separator and path.
     *
     * @param separator the separator
     * @param path      the path
     */
    public Path(String separator, String path)
    {
        this(path.split(Pattern.quote(separator)));
    }

    /**
     * Constructs a path using given separator and path.
     *
     * @param separator the separator
     * @param path      the path
     */
    public Path(char separator, String path)
    {
        this(String.valueOf(separator), path);
    }

    /**
     * Constructs a path using give parts
     *
     * @param parts the parts
     */
    public Path(List<String> parts)
    {
        this.parts = Collections.unmodifiableList(parts);
    }

    /**
     * Gets the parts making up this part
     *
     * @return the parts
     */
    public List<String> getParts()
    {
        return parts;
    }

    /**
     * Gets the amount of parts of this path
     *
     * @return the amount of parts
     */
    public int getSize()
    {
        return this.parts.size();
    }

    /**
     * Gets a String representation of this Path with given separator
     *
     * @param separator the separator
     *
     * @return the String representation
     */
    public String asString(String separator)
    {
        String sep = "";
        StringBuilder sb = new StringBuilder();
        for (String part : this.parts)
        {
            sb.append(sep).append(part);
            sep = separator;
        }
        return sb.toString();
    }

    /**
     * Gets a String representation of this Path with given separator
     *
     * @param separator the separator
     *
     * @return the String representation
     */
    public String asString(char separator)
    {
        return asString(String.valueOf(separator));
    }

    /**
     * Returns the last part of this path
     *
     * @return the last part
     */
    public String getLast()
    {
        return this.parts.get(getSize() - 1);
    }

    /**
     * Returns the first part of this path
     *
     * @return the first part
     */
    public String getFirst()
    {
        return this.parts.get(0);
    }

    /**
     * Returns whether this path has only one part
     *
     * @return true when this path has one part
     */
    public boolean isBasePath()
    {
        return getSize() == 1;
    }

    // TODO needed?
    public Path subPath()
    {
        return new Path(parts.subList(1, getSize()));
    }
} public class DuplicatedPathException extends RuntimeException
{
    public DuplicatedPathException(String s)
    {
        super(s);
    }
} public class FieldAccessException extends InvalidReflectedObjectException
{
    private FieldAccessException(String message)
    {
        super(message);
    }

    private FieldAccessException(String msg, Throwable t)
    {
        super(msg, t);
    }

    public static FieldAccessException of(Path path, Class<? extends Section> clazz, Field field, Throwable t)
    {
        String msg = "Could not access a field to get or set a value";
        msg += "\nField: " + field.getName();
        msg += "\nSection: " + clazz.toString();
        msg += "\nPath: " + path;
        if (t == null)
        {
            return new FieldAccessException(msg);
        }
        return new FieldAccessException(msg, t);
    }
} public class InvalidReflectedObjectException extends RuntimeException
{
    private static final long serialVersionUID = -492268712863444129L;

    public InvalidReflectedObjectException(String message)
    {
        super(message);
    }

    public InvalidReflectedObjectException(String msg, Throwable t)
    {
        super(msg, t);
    }

    public static InvalidReflectedObjectException of(String message, Path path, Class<? extends Section> clazz, Field field, Throwable t)
    {
        String msg = message + "\nField: " + field.getName();
        msg += "\nSection: " + clazz.toString();
        msg += "\nPath: " + path;
        if (t == null)
        {
            return new InvalidReflectedObjectException(msg);
        }
        return new InvalidReflectedObjectException(msg, t);
    }
} public class SectionFactory
{
    private SectionFactory()
    {
    }

    /**
     * Returns true if the {@link org.cubeengine.reflect.Section} class <code>isAssignableFrom</code> the given class
     *
     * @param clazz the class to check
     *
     * @return true if the given class implements {@link org.cubeengine.reflect.Section}
     */
    public static boolean isSectionClass(Class clazz)
    {
        return Section.class.isAssignableFrom(clazz);
    }

    /**
     * Creates a new Instance of the <code>sectionClass</code> using its default-constructor
     * <p>the <code>parent</code> is needed when trying to instantiating a non-static inner-class Section
     *
     * @param sectionClass the class of the Section to instantiate
     * @param parent       an instance of the enclosing class of the <code>sectionClass</code>
     *
     * @return the instantiated Section
     */
    public static Section newSectionInstance(Class<? extends Section> sectionClass, Object parent)
    {
        try
        {
            if (sectionClass.getEnclosingClass() == null)
            {
                return sectionClass.newInstance();
            }
            else if (Modifier.isStatic(sectionClass.getModifiers()))
            {
                return sectionClass.newInstance();
            }
            else
            {
                return sectionClass.getDeclaredConstructor(sectionClass.getEnclosingClass()).newInstance(parent);
            }
        }
        catch (InstantiationException e)
        {
            throw new ReflectedInstantiationException(sectionClass, e);
        }
        catch (InvocationTargetException e)
        {
            throw new ReflectedInstantiationException(sectionClass, e);
        }
        catch (NoSuchMethodException e)
        {
            throw new ReflectedInstantiationException(sectionClass, e);
        }
        catch (IllegalAccessException e)
        {
            throw new ReflectedInstantiationException(sectionClass, e);
        }
    }
} public class StringUtils
{
    private StringUtils()
    {
    }

    /**
     * This method merges an array of strings to a single string
     *
     * @param delimiter the delimiter
     * @param strings   the strings to implode
     *
     * @return the imploded string
     */
    public static String implode(String delimiter, String[] strings)
    {
        if (strings.length == 0)
        {
            return "";
        }
        else
        {
            StringBuilder sb = new StringBuilder(strings[0]);
            for (int i = 1; i < strings.length; i++)
            {
                sb.append(delimiter).append(strings[i]);
            }
            return sb.toString();
        }
    }

    /**
     * Converts a fieldName into a readable path
     *
     * @param fieldName the field name
     *
     * @return the converted field
     */
    public static String fieldNameToPath(String fieldName)
    {
        final StringBuilder path = new StringBuilder();
        boolean lastUpper = true;
        for (char c : fieldName.toCharArray())
        {
            if (c == '_')
            {
                lastUpper = true;
                path.append('.');
                continue;
            }
            if (Character.isUpperCase(c))
            {
                c = Character.toLowerCase(c);
                if (!lastUpper)
                {
                    lastUpper = true;
                    path.append('-');
                }
            }
            else
            {
                lastUpper = false;
            }
            path.append(c);
        }
        return path.toString();
    }

    public static boolean isEmpty(String string)
    {
        return "".equals(string);
    }
} public static final Logger LOGGER = Logger.getLogger("ReflecT");
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.cubeengine.converter.ConversionException;
import org.cubeengine.converter.ConverterManager;
import org.cubeengine.converter.converter.ClassedConverter;
import org.cubeengine.converter.node.MapNode;
import org.cubeengine.converter.node.Node;
import org.cubeengine.converter.node.NullNode;
import org.cubeengine.converter.node.Path;
import org.cubeengine.reflect.annotations.Comment;
import org.cubeengine.reflect.annotations.Name;
import org.cubeengine.reflect.exception.DuplicatedPathException;
import org.cubeengine.reflect.exception.FieldAccessException;
import org.cubeengine.reflect.exception.InvalidReflectedObjectException;
import org.cubeengine.reflect.util.SectionFactory;
import org.cubeengine.reflect.util.StringUtils;
import static org.cubeengine.reflect.Reflector.LOGGER;
import static org.cubeengine.reflect.annotations.Name.SEPARATOR;
import static java.util.logging.Level.FINE;
/*
 * The MIT License
 * Copyright © 2013 Cube Island
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.cubeengine.reflect;




/**
 * A converter for Sections.
 * <p>
 * This converter will cache the Fields of Sections to speed up repeated saving and loading of the same section
 */
public class SectionConverter implements ClassedConverter<Section>
{
    private static final String[] NO_COMMENT = new String[0];
    private final Map<Field, Path> paths = new HashMap<Field, Path>();
    private final Map<Class<? extends Section>, Field[]> cachedFields = new HashMap<Class<? extends Section>, Field[]>();
    private final Map<Field, String[]> comments = new HashMap<Field, String[]>();

    /**
     * Detects if given field needs to be serialized
     * <p>static and transient Field do not get converted
     *
     * @param field the field to check
     *
     * @return whether the field is a field of the reflected that needs to be serialized
     */
    public static boolean isReflectedField(Field field)
    {
        int modifiers = field.getModifiers();
        return !(Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers) || Modifier.isFinal(modifiers));
    }

    /**
     * Constructs a path for given Field
     *
     * @param field the field to get the path for
     *
     * @return the ReflectedPath
     */
    protected final Path getPathFor(Field field)
    {
        Path path = this.paths.get(field);
        if (path == null)
        {
            if (field.isAnnotationPresent(Name.class))
            {
                path = new Path(SEPARATOR, field.getAnnotation(Name.class).value());
            }
            else
            {
                path = new Path(SEPARATOR, StringUtils.fieldNameToPath(field.getName())); // TODO configurable Naming convention #20
            }
            this.paths.put(field, path);
        }
        return path;
    }

    public Node toNode(Section section, ConverterManager manager) throws ConversionException
    {
        if (!(manager instanceof ReflectedConverterManager))
        {
            throw new IllegalArgumentException("provided ConverterManager is not a ReflectedConverterManager");
        }
        ReflectedConverterManager rManager = (ReflectedConverterManager)manager;

        MapNode baseNode = MapNode.emptyMap();
        Class<? extends Section> sectionClass = section.getClass();

        for (Field field : this.getReflectedFields(sectionClass))
        {
            if (rManager.getReflected().isInheritedField(field))
            {
                continue; // do not save inherited field of child config
            }
            try
            {
                Node newNode = toNode(section, rManager, field);
                addComment(newNode, field);

                Node prevNode = baseNode.get(getPathFor(field));
                if (prevNode instanceof MapNode)
                {
                    if (newNode instanceof MapNode)
                    {
                        for (Entry<String, Node> entry : ((MapNode)newNode).getMappedNodes().entrySet())
                        {
                            ((MapNode)prevNode).set(entry.getKey(), entry.getValue());
                        }
                    }
                }
                else
                {
                    baseNode.set(getPathFor(field), newNode);
                }
            }
            catch (Exception e)
            {
                this.handleException(e, section, field);
            }
        }
        if (rManager.getReflected().isChild())
        {
            // remove generated empty ParentNodes ONLY from child-reflected
            baseNode.cleanUpEmptyNodes();
        }
        return baseNode;
    }

    @SuppressWarnings("unchecked")
    private Node toNode(Section section, ConverterManager manager, Field field) throws ConversionException, IllegalAccessException
    {
        Node newNode;
        if (field.isAnnotationPresent(org.cubeengine.reflect.annotations.Converter.class))
        {
            newNode = manager.getConverterByClass(field.getAnnotation(
                org.cubeengine.reflect.annotations.Converter.class).value()).toNode(field.get(section), manager);
        }
        else
        {
            newNode = manager.convertToNode(field.get(section));
        }
        return newNode;
    }

    /**
     * Adds a comment to the given Node
     *
     * @param node  the Node to add the comment to
     * @param field the field possibly having a {@link org.cubeengine.reflect.annotations.Comment} annotation
     */
    private void addComment(Node node, Field field)
    {
        String[] comment = this.comments.get(field);
        if (comment == null)
        {
            if (field.isAnnotationPresent(Comment.class))
            {
                comment = field.getAnnotation(Comment.class).value();
            }
            else
            {
                comment = NO_COMMENT;
            }
            this.comments.put(field, comment);
        }
        if (comment.length != 0)
        {
            node.setComments(comment);
        }
    }

    private void handleException(Exception e, Section section, Field field)
    {
        if (e instanceof InvalidReflectedObjectException)
        {
            throw (InvalidReflectedObjectException)e;
        }
        else if (e instanceof IllegalAccessException)
        {
            throw FieldAccessException.of(getPathFor(field), section.getClass(), field, e);