de.cubeisland.engine.configuration.codec.ConfigurationCodec Maven / Gradle / Ivy
/**
* The MIT License (MIT)
*
* Copyright (c) 2013 Anselm Brehme, Phillip Schichtel
*
* 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 de.cubeisland.engine.configuration.codec;
import de.cubeisland.engine.configuration.*;
import de.cubeisland.engine.configuration.annotations.Comment;
import de.cubeisland.engine.configuration.annotations.Name;
import de.cubeisland.engine.configuration.convert.ConversionException;
import de.cubeisland.engine.configuration.convert.converter.generic.CollectionConverter;
import de.cubeisland.engine.configuration.convert.converter.generic.MapConverter;
import de.cubeisland.engine.configuration.node.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.*;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import static de.cubeisland.engine.configuration.Configuration.CONVERTERS;
import static de.cubeisland.engine.configuration.FieldType.*;
/**
* This abstract Codec can be implemented to read and write configurations that allow child-configs
*/
public abstract class ConfigurationCodec
{
// PUBLIC Methods
/**
* Loads in the given Configuration using the InputStream
* if the configuration has no default set it will be saved normally!
*
* @param config the Configuration to load
* @param is the InputStream to load from
*
* @return a collection of all erroneous Nodes
*/
public final Collection loadConfig(Configuration config, InputStream is)
{
return dumpIntoSection(config.getDefault(), config, this.load(is, config), config);
}
/**
* Saves the Configuration using given OutputStream
*
* @param config the Configuration to save
* @param os the OutputStream to save into
*/
public final void saveConfig(Configuration config, OutputStream os) throws IOException
{
this.save(convertSection(config.getDefault(), config, config), os, config);
}
/**
* Returns the FileExtension as String
*
* @return the fileExtension
*/
public abstract String getExtension();
// PROTECTED ABSTRACT Methods
/**
* Saves the values contained in the MapNode using given OutputStream
*
* @param node the Node containing all data to save
* @param os the File to save into
* @param config the Configuration
* @throws IOException
*/
protected abstract void save(MapNode node, OutputStream os, Configuration config) throws IOException;
/**
* Converts the InputStream into a MapNode
*
* @param is the InputStream to load from
* @param config the Configuration
*/
protected abstract MapNode load(InputStream is, Configuration config);
// PACKAGE-PRIVATE STATIC Methods
// Configuration loading Methods
/**
* Dumps the contents of the MapNode into the fields of the section using the defaultSection as default if a node is not given
*
* @param defaultSection the parent configuration-section
* @param section the configuration-section
* @param currentNode the Node to load from
* @param config the MultiConfiguration containing this section
*
* @return a collection of all erroneous Nodes
*/
final static Collection dumpIntoSection(Section defaultSection, Section section, MapNode currentNode, Configuration config)
{
if (defaultSection == null) // Special case for Section in Maps
{
defaultSection = section;
}
if (!defaultSection.getClass().equals(section.getClass()))
{
throw new IllegalArgumentException("defaultSection and section have to be the same type of section!");
}
Collection errorNodes = new HashSet();
for (Field field : section.getClass().getFields()) // ONLY public fields are allowed
{
if (isConfigField(field))
{
Node fieldNode = currentNode.getNodeAt(getPathFor(field));
if (fieldNode instanceof ErrorNode)
{
errorNodes.add((ErrorNode)fieldNode);
}
else
{
try
{
if (fieldNode instanceof NullNode)
{
errorNodes.addAll(dumpDefaultIntoField(defaultSection, section, field, config));
if (section != defaultSection)
{
config.addinheritedField(field);
}
}
else
{
errorNodes.addAll(dumpIntoField(defaultSection, section, field, fieldNode, config));
}
}
catch (Exception e)
{
throw InvalidConfigurationException.of("Error while dumping loaded config into fields!", getPathFor(field), section.getClass(), field, e);
}
}
}
}
return errorNodes;
}
/**
* Copy the contents of the parent section into a field of the section
*
* @param parentSection the parent configuration-section
* @param section the configuration-section
* @param field the Field to load into
*
* @return a collection of all erroneous Nodes
*/
@SuppressWarnings("unchecked")
final static Collection dumpDefaultIntoField(Section parentSection, Section section, Field field, Configuration config) throws ConversionException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException
{
if (parentSection != section)
{
if (getFieldType(field) == FieldType.SECTION_COLLECTION)
{
throw new InvalidConfigurationException("Child-Configurations are not allowed for Sections in Collections");
}
}
return dumpIntoField(parentSection, section, field, convertField(field, parentSection, parentSection, config), config); // convert parent in node and dump back in
}
/**
* Dumps the contents of the Node into a field of the section
*
* @param defaultSection the parent configuration-section
* @param section the configuration-section
* @param field the Field to load into
* @param fieldNode the Node to load from
* @param config the MultiConfiguration containing this section
*
* @return a collection of all erroneous Nodes
*/
@SuppressWarnings("unchecked")
final static Collection dumpIntoField(Section defaultSection, Section section, Field field, Node fieldNode, Configuration config) throws ConversionException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException
{
Collection errorNodes = new HashSet();
Type type = field.getGenericType();
FieldType fieldType = getFieldType(field);
Object fieldValue = null;
Object defaultValue = field.get(defaultSection);
switch (fieldType)
{
case NORMAL:
fieldValue = CONVERTERS.convertFromNode(fieldNode, type); // Convert the value
if (fieldValue == null && !(section == defaultSection))
{
fieldValue = field.get(defaultSection);
config.addinheritedField(field);
}
break;
case SECTION:
if (fieldNode instanceof MapNode)
{
fieldValue = SectionFactory.newSectionInstance((Class extends Section>) field.getType(), section);
if (defaultValue == null)
{
if (section == defaultSection)
{
defaultValue = fieldValue;
}
else
{
defaultValue = SectionFactory.newSectionInstance((Class extends Section>) field.getType(), defaultSection);
}
}
errorNodes.addAll(dumpIntoSection((Section) defaultValue, (Section) fieldValue, (MapNode) fieldNode, config));
}
else
{
throw new InvalidConfigurationException("Node for Section is not a MapNode!\n" + fieldNode);
}
break;
case SECTION_COLLECTION:
if (section != defaultSection)
{
throw new InvalidConfigurationException("Child-Configurations are not allowed for Sections in Collections");
}
if (fieldNode instanceof ListNode)
{
fieldValue = CollectionConverter.getCollectionFor((ParameterizedType) type);
if (((ListNode)fieldNode).isEmpty())
{
break;
}
Class extends Section> subSectionClass = (Class extends Section>)((ParameterizedType)type).getActualTypeArguments()[0];
for (Node listedNode : ((ListNode)fieldNode).getListedNodes())
{
if (listedNode instanceof NullNode)
{
listedNode = MapNode.emptyMap();
}
if (listedNode instanceof MapNode)
{
Section subSection = SectionFactory.newSectionInstance(subSectionClass, section);
errorNodes.addAll(dumpIntoSection(subSection, subSection, (MapNode) listedNode, config));
((Collection)fieldValue).add(subSection);
}
else
{
throw new InvalidConfigurationException("Node for listed Section is not a MapNode!\n" + listedNode);
}
}
}
else
{
throw new InvalidConfigurationException("Node for listed Sections is not a ListNode!\n" + fieldNode);
}
break;
case SECTION_MAP:
if (fieldNode instanceof MapNode)
{
fieldValue = MapConverter.getMapFor((ParameterizedType)type);
if (((MapNode)fieldNode).isEmpty())
if (((MapNode)fieldNode).isEmpty())
{
break;
}
Map