Skip to content

Instantly share code, notes, and snippets.

@justisr
Last active June 1, 2018 20:43
Show Gist options
  • Save justisr/ae6b296ff43529ade32f8ba36237d98d to your computer and use it in GitHub Desktop.
Save justisr/ae6b296ff43529ade32f8ba36237d98d to your computer and use it in GitHub Desktop.
//Created by Justis Root. Released into the public domain.
//https://gist.github.com/justisr
//
//Source is licensed for any use, provided that this copyright notice is retained.
//Modifications not expressly accepted by the author should be noted in the license of any forks.
//No warranty for any purpose whatsoever is implied or expressed,
//and the author shall not be held liable for any losses, direct or indirect as a result of using this software.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
/**
* A YAML-similar markup Language file wrapper
* @author Justis R
* @version 3.0.1
*/
public class MYMLFile {
private File file;
/**
* Create a MYMLFile wrapper for the file at the specified path with the specified name within the program's running folder.<br>
* @param path from the program's running folder to the destination.<br>
* Each parameter prior to the last being a folder.
*/
public MYMLFile(String... path) {
StringBuilder pathBuilder = new StringBuilder();
for (int i = 0; i < path.length; i++) pathBuilder.append(path[i] + File.separator);
file = new File(pathBuilder.toString());
reload();
}
/**
* Create a MYMLFile object for specified file.<br>
* @param file for which to wrap the MYMLFile object.
*/
public MYMLFile(File file) {
this.file = file;
reload();
}
/**
* Get the raw File object associated with this file wrapper instance.<br>
* @return File wrapped by this instance.
*/
public File getFile() {
return file;
}
/**
* Gets the file from disk or generates one if it doesn't exist.<br>
* Save all of the file's contents, all the paths, values, everything, to memory.<br>
* Overwrites any and all existing contents of memory<br>
* @throws IOException when the directory does not exist.
*/
public void reload() {
section = new Section(-1, null, "", "", new ArrayList<>());
try {
File direc = new File(file.getPath().replace(file.getName(), ""));
if (!direc.exists())
direc.mkdirs();
if (!file.exists())
file.createNewFile();
Scanner input = new Scanner(file);
List<String> header = new ArrayList<>();
Section current = section;
while (input.hasNextLine()) {
Section returned = current.nextLine(input.nextLine(), header);
if (current != returned)
header = new ArrayList<>();
current = returned;
}
if (section.children.isEmpty())
section.header = header;
else footer = header;
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Save all the current contents of memory to disk.<br>
* Overwrites any and all existing contents of the file<br>
* @throws IOException when the directory does not exist.
*/
public void save() {
try {
FileWriter fw = new FileWriter(file, false);
for (String line : getContents())
fw.write(line + "\n");
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private Section section;
private List<String> footer;
private static class Section {
private final Section parent;
private int tab;
private String key, value;
private List<String> header;
private final Map<String, Section> children = new LinkedHashMap<>();
private Section(int tab, Section parent, String key, String value, List<String> header) {
this.tab = tab;
this.parent = parent;
this.key = key;
this.value = value;
this.header = header;
}
private Section nextLine(String line, List<String> header) {
if (isCommented(line)) {
header.add(line);
return this;
} else {
int keyIndex = 0;
for (; keyIndex < line.length() ; keyIndex++) {
if (!Character.isWhitespace(line.charAt(keyIndex)))
break;
}
String value = line.replaceFirst(".*?" + VALUE_SEPARATOR + "\\s?", "");
String key;
if (value.length() == line.length()) {
value = line.substring(keyIndex);
key = value;
} else key = line.substring(keyIndex, line.length() - value.length() - 1);
if (key.length() > 1 && key.charAt(key.length()-1) == VALUE_SEPARATOR) key = key.substring(0, key.length()-1);
if (keyIndex > tab) return addChild(keyIndex, key, value, header);
else return nearestParent(keyIndex).addChild(keyIndex, key, value, header);
}
}
/**
* Create a child section of the current section.
* @param keyIndex The index of the first key character. Also the number of tab characters
* @param parent section of this new child
* @param key of the child section
* @param value of the child section
* @param comments for the child section
* @return a new child section of the current section with the applied settings.
*/
private Section addChild(int keyIndex, String key, String value, List<String> comments) {
Section child = new Section(keyIndex, this, key, value, comments);
children.put(key, child);
return child;
}
/**
* Get the nearest possible parent of the line with the specific tab intend
* @param indent of line whose parent is being looked for
* @return the nearest possible parent section of the line with that indent
*/
private Section nearestParent(int indent) {
return tab < indent ? this : parent.nearestParent(indent);
}
/**
* Replace the name of the specified section with a new one<br>
* <b>This will also move the section down to the bottom of whatever path branch it's on</b><br>
* Creates a new section with the specified name along the path if the section does not exist<br>
* @param key to rename the section to<br>
*/
private void setKey(String key) {
parent.children.remove(this.key);
parent.children.put(key, this);
this.key = key;
}
/**
* Replace the name of the specified section with a new one<br>
* <b>This will also move the section down to the bottom of whatever path branch it's on</b><br>
* Creates a new section with the specified name along the path if the section does not exist
* @param path to section rename
* @param key to rename the section to
*/
private void rename(List<String> path, String key) {
if (path.size() < 2) {
if (!children.containsKey(path.get(0))) addChild(tab+1, key, "", new ArrayList<>());
else children.get(path.get(0)).setKey(key);
} else {
Section child = children.get(path.get(0));
path.remove(0);
if (child != null) child.rename(path, key);
else addChild(tab+1, key, "", new ArrayList<>()).rename(path, key);
}
}
/**
* Remove a section and all inner sections
* @param path to the section to remove
*/
private void remove(List<String> path) {
if (path.size() < 2) children.remove(path.get(0));
Section child = children.get(path.get(0));
if (child == null) return;
path.remove(0);
child.remove(path);
}
/**
* Set the comments at the specified path
* @param path to set the comments for
* @param value to set as the comments
*/
private void setComments(List<String> path, List<String> value) {
Section sec = hardGet(path, "");
sec.header = value;
}
/**
* Convert an object into a string value, and set that value at the desired path<br>
* Converts arrays into a List-like string value. Otherwise uses #toString() on the object
* @param path to set the value at, [path, to.value]
* @param value to set
*/
private void setValue(List<String> path, Object value) {
StringBuilder builder = new StringBuilder(value.toString());
if (value instanceof Object[]) {
builder.setLength(0);
builder.append('[');
Object[] o = (Object[]) value;
for (int i = 0; i < o.length; i++)
builder.append(o[i] + (i == o.length - 1 ? "]" : ", "));
}
hardGet(path, value).value = builder.toString();
}
/**
* Get the section at the specified path, or create one if it does not exist
* @param path to get the section from or create a section at
* @param value to set as the path's value if the path does not exist
* @return section at the specified path
*/
private Section hardGet(List<String> path, Object value) {
String key = path.get(0);
if (path.size() < 2) {
if (children.containsKey(key)) return children.get(key);
else return addChild(tab+1, key, value.toString(), new ArrayList<>());
} else {
path.remove(0);
if (children.containsKey(key)) return children.get(key).hardGet(path, value);
else return addChild(tab+1, key, value.toString(), new ArrayList<>()).hardGet(path, value);
}
}
/**
* Get the section at the specified path
* @param string list path to the section
* @return Section at the path, null if section does not exist
*/
private Section get(List<String> path) {
if (path.size() < 2) return children.get(path.get(0));
Section child = children.get(path.get(0));
if (child == null) return null;
path.remove(0);
return child.get(path);
}
/**
* Get the sections' path/value and comments, as well as all of the paths/values and comments of nested sections
* @param contents list to append the contents to
* @param indent for the section's line
* @return List of the section comments, the path/value as well as all nested sections
*/
private List<String> getContents(List<String> contents, int indent) {
for (String head : header) contents.add(head);
if (tab > -1) contents.add(new String(new char[indent++]).replace("\0", TAB) + key + VALUE_SEPARATOR + " " + value);
for (Section sec : children.values()) sec.getContents(contents, indent);
return contents;
}
}
/**
* The whitespace to be used to represent a tab when generating the file from memory
*/
public static final String TAB = " ";
public static final char PATH_CHAR = '.', VALUE_SEPARATOR = ':', COMMENT_INDICATOR = '#';
/**
* Replace the name of the specified section with a new one
* <b>This will also move the section down to the bottom of whatever path branch it's on</b>
* Creates a new section with the specified name along the path if the section does not exist
* @param path to section rename
* @param name to rename the section to.
*/
public void renameSection(String path, String name) {
section.rename(splitPath(path), name);
}
/**
* If the section at the specified path exists, remove it and any existing subsections
* @param path to section to remove
*/
public void removeSection(String path) {
section.remove(splitPath(path));
}
/**
* Returns true if the path specified is one that exists
* @param path to check for existence
* @return true if the path exists, otherwise false
*/
public boolean pathExists(String path) {
return section.get(splitPath(path)) != null;
}
/**
* List all the available sections nested after the given path
* @param path to get the nested sections from
* @return Set of all the section names after the given path, null if the path does not exist
*/
public Set<String> listSections(String path) {
if (path.isEmpty()) return section.children.keySet();
Section sec = section.get(splitPath(path));
if (sec == null) return null;
return sec.children.keySet();
}
/**
* Set the comments of the section at the specified path
* @param path to the section to set the comments of
* @param comment strings to set as the comments for the section at the specified path
*/
public void setComments(String path, String... comments) {
List<String> commented = new ArrayList<>();
for (String cmt : comments)
if (isCommented(cmt)) commented.add(cmt);
else commented.add(COMMENT_INDICATOR + cmt);
section.setComments(splitPath(path), commented);
}
/**
* Get all the comments of the section at the specified path
* @param path to the section to get the comments of
* @return String list of comments for the section at the specified path
*/
public List<String> getComments(String path) {
Section sec = section.get(splitPath(path));
if (sec == null) return null;
return sec.header;
}
/**
* Set the value at the specified path
* @param path to set the value of
* @param value to set
*/
public void set(String path, Object value) {
section.setValue(splitPath(path), value == null ? "null" : value);
}
/**
* Get the string value at a path location, and create the path if it does not already exist.
* @param path to look for the value of or create if a value there does not already exist.
* @return the value associated with the path after the hard get.
*/
public String hardGetString(String path) {
return hardGetString(path, "");
}
/**
* Get the string value at a path location, and create the path with a specific value if it does not already exist.
* @param path to look for the value of or create if a value there does not already exist.
* @param value to apply to the path if the path did not exist.
* @return the value associated with the path after the hard get.
*/
public String hardGetString(String path, String value) {
return section.hardGet(splitPath(path), value).value;
}
/**
* Get the string value at the specified path
* @param path to get the string value at
* @return String value located at that path, or null if the path does not exist
*/
public String getString(String path) {
Section sec = section.get(splitPath(path));
if (sec == null) return null;
return sec.value;
}
/**
* Get the Boolean value at the specified path
* @param path to get the boolean value at
* @return True if the value at that path equalsIgnoreCase("true"), null if the path does not exist
*/
public Boolean getBoolean(String path) {
String value = getString(path);
if (value == null) return null;
return Boolean.parseBoolean(value.trim());
}
/**
* Get the Integer value at the specified path
* @param path to get the Integer value at
* @return Integer value located at that path, or null if the path does not exist
* @exception NumberFormatException if the value is not an integer
*/
public Integer getInt(String path) {
String value = getString(path);
if (value == null) return null;
return Integer.parseInt(value.trim());
}
/**
* Get the Byte value at the specified path
* @param path to get the Byte value at
* @return Byte value located at that path, or null if the path does not exist
* @exception NumberFormatException if the value is not a byte
*/
public Byte getByte(String path) {
String value = getString(path);
if (value == null) return null;
return Byte.parseByte(value.trim());
}
/**
* Get the Long value at the specified path
* @param path to get the Long value at
* @return Long value located at that path, or null if the path does not exist
* @exception NumberFormatException if the value is not a long
*/
public Long getLong(String path) {
String value = getString(path);
if (value == null) return null;
return Long.parseLong(value.trim());
}
/**
* Get the Double value at the specified path
* @param path to get the Double value at
* @return Double value located at that path, or null if the path does not exist
* @exception NumberFormatException if the value is not a double
*/
public Double getDouble(String path) {
String value = getString(path);
if (value == null) return null;
return Double.parseDouble(value.trim());
}
/**
* Get the Float value at the specified path
* @param path to get the Float value at
* @return Float value located at that path, or null if the path does not exist
* @exception NumberFormatException if the value is not a float
*/
public Float getFloat(String path) {
String value = getString(path);
if (value == null) return null;
return Float.parseFloat(value.trim());
}
/**
* Get a list of strings at the specified path<br>
* If the format [s1, s2, s3] is followed, it will be used to parse, if not, the list elements will be parsed using whitespace
* @param path to the string list
* @return list of strings parsed at the specified path, or null if the path does not exist
*/
public List<String> getStringList(String path) {
String value = getString(path);
if (value == null) return null;
if (value.startsWith("[") && value.endsWith("]")) return Arrays.asList(value.substring(0, value.length()-1).split(",\\s?"));
return Arrays.asList(value.split("\\s+"));
}
/**
* Get the contents of the entire file
* @return List of every line, in order
*/
public List<String> getContents() {
List<String> contents = section.getContents(new LinkedList<>(), 0);
if (footer != null) contents.addAll(footer);
return contents;
}
/**
* Get defaults from an InputStream and copy it into the file if the file is empty, doesn't exist, or if overwrite is true
* @param InputStream to copy from
* @param overwrite contents if they exist within the file
*/
public MYMLFile copyDefaults(InputStream is, boolean overwrite) {
if (!overwrite && file.exists() && !isEmpty(file)) return this;
if (is == null) System.out.println("[Warning] " + file.getName() + "'s .jar file has been modified! Please restart!");
else
try {
Files.copy(is, file.getAbsoluteFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
e.printStackTrace();
}
reload();
return this;
}
/**
* @param string to read for comments
* @return true if the line is whitespace or commented out with a pound sign
*/
private static boolean isCommented(String string) {
return string.matches("\\s*?(" + COMMENT_INDICATOR + ".*)*");
}
/**
* Split a path string using the path char, into a string list
* @param string path to split
* @return String list of path member keys
*/
public static List<String> splitPath(String string) {
int off = 0, next = 0;
ArrayList<String> list = new ArrayList<>();
while ((next = string.indexOf(PATH_CHAR, off)) != -1) {
list.add(string.substring(off, next));
off = next + 1;
}
if (off == 0)
return Arrays.asList(string);
list.add(string.substring(off, string.length()));
int resultSize = list.size();
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
return list.subList(0, resultSize);
}
/**
* @return true if the file is empty of characters, otherwise false
* @throws FileNotFoundException If the file is not found
*/
public static boolean isEmpty(File file) {
Scanner input;
try {
input = new Scanner(file);
if (input.hasNextLine()) {
input.close();
return false;
}
input.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return true;
}
/**
* @param folderLoc The path from the main program folder to the destination folder
* @return Set of all files contained in the destination folder
*/
public static Set<MYMLFile> getFolderContents(String... path) {
Set<MYMLFile> files = new HashSet<>();
StringBuilder pathBuilder = new StringBuilder();
for (int i = 0; i < path.length; i++)
pathBuilder.append(path[i] + File.separator);
File direc = new File(pathBuilder.toString());
if (!direc.exists()) direc.mkdirs();
if (direc.isDirectory())
for (File f : direc.listFiles())
files.add(new MYMLFile(f));
return files;
}
}
@crock
Copy link

crock commented Apr 24, 2017

Looks great, I'll work on a PHP port when I find some spare time! Together we can make MYML a global phenomenon!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment