org.spout.api.util.config.annotated.AnnotatedObjectConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.spout.api.util.config.annotated.AnnotatedObjectConfiguration.java

Source

/*
 * This file is part of SpoutAPI.
 *
 * Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
 * SpoutAPI is licensed under the SpoutDev License Version 1.
 *
 * SpoutAPI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * In addition, 180 days after any changes are published, you can use the
 * software, incorporating those changes, under the terms of the MIT license,
 * as described in the SpoutDev License Version 1.
 *
 * SpoutAPI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License,
 * the MIT license and the SpoutDev License Version 1 along with this program.
 * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
 * License and see <http://www.spout.org/SpoutDevLicenseV1.txt> for the full license,
 * including the MIT license.
 */
package org.spout.api.util.config.annotated;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang3.ArrayUtils;

import org.spout.api.exception.ConfigurationException;
import org.spout.api.util.ReflectionUtils;
import org.spout.api.util.config.Configuration;
import org.spout.api.util.config.ConfigurationNode;
import org.spout.api.util.config.ConfigurationNodeSource;

/**
 * A configuration wrapper used to save annotated objects.
 * <p>
 * Fields from objects annotated with Setting will be saved to the configuration
 * file. Methods from objects annotated with Load or Save and with a
 * ConfigurationNode as a parameter will be invoked during the corresponding
 * event.
 * <p>
 * Example:
 * 
 * <pre>
 * public class AnnotatedObjectExample {
 *     {@code @Setting}
 *     private int num = 42;
 *     {@code @Setting}({"foo", "bar"})
 *     private String str = "hello world";
 *     private long abc = 1234567890;
 * 
 *     {@code @Load}
 *     private void load(ConfigurationNode node) {
 *         abc = node.getNode("abc").getLong();
 *     }
 * 
 *     {@code @Save}
 *     private void save(ConfigurationNode node) {
 *         node.getNode("abc").setValue(long);
 *     }
 * }
 * </pre>
 * 
 * A new instance is made from the class above, and the object is saved with
 * path "example".
 * <p>
 * This will result in a configuration file which will look like this:
 * 
 * <pre>
 * example:
 *     num: 42
 *     foo:
 *         bar: hello world
 *     abc: 1234567890
 * </pre>
 * 
 */
public class AnnotatedObjectConfiguration extends AnnotatedConfiguration {
    private final Map<Object, Set<Member>> objectMembers = new HashMap<Object, Set<Member>>();
    private final Map<Object, String[]> objectPaths = new HashMap<Object, String[]>();

    /**
     * Creates a new empty AnnotatedObjectConfiguration wrapper.
     */
    public AnnotatedObjectConfiguration() {
    }

    /**
     * Creates a new AnnotatedObjectConfiguration wrapper wrapping the provided
     * configuration.
     */
    public AnnotatedObjectConfiguration(Configuration config) {
        super(config);
    }

    /**
     * Adds an object to be saved or loaded by the configuration.
     * <p>
     * The node path of the object in the configuration is specified as an array
     * (varargs) of strings. Only annotated fields and methods will be used.
     * <p>
     * The individual paths of the fields can be specified with the Setting
     * annotation. If none are specified, the field name is used.
     * <p>
     * A field named "exa" annotated with {@code @Setting({"cd.ef"})} in an object with
     * path "ab" will have its value saved at "ab.cd.ef". If no path is
     * specified in Setting, the path will be "ab.exa".
     *
     * @param object The object to load or save
     * @param path The path at which the object should be or is located in the
     * configuration
     */
    public void addObject(Object object, String... path) {
        if (!objectMembers.containsKey(object)) {
            final Set<Member> members = new HashSet<Member>();
            members.addAll(ReflectionUtils.getDeclaredFieldsRecur(object.getClass(), Setting.class));
            members.addAll(ReflectionUtils.getDeclaredMethodsRecur(object.getClass(), Load.class, Save.class));
            objectMembers.put(object, members);
        }
        if (!objectPaths.containsKey(object)) {
            objectPaths.put(object, path);
        }
    }

    /**
     * Removes an object from the configuration. It will not be used during the
     * next save or load invocations.
     *
     * @param object
     */
    public void removeObject(Object object) {
        objectMembers.remove(object);
        objectPaths.remove(object);
    }

    @Override
    public void load(ConfigurationNodeSource source) throws ConfigurationException {
        for (Entry<Object, Set<Member>> entry : objectMembers.entrySet()) {
            final Object object = entry.getKey();
            final String[] objectPath = objectPaths.get(object);
            final Set<Method> methods = new HashSet<Method>();
            for (Member member : entry.getValue()) {
                if (member instanceof Method) {
                    final Method method = (Method) member;
                    if (method.isAnnotationPresent(Load.class)) {
                        methods.add(method);
                    }
                    continue;
                }
                final Field field = (Field) member;
                field.setAccessible(true);
                String[] fieldPath = field.getAnnotation(Setting.class).value();
                if (fieldPath.length == 0) {
                    fieldPath = new String[] { field.getName() };
                }
                final ConfigurationNode fieldNode = source.getNode(ArrayUtils.addAll(objectPath, fieldPath));
                final Object value = fieldNode.getTypedValue(field.getGenericType());
                try {
                    if (value != null) {
                        field.set(object, value);
                    } else {
                        fieldNode.setValue(field.getGenericType(), field.get(object));
                    }
                } catch (IllegalAccessException ex) {
                    throw new ConfigurationException(ex);
                }
            }
            invokeMethods(methods, object, source.getNode(objectPath));
        }
    }

    @Override
    public void save(ConfigurationNodeSource source) throws ConfigurationException {
        for (Entry<Object, Set<Member>> entry : objectMembers.entrySet()) {
            final Object object = entry.getKey();
            final String[] objectPath = objectPaths.get(object);
            final Set<Method> methods = new HashSet<Method>();
            for (Member member : entry.getValue()) {
                if (member instanceof Method) {
                    final Method method = (Method) member;
                    if (method.isAnnotationPresent(Save.class)) {
                        methods.add(method);
                    }
                    continue;
                }
                final Field field = (Field) member;
                field.setAccessible(true);
                String[] fieldPath = field.getAnnotation(Setting.class).value();
                if (fieldPath.length == 0) {
                    fieldPath = new String[] { field.getName() };
                }
                final ConfigurationNode fieldNode = source.getNode(ArrayUtils.addAll(objectPath, fieldPath));
                try {
                    fieldNode.setValue(field.getGenericType(), field.get(object));
                } catch (IllegalAccessException ex) {
                    throw new ConfigurationException(ex);
                }
            }
            invokeMethods(methods, object, source.getNode(objectPath));
        }
    }

    private void invokeMethods(Set<Method> methods, Object target, ConfigurationNode nodeParam)
            throws ConfigurationException {
        for (Method method : methods) {
            method.setAccessible(true);
            Class<?>[] parameters = method.getParameterTypes();
            if (parameters.length == 0 || !ConfigurationNode.class.isAssignableFrom(parameters[0])) {
                continue;
            }
            try {
                method.invoke(target, nodeParam);
            } catch (IllegalAccessException ex) {
                throw new ConfigurationException(ex);
            } catch (InvocationTargetException ex) {
                throw new ConfigurationException(ex);
            }
        }
    }
}