package com.alecgorge.minecraft.jsonapi.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* Used for accessing and creating .[properties] files, reads them as utf-8, saves as utf-8.
* Internationalization is key importance especially for character codes.
*
* @author Nijikokun
* @version 1.0.4, %G%
*/
public final class PropertiesFile {
private static final Logger log = Logger.getLogger("Minecraft");
private String fileName;
private List<String> lines = new ArrayList<String>();
private Map<String, String> props = new HashMap<String, String>();
/**
* Creates or opens a properties file using specified filename
*
* @param fileName
*/
public PropertiesFile(String fileName) {
this.fileName = fileName;
File file = new File(fileName);
if (file.exists()) {
try {
load();
} catch (IOException ex) {
log.severe("[PropertiesFile] Unable to load " + fileName + "!");
}
} else {
save();
}
}
/**
* The loader for property files, it reads the file as UTF8 or converts the string into UTF8.
* Used for simple runthrough's, loading, or reloading of the file.
*
* @throws IOException
*/
public void load() throws IOException {
BufferedReader reader;
reader = new BufferedReader(new InputStreamReader(new FileInputStream(this.fileName), "UTF8"));
String line;
// Clear the file & unwritten properties
lines.clear();
props.clear();
// Begin reading the file.
while ((line = reader.readLine()) != null) {
line = new String(line.getBytes(), "UTF-8");
char c = 0;
int pos = 0;
while (pos < line.length() && Character.isWhitespace(c = line.charAt(pos))) {
pos++;
}
if ((line.length() - pos) == 0 || line.charAt(pos) == '#' || line.charAt(pos) == '!') {
lines.add(line);
continue;
}
int start = pos;
boolean needsEscape = line.indexOf('\\', pos) != -1;
StringBuffer key = needsEscape ? new StringBuffer() : null;
while (pos < line.length() && !Character.isWhitespace(c = line.charAt(pos++)) && c != '=' && c != ':') {
if (needsEscape && c == '\\') {
if (pos == line.length()) {
line = reader.readLine();
if (line == null) {
line = "";
}
pos = 0;
while (pos < line.length() && Character.isWhitespace(c = line.charAt(pos))) {
pos++;
}
} else {
c = line.charAt(pos++);
switch (c) {
case 'n':
key.append('\n');
break;
case 't':
key.append('\t');
break;
case 'r':
key.append('\r');
break;
case 'u':
if (pos + 4 <= line.length()) {
char uni = (char) Integer.parseInt(line.substring(pos, pos + 4), 16);
key.append(uni);
pos += 4;
}
break;
default:
key.append(c);
break;
}
}
} else if (needsEscape) {
key.append(c);
}
}
boolean isDelim = (c == ':' || c == '=');
String keyString;
if (needsEscape) {
keyString = key.toString();
} else if (isDelim || Character.isWhitespace(c)) {
keyString = line.substring(start, pos - 1);
} else {
keyString = line.substring(start, pos);
}
while (pos < line.length() && Character.isWhitespace(c = line.charAt(pos))) {
pos++;
}
if (!isDelim && (c == ':' || c == '=')) {
pos++;
while (pos < line.length() && Character.isWhitespace(c = line.charAt(pos))) {
pos++;
}
}
// Short-circuit if no escape chars found.
if (!needsEscape) {
lines.add(line);
continue;
}
// Escape char found so iterate through the rest of the line.
StringBuilder element = new StringBuilder(line.length() - pos);
while (pos < line.length()) {
c = line.charAt(pos++);
if (c == '\\') {
if (pos == line.length()) {
line = reader.readLine();
if (line == null) {
break;
}
pos = 0;
while (pos < line.length() && Character.isWhitespace(c = line.charAt(pos))) {
pos++;
}
element.ensureCapacity(line.length() - pos + element.length());
} else {
c = line.charAt(pos++);
switch (c) {
case 'n':
element.append('\n');
break;
case 't':
element.append('\t');
break;
case 'r':
element.append('\r');
break;
case 'u':
if (pos + 4 <= line.length()) {
char uni = (char) Integer.parseInt(line.substring(pos, pos + 4), 16);
element.append(uni);
pos += 4;
}
break;
default:
element.append(c);
break;
}
}
} else {
element.append(c);
}
}
lines.add(keyString + "=" + element.toString());
}
reader.close();
}
/**
* Writes out the <code>key=value</code> properties that were changed into
* a .[properties] file in UTF8.
*
* @see #load()
*/
public void save() {
OutputStream os = null;
try {
os = new FileOutputStream(this.fileName);
} catch (FileNotFoundException ex) {
log.severe("[PropertiesFile] Unable to open " + fileName + "!");
}
PrintStream ps = null;
try {
ps = new PrintStream(os, true, "UTF-8");
} catch (UnsupportedEncodingException ex) {
log.severe("[PropertiesFile] Unable to write to " + fileName + "!");
}
// Keep track of properties that were set
List<String> usedProps = new ArrayList<String>();
for (String line : this.lines) {
if (line.trim().length() == 0) {
ps.println(line);
continue;
}
if (line.charAt(0) == '#') {
ps.println(line);
continue;
}
if (line.contains("=")) {
int delimPosition = line.indexOf('=');
String key = line.substring(0, delimPosition).trim();
if (this.props.containsKey(key)) {
String value = this.props.get(key);
ps.println(key + "=" + value);
usedProps.add(key);
} else {
ps.println(line);
}
} else {
ps.println(line);
}
}
// Add any new properties
for (Map.Entry<String, String> entry : this.props.entrySet()) {
if (!usedProps.contains(entry.getKey())) {
ps.println(entry.getKey() + "=" + entry.getValue());
}
}
// Exit that stream
ps.close();
// Reload
try {
props.clear();
lines.clear();
this.load();
} catch (IOException ex) {
log.severe("[PropertiesFile] Unable to load " + fileName + "!");
}
}
/**
* Returns a Map of all <code>key=value</code> properties in the file as <code><key (java.lang.String), value (java.lang.String)></code>
* <br /><br />
* Example:
* <blockquote><pre>
* PropertiesFile settings = new PropertiesFile("settings.properties");
* Map<String, String> mappedSettings;
*
* try {
* mappedSettings = settings.returnMap();
* } catch (Exception ex) {
* log.info("Failed mapping settings.properties");
* }
* </pre></blockquote>
*
* @return <code>map</code> - Simple Map HashMap of the entire <code>key=value</code> as <code><key (java.lang.String), value (java.lang.String)></code>
* @throws Exception If the properties file doesn't exist.
*/
public Map<String, String> returnMap() throws Exception {
Map<String, String> map = new HashMap<String, String>();
BufferedReader reader = new BufferedReader(new FileReader(this.fileName));
String line;
while ((line = reader.readLine()) != null) {
if (line.trim().length() == 0) {
continue;
}
if (line.charAt(0) == '#') {
continue;
}
if (line.contains("=")) {
int delimPosition = line.indexOf('=');
String key = line.substring(0, delimPosition).trim();
String value = line.substring(delimPosition + 1).trim();
map.put(key, value);
} else {
continue;
}
}
reader.close();
return map;
}
/**
* Checks to see if the .[properties] file contains the given <code>key</code>.
*
* @param var The key we are going to be checking the existance of.
* @return <code>Boolean</code> - True if the <code>key</code> exists, false if it cannot be found.
*/
public boolean containsKey(String var) {
for (String line : this.lines) {
if (line.trim().length() == 0) {
continue;
}
if (line.charAt(0) == '#') {
continue;
}
if (line.contains("=")) {
int delimPosition = line.indexOf('=');
String key = line.substring(0, delimPosition);
if (key.equals(var)) {
return true;
}
} else {
continue;
}
}
return false;
}
/**
* Checks to see if this <code>key</code> exists in the .[properties] file.
*
* @param var The key we are grabbing the value of.
* @return <code>java.lang.String</code> - True if the <code>key</code> exists, false if it cannot be found.
*/
public String getProperty(String var) {
for (String line : this.lines) {
if (line.trim().length() == 0) {
continue;
}
if (line.charAt(0) == '#') {
continue;
}
if (line.contains("=")) {
int delimPosition = line.indexOf('=');
String key = line.substring(0, delimPosition).trim();
String value = line.substring(delimPosition + 1);
if (key.equals(var)) {
return value;
}
} else {
continue;
}
}
return "";
}
/**
* Remove a key from the file if it exists.
* This will save() which will invoke a load() on the file.
*
* @see #save()
* @param var The <code>key</code> that will be removed from the file
*/
public void removeKey(String var) {
Boolean changed = false;
if (this.props.containsKey(var)) {
this.props.remove(var);
changed = true;
}
try {
for (int i = 0; i < this.lines.size(); i++) {
String line = this.lines.get(i);
if (line.trim().length() == 0) {
continue;
}
if (line.charAt(0) == '#') {
continue;
}
if (line.contains("=")) {
int delimPosition = line.indexOf('=');
String key = line.substring(0, delimPosition).trim();
if (key.equals(var)) {
this.lines.remove(i);
changed = true;
}
} else {
continue;
}
}
} catch (ConcurrentModificationException concEx) {
removeKey(var);
return;
}
// Save on change
if (changed) {
save();
}
}
/**
* Checks the existance of a <code>key</code>.
*
* @see #containsKey(java.lang.String)
* @param key The <code>key</code> in question of existance.
* @return <code>Boolean</code> - True for existance, false for <code>key</code> found.
*/
public boolean keyExists(String key) {
try {
return (this.containsKey(key)) ? true : false;
} catch (Exception ex) {
return false;
}
}
/**
* Returns the value of the <code>key</code> given as a <code>String</code>,
* however we do not set a string if no <code>key</code> is found.
*
* @see #getProperty(java.lang.String)
* @param key The <code>key</code> we will retrieve the property from, if no <code>key</code> is found default to "" or empty.
*/
public String getString(String key) {
if (this.containsKey(key)) {
return this.getProperty(key);
}
return "";
}
/**
* Returns the value of the <code>key</code> given as a <code>String</code>.
* If it is not found, it will invoke saving the default <code>value</code> to the properties file.
*
* @see #setString(java.lang.String, java.lang.String)
* @see #getProperty(java.lang.String)
* @param key The key that we will be grabbing the value from, if no value is found set and return <code>value</code>
* @param value The default value that we will be setting if no prior <code>key</code> is found.
* @return java.lang.String Either we will return the default value or a prior existing value depending on existance.
*/
public String getString(String key, String value) {
if (this.containsKey(key)) {
return this.getProperty(key);
}
setString(key, value);
return value;
}
/**
* Save the value given as a <code>String</code> on the specified key.
*
* @see #save()
* @param key The <code>key</code> that we will be addressing the <code>value</code> to.
* @param value The <code>value</code> we will be setting inside the <code>.[properties]</code> file.
*/
public void setString(String key, String value) {
props.put(key, value);
save();
}
/**
* Returns the value of the <code>key</code> given in a Integer,
* however we do not set a string if no <code>key</code> is found.
*
* @see #getProperty(String var)
* @param key The <code>key</code> we will retrieve the property from, if no <code>key</code> is found default to 0
*/
public int getInt(String key) {
if (this.containsKey(key)) {
return Integer.parseInt(this.getProperty(key));
}
return 0;
}
/**
* Returns the int value of a key
*
* @see #setInt(String key, int value)
* @param key The key that we will be grabbing the value from, if no value is found set and return <code>value</code>
* @param value The default value that we will be setting if no prior <code>key</code> is found.
* @return <code>Integer</code> - Either we will return the default value or a prior existing value depending on existance.
*/
public int getInt(String key, int value) {
if (this.containsKey(key)) {
return Integer.parseInt(this.getProperty(key));
}
setInt(key, value);
return value;
}
/**
* Save the value given as a <code>int</code> on the specified key.
*
* @see #save()
* @param key The <code>key</code> that we will be addressing the <code>value</code> to.
* @param value The <code>value</code> we will be setting inside the <code>.[properties]</code> file.
*/
public void setInt(String key, int value) {
props.put(key, String.valueOf(value));
save();
}
/**
* Returns the value of the <code>key</code> given in a Double,
* however we do not set a string if no <code>key</code> is found.
*
* @see #getProperty(String var)
* @param key The <code>key</code> we will retrieve the property from, if no <code>key</code> is found default to 0.0
*/
public double getDouble(String key) {
if (this.containsKey(key)) {
return Double.parseDouble(this.getProperty(key));
}
return 0;
}
/**
* Returns the double value of a key
*
* @see #setDouble(String key, double value)
* @param key The key that we will be grabbing the value from, if no value is found set and return <code>value</code>
* @param value The default value that we will be setting if no prior <code>key</code> is found.
* @return <code>Double</code> - Either we will return the default value or a prior existing value depending on existance.
*/
public double getDouble(String key, double value) {
if (this.containsKey(key)) {
return Double.parseDouble(this.getProperty(key));
}
setDouble(key, value);
return value;
}
/**
* Save the value given as a <code>double</code> on the specified key.
*
* @see #save()
* @param key The <code>key</code> that we will be addressing the <code>value</code> to.
* @param value The <code>value</code> we will be setting inside the <code>.[properties]</code> file.
*/
public void setDouble(String key, double value) {
props.put(key, String.valueOf(value));
save();
}
/**
* Returns the value of the <code>key</code> given in a Long,
* however we do not set a string if no <code>key</code> is found.
*
* @see #getProperty(String var)
* @param key The <code>key</code> we will retrieve the property from, if no <code>key</code> is found default to 0L
*/
public long getLong(String key) {
if (this.containsKey(key)) {
return Long.parseLong(this.getProperty(key));
}
return 0;
}
/**
* Returns the long value of a key
*
* @see #setLong(String key, long value)
* @param key The key that we will be grabbing the value from, if no value is found set and return <code>value</code>
* @param value The default value that we will be setting if no prior <code>key</code> is found.
* @return <code>Long</code> - Either we will return the default value or a prior existing value depending on existance.
*/
public long getLong(String key, long value) {
if (this.containsKey(key)) {
return Long.parseLong(this.getProperty(key));
}
setLong(key, value);
return value;
}
/**
* Save the value given as a <code>long</code> on the specified key.
*
* @see #save()
* @param key The <code>key</code> that we will be addressing the <code>value</code> to.
* @param value The <code>value</code> we will be setting inside the <code>.[properties]</code> file.
*/
public void setLong(String key, long value) {
props.put(key, String.valueOf(value));
save();
}
/**
* Returns the value of the <code>key</code> given in a Boolean,
* however we do not set a string if no <code>key</code> is found.
*
* @see #getProperty(String var)
* @param key The <code>key</code> we will retrieve the property from, if no <code>key</code> is found default to false
*/
public boolean getBoolean(String key) {
if (this.containsKey(key)) {
return Boolean.parseBoolean(this.getProperty(key));
}
return false;
}
/**
* Returns the boolean value of a key
*
* @see #setBoolean(String key, boolean value)
* @param key The key that we will be grabbing the value from, if no value is found set and return <code>value</code>
* @param value The default value that we will be setting if no prior <code>key</code> is found.
* @return <code>Boolean</code> - Either we will return the default value or a prior existing value depending on existance.
*/
public boolean getBoolean(String key, boolean value) {
if (this.containsKey(key)) {
return Boolean.parseBoolean(this.getProperty(key));
}
setBoolean(key, value);
return value;
}
/**
* Save the value given as a <code>boolean</code> on the specified key.
*
* @see #save()
* @param key The <code>key</code> that we will be addressing the <code>value</code> to.
* @param value The <code>value</code> we will be setting inside the <code>.[properties]</code> file.
*/
public void setBoolean(String key, boolean value) {
props.put(key, String.valueOf(value));
save();
}
}