package rescuecore.config; import java.io.IOException; import java.io.FileReader; import java.io.BufferedReader; import java.io.PrintWriter; import java.io.File; import java.util.Map; import java.util.HashMap; import java.util.Collections; import java.util.Set; /** This class represents a config file and any other config files that might have been included with a !include directive. Config files must be defined relative to a base directory so that includes can be resolved. */ public class Config { private static final String INCLUDE = "!include"; /** The raw data and caches of int/float/boolean interpretations. */ private Map<String, String> data; private Map<String, Integer> intData; private Map<String, Double> floatData; private Map<String, Boolean> booleanData; /** Create an empty config. */ public Config() { data = new HashMap<String, String>(); intData = new HashMap<String, Integer>(); floatData = new HashMap<String, Double>(); booleanData = new HashMap<String, Boolean>(); } /** Create a config that reads from a given file. Additional config files can be read later with the {@link read(String)} method. @param file The config file to read. Must not be null. @throws IOException If there is an error reading the file. @throws ConfigException If there is an error parsing the config file or one of its descendants. */ public Config(File file) throws IOException, ConfigException { this(); read(file); } /** Read a config file and add its contents. Existing entries with the same name will be overwritten. @param file The config file to read. Must not be null. If this is a directory then all files in the directory will be read. @throws IOException If there is an error reading the file. @throws ConfigException If there is an error parsing the config file or one of its descendants. */ public void read(File file) throws IOException, ConfigException { if (file == null) { throw new IllegalArgumentException("File cannot be null"); } if (!file.exists()) { throw new IllegalArgumentException("File " + file.getAbsolutePath() + " does not exist"); } if (file.isDirectory()) { for (File next : file.listFiles()) { read(next); } } else { readConfigFile(file); } } /** Read config information from a Reader and add its contents. Existing entries with the same name will be overwritten. @param in The Reader to read from. Must not be null. @throws IOException If there is an error reading the file. @throws ConfigException If there is an error parsing the config file or one of its descendants. */ private void readConfigFile(File in) throws IOException, ConfigException { BufferedReader reader = new BufferedReader(new FileReader(in)); String name = in.getAbsolutePath(); String line = ""; int lineNumber = 0; try { while (line != null) { line = reader.readLine(); ++lineNumber; if (line != null) { line = line.trim(); // Ignore empty lines if ("".equals(line)) { continue; } // Ignore lines that start with # if (line.startsWith("#")) { continue; } // Look for a !include else if (line.startsWith(INCLUDE)) { if (INCLUDE.equals(line)) { throw new ConfigException(name, "Line " + lineNumber + ": Empty include directive"); } String includeName = line.substring(INCLUDE.length() + 1).trim(); if ("".equals(includeName)) { throw new ConfigException(name, "Line " + lineNumber + ": Empty include directive"); } read(new File(in.getParentFile(), includeName)); } else { int index = line.indexOf(':'); if (index == -1) { throw new ConfigException(name, "Line " + lineNumber + ": No ':' found"); } if (index == line.length() - 1) { throw new ConfigException(name, "Line " + lineNumber + ": No value found"); } if (index == 0) { throw new ConfigException(name, "Line " + lineNumber + ": No key found"); } String key = line.substring(0, index).trim(); String value = line.substring(index + 1).trim(); data.put(key, value); intData.remove(key); floatData.remove(key); booleanData.remove(key); } } } } finally { reader.close(); } } /** Write this config to a PrintWriter. @param out The PrintWriter to write to. Must not be null. @throws IOException If there is an error writing to the stream. */ public void write(PrintWriter out) throws IOException { if (out == null) { throw new IllegalArgumentException("Output cannot be null"); } for (Map.Entry<String, String> next : data.entrySet()) { out.print(next.getKey()); out.print(" : "); out.println(next.getValue()); } } /** Get all keys in this config. @return An immutable view of all keys. */ public Set<String> getAllKeys() { return Collections.unmodifiableSet(data.keySet()); } /** Get the value of a key as a String. @param key The key to look up. Must not be null. @return The value associated with that key. @throws NoSuchConfigOptionException If the key is not defined. */ public String getValue(String key) { if (key == null) { throw new IllegalArgumentException("Key cannot be null"); } if (!data.containsKey(key)) { throw new NoSuchConfigOptionException(key); } return data.get(key); } /** Get the value of a key as an integer. @param key The key to look up. Must not be null. @return The value associated with that key interpreted as an integer. @throws NoSuchConfigOptionException If the key is not defined. @throws NumberFormatException If the value of the key cannot be interpreted as an integer. */ public int getIntValue(String key) { if (key == null) { throw new IllegalArgumentException("Key cannot be null"); } if (intData.containsKey(key)) { return intData.get(key); } int result = Integer.parseInt(getValue(key)); intData.put(key, result); return result; } /** Get the value of a key as a floating point number. @param key The key to look up. Must not be null. @return The value associated with that key interpreted as a floating point number. @throws NoSuchConfigOptionException If the key is not defined. @throws NumberFormatException If the value of the key cannot be interpreted as a floating point number. */ public double getFloatValue(String key) { if (key == null) { throw new IllegalArgumentException("Key cannot be null"); } if (floatData.containsKey(key)) { return floatData.get(key); } double result = Double.parseDouble(getValue(key)); floatData.put(key, result); return result; } /** Get the value of a key as a boolean. "true", "t", "yes", "y" and "1" (case insensitive) are all interpreted as true, all other values are false. @param key The key to look up. Must not be null. @return The value associated with that key interpreted as a boolean. @throws NoSuchConfigOptionException If the key is not defined. */ public boolean getBooleanValue(String key) { if (key == null) { throw new IllegalArgumentException("Key cannot be null"); } if (booleanData.containsKey(key)) { return booleanData.get(key); } boolean result = false; String value = getValue(key); if ("true".equalsIgnoreCase(value) || "t".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value) || "y".equalsIgnoreCase(value) || "1".equalsIgnoreCase(value)) { result = true; } booleanData.put(key, result); return result; } /** Set the value of a key. @param key The key to set. Must not be null. @param value The new value. If this is null then {@link #removeKey(String)} is called with the given key. */ public void setValue(String key, String value) { if (key == null) { throw new IllegalArgumentException("Key cannot be null"); } if (value == null) { removeKey(key); return; } data.put(key, value); intData.remove(key); floatData.remove(key); booleanData.remove(key); } /** Set the value of a key as an integer. @param key The key to set. Must not be null. @param value The new value. */ public void setIntValue(String key, int value) { if (key == null) { throw new IllegalArgumentException("Key cannot be null"); } data.put(key, Integer.valueOf(value).toString()); intData.put(key, value); floatData.remove(key); booleanData.remove(key); } /** Set the value of a key as a floating point number. @param key The key to set. Must not be null. @param value The new value. */ public void setFloatValue(String key, double value) { if (key == null) { throw new IllegalArgumentException("Key cannot be null"); } data.put(key, Double.valueOf(value).toString()); intData.remove(key); floatData.put(key, value); booleanData.remove(key); } /** Set the value of a key as a boolean. @param key The key to set. Must not be null. @param value The new value. */ public void setBooleanValue(String key, boolean value) { if (key == null) { throw new IllegalArgumentException("Key cannot be null"); } data.put(key, value ? "true" : "false"); intData.remove(key); floatData.remove(key); booleanData.put(key, value); } /** Remove a key from the config. @param key The key to remove. Must not be null. */ public void removeKey(String key) { if (key == null) { throw new IllegalArgumentException("Key cannot be null"); } data.remove(key); intData.remove(key); floatData.remove(key); booleanData.remove(key); } /** Remove all keys. */ public void removeAllKeys() { data.clear(); intData.clear(); floatData.clear(); booleanData.clear(); } }