au.com.addstar.cellblock.configuration.AutoConfig.java Source code

Java tutorial

Introduction

Here is the source code for au.com.addstar.cellblock.configuration.AutoConfig.java

Source

/*
 * Copyright (c) 2017
 *
 * 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 au.com.addstar.cellblock.configuration;

import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.*;
import java.util.logging.Level;

/**
 * AutoConfiguration System
 * <p>
 * Allows for simple config file creation and handling.
 * Any class that extends this class can specify its fields to be config values using {@link ConfigField}
 * <p>
 * You should specify a default value for each field
 * <p>
 * Valid primitive field types are:
 * <ul>
 * <li>Short</li>
 * <li>Integer</li>
 * <li>Long</li>
 * <li>Float</li>
 * <li>Double</li>
 * <li>Boolean</li>
 * <li>String</li>
 * </ul>
 * <p>
 * Valid Complex field types are:
 * <ul>
 * <li>Array of any primitive type</li>
 * <li>List of any primitive type</li>
 * <li>Set of any primitive type</li>
 * </ul>
 * <p>
 * NOTE: You cannot use an abstract type for the type of a config field. The loader needs to know what class to instantiate.
 *
 * @author Schmoller
 * @version 1.5
 */
public abstract class AutoConfig {
    private final File mFile;
    private final HashMap<String, String> mCategoryComments;

    protected AutoConfig(File file) {
        mFile = file;
        mCategoryComments = new HashMap<>();
    }

    protected void setCategoryComment(String category, String comment) {
        mCategoryComments.put(category, comment);
    }

    /**
     * This should be used to process any data loaded from the config including
     * performing validation and translation
     *
     * @throws InvalidConfigurationException Thrown if a field has an invalid value
     */
    private void onPostLoad() throws InvalidConfigurationException {
    }

    /**
     * This should be used to update/convert any data in fields for saving
     */
    private void onPreSave() {
    }

    private <T> Set<T> newSet(Class<? extends Set<T>> setClass, Collection<T> data)
            throws InvalidConfigurationException {
        Validate.isTrue(!Modifier.isAbstract(setClass.getModifiers()),
                "You cannot use an abstract type for AutoConfiguration");

        Constructor<? extends Set<T>> constructor;

        try {
            constructor = setClass.getConstructor(Collection.class);

            return constructor.newInstance(data);
        } catch (Exception e) {
            throw new InvalidConfigurationException(e);
        }
    }

    private <T> List<T> newList(Class<? extends List<T>> listClass, Collection<T> data)
            throws InvalidConfigurationException {
        Validate.isTrue(!Modifier.isAbstract(listClass.getModifiers()),
                "You cannot use an abstract type for AutoConfiguration");

        Constructor<? extends List<T>> constructor;

        try {
            constructor = listClass.getConstructor(Collection.class);

            return constructor.newInstance(data);
        } catch (Exception e) {
            throw new InvalidConfigurationException(e);
        }
    }

    @SuppressWarnings("unchecked")
    public boolean load() {
        FileConfiguration yml = new YamlConfiguration();
        try {
            if (mFile.getParentFile().exists() || mFile.getParentFile().mkdirs()) {// Make sure the file exists
                if (mFile.exists() || mFile.createNewFile()) {
                    // Parse the config
                    yml.load(mFile);
                    for (Field field : getClass().getDeclaredFields()) {
                        ConfigField configField = field.getAnnotation(ConfigField.class);
                        if (configField == null)
                            continue;

                        String optionName = configField.name();
                        if (optionName.isEmpty())
                            optionName = field.getName();

                        field.setAccessible(true);

                        String path = (configField.category().isEmpty() ? "" : configField.category() + ".") //$NON-NLS-2$
                                + optionName;
                        if (!yml.contains(path)) {
                            if (field.get(this) == null)
                                throw new InvalidConfigurationException(
                                        path + " is required to be set! Info:\n" + configField.comment());
                        } else {
                            // Parse the value

                            if (field.getType().isArray()) {
                                // Integer
                                if (field.getType().getComponentType().equals(Integer.TYPE))
                                    field.set(this, yml.getIntegerList(path).toArray(new Integer[0]));

                                // Float
                                else if (field.getType().getComponentType().equals(Float.TYPE))
                                    field.set(this, yml.getFloatList(path).toArray(new Float[0]));

                                // Double
                                else if (field.getType().getComponentType().equals(Double.TYPE))
                                    field.set(this, yml.getDoubleList(path).toArray(new Double[0]));

                                // Long
                                else if (field.getType().getComponentType().equals(Long.TYPE))
                                    field.set(this, yml.getLongList(path).toArray(new Long[0]));

                                // Short
                                else if (field.getType().getComponentType().equals(Short.TYPE))
                                    field.set(this, yml.getShortList(path).toArray(new Short[0]));

                                // Boolean
                                else if (field.getType().getComponentType().equals(Boolean.TYPE))
                                    field.set(this, yml.getBooleanList(path).toArray(new Boolean[0]));

                                // String
                                else if (field.getType().getComponentType().equals(String.class)) {
                                    field.set(this, yml.getStringList(path).toArray(new String[0]));
                                } else
                                    throw new IllegalArgumentException("Cannot use type "
                                            + field.getType().getSimpleName() + " for AutoConfiguration"); //$NON-NLS-1$
                            } else if (List.class.isAssignableFrom(field.getType())) {
                                if (field.getGenericType() == null)
                                    throw new IllegalArgumentException(
                                            "Cannot use type List without specifying generic type for AutoConfiguration");

                                Type type = ((ParameterizedType) field.getGenericType())
                                        .getActualTypeArguments()[0];

                                if (type.equals(Integer.class))
                                    field.set(this, newList((Class<? extends List<Integer>>) field.getType(),
                                            yml.getIntegerList(path)));
                                else if (type.equals(Float.class))
                                    field.set(this, newList((Class<? extends List<Float>>) field.getType(),
                                            yml.getFloatList(path)));
                                else if (type.equals(Double.class))
                                    field.set(this, newList((Class<? extends List<Double>>) field.getType(),
                                            yml.getDoubleList(path)));
                                else if (type.equals(Long.class))
                                    field.set(this, newList((Class<? extends List<Long>>) field.getType(),
                                            yml.getLongList(path)));
                                else if (type.equals(Short.class))
                                    field.set(this, newList((Class<? extends List<Short>>) field.getType(),
                                            yml.getShortList(path)));
                                else if (type.equals(Boolean.class))
                                    field.set(this, newList((Class<? extends List<Boolean>>) field.getType(),
                                            yml.getBooleanList(path)));
                                else if (type.equals(String.class))
                                    field.set(this, newList((Class<? extends List<String>>) field.getType(),
                                            yml.getStringList(path)));
                                else
                                    throw new IllegalArgumentException(
                                            "Cannot use type " + field.getType().getSimpleName() + "<" //$NON-NLS-2$
                                                    + type.toString() + "> for AutoConfiguration");
                            } else if (Set.class.isAssignableFrom(field.getType())) {
                                if (field.getGenericType() == null)
                                    throw new IllegalArgumentException(
                                            "Cannot use type set without specifying generic type for AytoConfiguration");

                                Type type = ((ParameterizedType) field.getGenericType())
                                        .getActualTypeArguments()[0];

                                if (type.equals(Integer.class))
                                    field.set(this, newSet((Class<? extends Set<Integer>>) field.getType(),
                                            yml.getIntegerList(path)));
                                else if (type.equals(Float.class))
                                    field.set(this, newSet((Class<? extends Set<Float>>) field.getType(),
                                            yml.getFloatList(path)));
                                else if (type.equals(Double.class))
                                    field.set(this, newSet((Class<? extends Set<Double>>) field.getType(),
                                            yml.getDoubleList(path)));
                                else if (type.equals(Long.class))
                                    field.set(this, newSet((Class<? extends Set<Long>>) field.getType(),
                                            yml.getLongList(path)));
                                else if (type.equals(Short.class))
                                    field.set(this, newSet((Class<? extends Set<Short>>) field.getType(),
                                            yml.getShortList(path)));
                                else if (type.equals(Boolean.class))
                                    field.set(this, newSet((Class<? extends Set<Boolean>>) field.getType(),
                                            yml.getBooleanList(path)));
                                else if (type.equals(String.class))
                                    field.set(this, newSet((Class<? extends Set<String>>) field.getType(),
                                            yml.getStringList(path)));
                                else
                                    throw new IllegalArgumentException(
                                            "Cannot use type " + field.getType().getSimpleName() + "<" //$NON-NLS-2$
                                                    + type.toString() + "> for AutoConfiguration");
                            } else {
                                // Integer
                                if (field.getType().equals(Integer.TYPE))
                                    field.setInt(this, yml.getInt(path));

                                // Float
                                else if (field.getType().equals(Float.TYPE))
                                    field.setFloat(this, (float) yml.getDouble(path));

                                // Double
                                else if (field.getType().equals(Double.TYPE))
                                    field.setDouble(this, yml.getDouble(path));

                                // Long
                                else if (field.getType().equals(Long.TYPE))
                                    field.setLong(this, yml.getLong(path));

                                // Short
                                else if (field.getType().equals(Short.TYPE))
                                    field.setShort(this, (short) yml.getInt(path));

                                // Boolean
                                else if (field.getType().equals(Boolean.TYPE))
                                    field.setBoolean(this, yml.getBoolean(path));

                                // ItemStack
                                else if (field.getType().equals(ItemStack.class))
                                    field.set(this, yml.getItemStack(path));

                                // String
                                else if (field.getType().equals(String.class))
                                    field.set(this, yml.getString(path));
                                else
                                    throw new IllegalArgumentException("Cannot use type "
                                            + field.getType().getSimpleName() + " for AutoConfiguration"); //$NON-NLS-1$
                            }
                        }
                    }

                    onPostLoad();
                } else {
                    Bukkit.getLogger().log(Level.INFO, "Unable to create file: " + mFile.toString());
                }
            } else {
                Bukkit.getLogger().log(Level.INFO, "Unable to create file: " + mFile.getParentFile().toString());
            }
            return true;
        } catch (IOException | InvalidConfigurationException | IllegalAccessException
                | IllegalArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean save() {
        try {
            onPreSave();

            YamlConfiguration config = new YamlConfiguration();
            Map<String, String> comments = new HashMap<>();

            // Add all the category comments
            comments.putAll(mCategoryComments);

            // Add all the values
            for (Field field : getClass().getDeclaredFields()) {
                ConfigField configField = field.getAnnotation(ConfigField.class);
                if (configField == null)
                    continue;

                String optionName = configField.name();
                if (optionName.isEmpty())
                    optionName = field.getName();

                field.setAccessible(true);

                String path = (configField.category().isEmpty() ? "" : configField.category() + ".") + optionName; //$NON-NLS-2$

                // Ensure the secion exists
                if (!configField.category().isEmpty() && !config.contains(configField.category()))
                    config.createSection(configField.category());

                if (field.getType().isArray()) {
                    // Integer
                    if (field.getType().getComponentType().equals(Integer.TYPE))
                        config.set(path, Arrays.asList((Integer[]) field.get(this)));

                    // Float
                    else if (field.getType().getComponentType().equals(Float.TYPE))
                        config.set(path, Arrays.asList((Float[]) field.get(this)));

                    // Double
                    else if (field.getType().getComponentType().equals(Double.TYPE))
                        config.set(path, Arrays.asList((Double[]) field.get(this)));

                    // Long
                    else if (field.getType().getComponentType().equals(Long.TYPE))
                        config.set(path, Arrays.asList((Long[]) field.get(this)));

                    // Short
                    else if (field.getType().getComponentType().equals(Short.TYPE))
                        config.set(path, Arrays.asList((Short[]) field.get(this)));

                    // Boolean
                    else if (field.getType().getComponentType().equals(Boolean.TYPE))
                        config.set(path, Arrays.asList((Boolean[]) field.get(this)));

                    // String
                    else if (field.getType().getComponentType().equals(String.class))
                        config.set(path, Arrays.asList((String[]) field.get(this)));
                    else
                        throw new IllegalArgumentException(
                                "Cannot use type " + field.getType().getSimpleName() + " for AutoConfiguration"); //$NON-NLS-2$
                } else if (List.class.isAssignableFrom(field.getType())) {
                    if (field.getGenericType() == null)
                        throw new IllegalArgumentException(
                                "Cannot use type List without specifying generic type for AutoConfiguration");

                    Type type = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];

                    if (type.equals(Integer.class) || type.equals(Float.class) || type.equals(Double.class)
                            || type.equals(Long.class) || type.equals(Short.class) || type.equals(Boolean.class)
                            || type.equals(String.class)) {
                        config.set(path, field.get(this));
                    } else
                        throw new IllegalArgumentException("Cannot use type " + field.getType().getSimpleName()
                                + "<" + type.toString() + "> for AutoConfiguration"); //$NON-NLS-1$ //$NON-NLS-2$
                } else if (Set.class.isAssignableFrom(field.getType())) {
                    if (field.getGenericType() == null)
                        throw new IllegalArgumentException(
                                "Cannot use type Set without specifying generic type for AutoConfiguration");

                    Type type = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];

                    if (type.equals(Integer.class) || type.equals(Float.class) || type.equals(Double.class)
                            || type.equals(Long.class) || type.equals(Short.class) || type.equals(Boolean.class)
                            || type.equals(String.class)) {
                        config.set(path, new ArrayList<Object>((Set<?>) field.get(this)));
                    } else
                        throw new IllegalArgumentException("Cannot use type " + field.getType().getSimpleName()
                                + "<" + type.toString() + "> for AutoConfiguration"); //$NON-NLS-1$ //$NON-NLS-2$
                } else {
                    // Integer
                    if (field.getType().equals(Integer.TYPE))
                        config.set(path, field.get(this));

                    // Float
                    else if (field.getType().equals(Float.TYPE))
                        config.set(path, field.get(this));

                    // Double
                    else if (field.getType().equals(Double.TYPE))
                        config.set(path, field.get(this));

                    // Long
                    else if (field.getType().equals(Long.TYPE))
                        config.set(path, field.get(this));

                    // Short
                    else if (field.getType().equals(Short.TYPE))
                        config.set(path, field.get(this));

                    // Boolean
                    else if (field.getType().equals(Boolean.TYPE))
                        config.set(path, field.get(this));

                    // ItemStack
                    else if (field.getType().equals(ItemStack.class))
                        config.set(path, field.get(this));

                    // String
                    else if (field.getType().equals(String.class))
                        config.set(path, field.get(this));
                    else
                        throw new IllegalArgumentException(
                                "Cannot use type " + field.getType().getSimpleName() + " for AutoConfiguration"); //$NON-NLS-2$
                }

                // Record the comment
                if (!configField.comment().isEmpty())
                    comments.put(path, configField.comment());
            }

            String output = config.saveToString();

            // Apply comments
            String category = "";
            List<String> lines = new ArrayList<>(Arrays.asList(output.split("\n")));
            for (int l = 0; l < lines.size(); l++) {
                String line = lines.get(l);

                if (line.startsWith("#"))
                    continue;

                if (line.trim().startsWith("-"))
                    continue;

                if (!line.contains(":"))
                    continue;

                String path;
                line = line.substring(0, line.indexOf(":"));

                if (line.startsWith("  "))
                    path = category + "." + line.substring(2).trim();
                else {
                    category = line.trim();
                    path = line.trim();
                }

                if (comments.containsKey(path)) {
                    String indent = "";
                    for (int i = 0; i < line.length(); i++) {
                        if (line.charAt(i) == ' ')
                            indent += " ";
                        else
                            break;
                    }

                    // Add in the comment lines
                    String[] commentLines = comments.get(path).split("\n");
                    lines.add(l++, "");
                    for (int i = 0; i < commentLines.length; i++) {
                        commentLines[i] = indent + "# " + commentLines[i];
                        lines.add(l++, commentLines[i]);
                    }
                }
            }
            output = "";
            for (String line : lines)
                output += line + "\n";

            FileWriter writer = new FileWriter(mFile);
            writer.write(output);
            writer.close();
            return true;
        } catch (IllegalArgumentException | IOException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return false;
    }
}