shuffle.fwk.config.ConfigManager.java Source code

Java tutorial

Introduction

Here is the source code for shuffle.fwk.config.ConfigManager.java

Source

/*  ShuffleMove - A program for identifying and simulating ideal moves in the game
 *  called Pokemon Shuffle.
 *  
 *  Copyright (C) 2015  Andrew Meyers
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  This program 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 General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package shuffle.fwk.config;

import java.awt.Color;
import java.awt.Font;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.IOUtils;

import shuffle.fwk.EntryMode;
import shuffle.fwk.GradingMode;
import shuffle.fwk.ShuffleVersion;
import shuffle.fwk.config.writer.PreferencesWriter;
import shuffle.fwk.data.Species;
import shuffle.fwk.data.SpeciesPaint;
import shuffle.fwk.data.Stage;
import shuffle.fwk.data.Team;
import shuffle.fwk.update.UpdateCheck;

/**
 * @author Andrew Meyers
 *
 */
public class ConfigManager {
    private static final Logger LOG = Logger.getLogger(ConfigManager.class.getName());
    static {
        LOG.setLevel(Level.FINE);
    }

    private static final String KEY_VERSION = "VERSION";
    private static final String KEY_BUILD_DATE = "BUILD_DATE";

    private final List<String> filePaths;
    private final ConfigLoader loader;
    private final ConfigFactory factory;
    private final LinkedHashMap<EntryType, LinkedHashMap<String, ConfigEntry>> data = new LinkedHashMap<EntryType, LinkedHashMap<String, ConfigEntry>>();
    private final Map<String, List<String>> savedDataStrings = new LinkedHashMap<String, List<String>>();

    public ConfigManager(List<String> resources, List<String> files, ConfigFactory factory) {
        this.factory = factory;
        loader = new ConfigLoader(resources, files);
        filePaths = files;
        loadFromConfig();
        savedDataStrings.clear();
        savedDataStrings.putAll(getDataStrings());
    }

    public ConfigManager(ConfigManager copyFrom) {
        factory = copyFrom.factory;
        filePaths = copyFrom.filePaths;
        loader = copyFrom.loader.getNewCopy();
        copyFromManager(copyFrom);
    }

    public ConfigFactory getFactory() {
        return factory;
    }

    public <T extends ConfigManager> boolean copyFromManager(T manager) {
        boolean changed = false;
        synchronized (data) {
            LinkedHashMap<String, List<String>> oldData = getDataStrings();
            data.clear();
            for (EntryType type : EntryType.values()) {
                LinkedHashMap<String, ConfigEntry> mappings = new LinkedHashMap<String, ConfigEntry>();
                List<String> keyOrder = new ArrayList<String>();
                for (String key : manager.getKeys(type)) {
                    ConfigEntry entry = manager.getEntry(type, key);
                    keyOrder.add(key);
                    mappings.put(key, entry);
                }
                data.put(type, mappings);
            }
            LinkedHashMap<String, List<String>> newData = getDataStrings();
            changed |= !oldData.equals(newData);
            onCopyFrom(manager);
        }
        return changed;
    }

    protected <T extends ConfigManager> void onCopyFrom(T manager) {
        // Empty default implementation
    }

    protected void clearMappingsOfType(EntryType type) {
        synchronized (data) {
            data.get(type).clear();
        }
    }

    protected void putAllOfType(EntryType type, Map<String, ? extends Object> map) {
        synchronized (data) {
            LinkedHashMap<String, ConfigEntry> mappings = data.get(type);
            Class<?> classForCast = type.getDataClass();
            for (String key : map.keySet()) {
                Object obj = map.get(key);
                if (obj != null && classForCast.isInstance(obj)) {
                    ConfigEntry entry = new ConfigEntry(type, obj);
                    mappings.put(key, entry);
                }
            }
        }
    }

    public boolean loadFromConfig() {
        loader.setForceReload(true);
        boolean changed = false;
        synchronized (data) {
            LinkedHashMap<String, List<String>> oldData = getDataStrings();
            data.clear();
            for (EntryType type : EntryType.values()) {
                LinkedHashMap<String, ConfigEntry> mappings = loader.getMappings(type);
                data.put(type, mappings);
            }
            maintainVersion();
            LinkedHashMap<String, List<String>> newData = getDataStrings();
            changed |= !oldData.equals(newData);
            if (changed) {
                savedDataStrings.clear();
                savedDataStrings.putAll(newData);
            }
        }
        return changed;
    }

    protected boolean shouldUpdate() {
        return false;
    }

    /**
     * 
     */
    protected void maintainVersion() {
        if (shouldUpdate()) {
            if (isOutOfDate()) {
                // we need to update from the default, overriding any out-dated keys
                loader.updateFromResource();
                for (EntryType type : EntryType.values()) {
                    LinkedHashMap<String, ConfigEntry> mappings = loader.getMappings(type);
                    data.put(type, mappings);
                }
            }
        }
        setDateStamp();
    }

    protected final void setDateStamp() {
        setEntry(EntryType.STRING, KEY_VERSION, ShuffleVersion.VERSION_FULL);
        setEntry(EntryType.LONG, KEY_BUILD_DATE, ShuffleVersion.BUILD_DATE);
    }

    public final boolean isOutOfDate() {
        try {
            String fileVersionString = getStringValue(KEY_VERSION, UpdateCheck.NO_VERSION);
            int[] fileVersionNumbers = new UpdateCheck().getVersionNumbers(fileVersionString);
            int[] curVersionNumbers = ShuffleVersion.VERSION_ARRAY;
            boolean uptodate = true;
            for (int i = 0; i < 3; i++) {
                if (fileVersionNumbers[i] > curVersionNumbers[i]) {
                    break;
                } else if (fileVersionNumbers[i] < curVersionNumbers[i]) {
                    uptodate = false;
                    break;
                }
            }
            Long fileBuildDate = getLongValue(KEY_BUILD_DATE, 0l);
            if (fileBuildDate < ShuffleVersion.BUILD_DATE) {
                uptodate = false;
            }
            return !uptodate;
        } catch (Exception e) {
            // then we assume it has no saved version.
            return true;
        }
    }

    public void saveDataToConfig() {
        PreferencesWriter pw = new PreferencesWriter(filePaths);
        LinkedHashMap<String, List<String>> dataToWrite;
        synchronized (data) {
            dataToWrite = getDataStrings();
            savedDataStrings.clear();
            savedDataStrings.putAll(dataToWrite);
        }
        pw.writePreferences(dataToWrite);
    }

    /**
     * @return
     */
    private LinkedHashMap<String, List<String>> getDataStrings() {
        LinkedHashMap<String, List<String>> dataToWrite = new LinkedHashMap<String, List<String>>();
        for (EntryType type : EntryType.values()) {
            if (!data.containsKey(type)) {
                continue;
            }
            List<String> linesToWrite = new ArrayList<String>();
            LinkedHashMap<String, ConfigEntry> typeMappings = data.get(type);
            for (String key : typeMappings.keySet()) {
                ConfigEntry entry = typeMappings.get(key);
                String entryString = safelyGetSaveString(entry);
                linesToWrite.add(String.format("%s %s", key, entryString));
            }
            dataToWrite.put(type.toString(), linesToWrite);
        }
        return dataToWrite;
    }

    /**
     * @param entry
     * @return
     * @throws Exception
     */
    private String safelyGetSaveString(ConfigEntry entry) {
        String saveString = null;
        try {
            saveString = entry.getSaveString();
        } catch (Exception e) {
            LOG.log(Level.WARNING, "Cannot get the save string for: " + entry, e);
        }
        return saveString;
    }

    public ConfigEntry getEntry(EntryType type, String key) {
        ConfigEntry value = null;
        if (key != null && type != null) {
            synchronized (data) {
                if (data.containsKey(type) && data.get(type).containsKey(key)) {
                    value = data.get(type).get(key);
                }
            }
        }
        return value;
    }

    /**
     * Sets the value for the given key to the given newValue.
     * 
     * @param type
     *           The EntryType for this value
     * @param key
     *           The key for this value
     * @param newValue
     *           The actual value for this value
     *           
     * @return True if anything changed. False otherwise.
     */
    public boolean setEntry(EntryType type, String key, ConfigEntry newValue) {
        if (key == null || type == null) {
            return false;
        }
        boolean changed = false;
        synchronized (data) {
            if (data.containsKey(type)) {
                LinkedHashMap<String, ConfigEntry> mappings = data.get(type);
                ConfigEntry oldValue = mappings.get(key);
                changed |= !(newValue == oldValue || newValue != null && newValue.equals(oldValue));
                if (newValue == null) {
                    mappings.remove(key);
                } else {
                    mappings.put(key, newValue);
                }
            }
        }
        return changed;
    }

    public boolean setEntry(String key, ConfigEntry newValue) {
        if (key == null || newValue == null) {
            return false;
        }
        return setEntry(newValue.getEntryType(), key, newValue);
    }

    public boolean setEntry(EntryType type, String key, Object newValue) {
        boolean result = false;
        try {
            ConfigEntry entry = newValue == null ? null : new ConfigEntry(type, newValue);
            result = setEntry(type, key, entry);
        } catch (Exception e) {
            LOG.log(Level.FINE,
                    String.format("Cannot parse config entry, key: %s type: %s value: %s ", key, type, newValue),
                    e);
        }
        return result;
    }

    public boolean removeEntry(EntryType type, String key) {
        return setEntry(type, key, null);
    }

    public boolean hasKey(EntryType type, String key) {
        boolean ret = false;
        synchronized (data) {
            if (type != null && key != null && data.containsKey(type)) {
                ret = data.get(type).containsKey(key);
            }
        }
        return ret;
    }

    public List<String> getKeys(EntryType type) {
        List<String> ret = new ArrayList<String>();
        synchronized (data) {
            ret = new ArrayList<String>(data.get(type).keySet());
        }
        return ret;
    }

    public <T extends Object> List<T> getValues(EntryType type, Class<T> c) {
        List<T> ret = new ArrayList<T>();
        synchronized (data) {
            LinkedHashMap<String, ConfigEntry> mappings = data.get(type);
            for (String key : mappings.keySet()) {
                ConfigEntry entry = mappings.get(key);
                Object value = entry.getValue();
                if (c.isInstance(value)) {
                    T casted = c.cast(value);
                    ret.add(casted);
                }
            }
        }
        return ret;
    }

    /**
     * Gets the value for the given key.
     * 
     * @param key
     * @param c
     * @return The value, null if not found.
     */
    public <T extends Object> T getValue(EntryType type, String key, Class<T> c) {
        T ret = null;
        synchronized (data) {
            ConfigEntry entry = data.get(type).get(key);
            if (entry != null) {
                Object value = entry.getValue();
                if (c.isInstance(value)) {
                    ret = c.cast(value);
                }
            }
        }
        return ret;
    }

    public <T extends Object> T getValue(EntryType type, String key, Class<T> c, T def) {
        T value = getValue(type, key, c);
        if (value == null) {
            value = def;
        }
        return value;
    }

    // Boolean
    public Boolean getBooleanValue(String key) {
        return getValue(EntryType.BOOLEAN, key, Boolean.class);
    }

    public Boolean getBooleanValue(String key, Boolean bak) {
        return getValue(EntryType.BOOLEAN, key, Boolean.class, bak);
    }

    public List<Boolean> getBooleanValues() {
        return getValues(EntryType.BOOLEAN, Boolean.class);
    }

    // Integer
    public Integer getIntegerValue(String key) {
        return getValue(EntryType.INTEGER, key, Integer.class);
    }

    public Integer getIntegerValue(String key, Integer def) {
        return getValue(EntryType.INTEGER, key, Integer.class, def);
    }

    public List<Integer> getIntegerValues() {
        return getValues(EntryType.INTEGER, Integer.class);
    }

    // Long
    public Long getLongValue(String key) {
        return getValue(EntryType.LONG, key, Long.class);
    }

    public Long getLongValue(String key, Long def) {
        return getValue(EntryType.LONG, key, Long.class, def);
    }

    public List<Long> getLongValues() {
        return getValues(EntryType.LONG, Long.class);
    }

    // String
    public String getStringValue(String key) {
        return getValue(EntryType.STRING, key, String.class);
    }

    public String getStringValue(String key, String def) {
        return getValue(EntryType.STRING, key, String.class, def);
    }

    public List<String> getStringValues() {
        return getValues(EntryType.STRING, String.class);
    }

    // Color
    public Color getColorValue(String key) {
        return getValue(EntryType.COLOR, key, Color.class);
    }

    public Color getColorValue(String key, Color def) {
        return getValue(EntryType.COLOR, key, Color.class, def);
    }

    public List<Color> getColorValues() {
        return getValues(EntryType.COLOR, Color.class);
    }

    public Color getColorFor(Object o) {
        Color ret = null;
        if (o != null) {
            Species s = null;
            boolean mega = false;
            if (o instanceof Species) {
                s = (Species) o;
            }
            if (o instanceof SpeciesPaint) {
                s = ((SpeciesPaint) o).getSpecies();
                mega = ((SpeciesPaint) o).isMega();
            }
            if (s != null) {
                if (mega) {
                    ret = getColorValue(s.getMegaName());
                    ret = getColorValue(s.getMegaName());
                }
                if (ret == null) {
                    ret = getColorValue(s.getName());
                }
            }
            if (ret == null && o instanceof EntryMode) {
                ret = getColorValue(((EntryMode) o).getColorKey());
            }
            if (ret == null) { // Otherwise, everything is by its 'toString()' representation.
                ret = getColorValue(o.toString());
            }
        }
        return ret;
    }

    // Species
    public Species getSpeciesValue(String key) {
        return getValue(EntryType.SPECIES, key, Species.class);
    }

    public Species getSpeciesValue(String key, Species def) {
        return getValue(EntryType.SPECIES, key, Species.class, def);
    }

    public List<Species> getSpeciesValues() {
        return getValues(EntryType.SPECIES, Species.class);
    }

    // Stage
    public Stage getStageValue(String key) {
        return getValue(EntryType.STAGE, key, Stage.class);
    }

    public Stage getStageValue(String key, Stage def) {
        return getValue(EntryType.STAGE, key, Stage.class, def);
    }

    public List<Stage> getStageValues() {
        return getValues(EntryType.STAGE, Stage.class);
    }

    // File
    public File getFileValue(String key) {
        return getValue(EntryType.FILE, key, File.class);
    }

    public File getFileValue(String key, File def) {
        return getValue(EntryType.FILE, key, File.class, def);
    }

    public List<File> getFileValues() {
        return getValues(EntryType.FILE, File.class);
    }

    /**
     * Reads the entire file for the given key into a String. If the key doesn't map to a file which
     * can be read this will return an empty String.
     * 
     * @param key
     * @return
     */
    public String readEntireFile(String key) {
        File f = getFileValue(key);
        return readEntireFileFromFile(f);
    }

    public String readEntireFileFromPath(String filePath) {
        String ret = null;
        if (filePath != null) {
            File f = new File(filePath);
            ret = readEntireFileFromFile(f);
        }
        return ret == null ? "" : ret;
    }

    private String readEntireFileFromFile(File f) {
        String ret = null;
        if (f != null && f.exists() && f.canRead()) {
            Path path = Paths.get(f.toURI());
            try {
                ret = new String(Files.readAllBytes(path), Charset.defaultCharset());
            } catch (IOException e) {
                LOG.log(Level.FINE, "Cannot parse file due to an IOException: ", e);
            }
        }
        return ret == null ? "" : ret;
    }

    // Team
    public Team getTeamValue(String key) {
        return getValue(EntryType.TEAM, key, Team.class);
    }

    public Team getTeamValue(String key, Team def) {
        return getValue(EntryType.TEAM, key, Team.class, def);
    }

    public List<Team> getTeamValues() {
        return getValues(EntryType.TEAM, Team.class);
    }

    // Grading Mode
    public GradingMode getGradingModeValue(String key) {
        return getValue(EntryType.GRADING_MODE, key, GradingMode.class);
    }

    public GradingMode getGradingModeValue(String key, GradingMode def) {
        return getValue(EntryType.GRADING_MODE, key, GradingMode.class, def);
    }

    public List<GradingMode> getGradingModeValues() {
        return getValues(EntryType.GRADING_MODE, GradingMode.class);
    }

    // Entry Mode
    public EntryMode getEntryModeValue(String key) {
        return getValue(EntryType.ENTRY_MODE, key, EntryMode.class);
    }

    public EntryMode getEntryModeValue(String key, EntryMode def) {
        return getValue(EntryType.ENTRY_MODE, key, EntryMode.class, def);
    }

    public List<EntryMode> getEntryModeValues() {
        return getValues(EntryType.ENTRY_MODE, EntryMode.class);
    }

    // Font
    public Font getFontValue(String key) {
        return getValue(EntryType.FONT, key, Font.class);
    }

    public Font getFontValue(String key, Font def) {
        return getValue(EntryType.FONT, key, Font.class, def);
    }

    public List<Font> getFontValues() {
        return getValues(EntryType.FONT, Font.class);
    }

    // Resource
    public String getResourceValue(String key) {
        return getValue(EntryType.RESOURCE, key, String.class);
    }

    public String getResourceValue(String key, String def) {
        return getValue(EntryType.RESOURCE, key, String.class, def);
    }

    public List<String> getResourceValues() {
        return getValues(EntryType.RESOURCE, String.class);
    }

    public InputStream getResourceInputStream(String path) {
        InputStream result = null;
        if (path != null) {
            result = ClassLoader.getSystemResourceAsStream(path);
        }
        return result;
    }

    /**
     * Reads the entire resource for the given key into a String. If the key doesn't refer to a path
     * for a resource that can be read, this will return "". Otherwise, it will return the string
     * representation of the byte data for the resource.
     * 
     * @param key
     * @return
     */
    public String readEntireResource(String key) {
        String path = getResourceValue(key);
        return readEntireResourceFromPath(path);
    }

    public String readEntireResourceFromPath(String path) {
        InputStream is = getResourceInputStream(path);
        String result = null;
        if (is != null) {
            try {
                result = IOUtils.toString(is);
            } catch (IOException e) {
                LOG.log(Level.FINE, "Cannot parse file due to an IOException: ", e);
            } finally {
                try {
                    is.close();
                } catch (IOException e) {
                    LOG.log(Level.FINE, "Cannot parse file due to an IOException: ", e);
                }
            }
        }
        return result == null ? "" : result;
    }

    public String readEntireFileOrResource(String key) {
        String ret = readEntireFile(key);
        if (ret.isEmpty()) {
            ret = readEntireResource(key);
        }
        return ret;
    }

    /**
     * Return the entire file or resource as a string, given the path.
     * 
     * @param path
     * @return Null if not found.
     */
    public String readEntireFileOrResourceFromPath(String path) {
        String ret = readEntireFileFromPath(path);
        if (ret.isEmpty()) {
            ret = readEntireResourceFromPath(path);
        }
        return ret;
    }

    /**
     * Return the entire file or resource as a String, given the path and fall-back key.
     * 
     * @param path
     *           Attempted first to find a non-empty document.
     * @param key
     *           Used if the path specified a non-existent, unreadable, or otherwise empty file.
     * @return the file, or resource of the path, or if not available then the file or resource
     *         specified by the key. Otherwise "" is returned.
     */
    public String readEntireDocument(String path, String key) {
        String ret = readEntireFileOrResourceFromPath(path);
        if (ret == null || ret.isEmpty()) {
            ret = readEntireFileOrResource(key);
        }
        return ret;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (data == null ? 0 : data.hashCode());
        result = prime * result + (factory == null ? 0 : factory.hashCode());
        result = prime * result + (filePaths == null ? 0 : filePaths.hashCode());
        result = prime * result + (loader == null ? 0 : loader.hashCode());
        return result;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (obj != null && this.getClass().isInstance(obj)) {
            ConfigManager manager = (ConfigManager) obj;
            boolean equal = true;
            equal |= data == manager.data || data != null && data.equals(manager.data);
            equal |= factory == manager.factory || factory != null && factory.equals(manager.factory);
            equal |= filePaths == manager.filePaths || filePaths != null && filePaths.equals(manager.filePaths);
            equal |= loader == manager.loader || loader != null && loader.equals(manager.loader);
            return equal;
        }
        return false;
    }

    /**
     * @return
     */
    public boolean dataChanged() {
        LinkedHashMap<String, List<String>> dataStrings = getDataStrings();
        return !savedDataStrings.equals(dataStrings);
    }

}