package nbtool.data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Vector;
/*
* The general contract is that read operations such as get and find will succeed, but the result may not exist.
*
* Modifications to SExprs that are not present in the expression throws DoesNotExist, which is an
* uncaught exception.
*
* value* operations will also throw DNE.
* */
public abstract class SExpr implements Serializable{
//singleton return when operation could not find valid return.
private static final SExpr NOT_FOUND = new NotFound();
/**
* FACTORY METHODS */
public static SExpr deserializeFrom(String serializedSExpr) {
return _deserialize(serializedSExpr, new MutRef());
}
/* in java, fac-methods of form newAtom and atom are equivalent – all objects in java are passed by pointer. */
public static SExpr newAtom(String val) {
return new Found(val);
}
public static SExpr newAtom(Object v) {
return SExpr.newAtom(v.toString());
}
public static SExpr newList() {
return new Found(Arrays.asList(new SExpr[]{}));
}
public static SExpr newList(SExpr ... contents) {
return new Found(Arrays.asList(contents));
}
public static SExpr newKeyValue(String key, String value) {
return newList(new Found(key), new Found(value));
}
public static SExpr newKeyValue(String key, SExpr value) {
return newList(new Found(key), value);
}
public static SExpr newKeyValue(String key, int value) {
return newList(new Found(key), new Found(Integer.toString(value)));
}
public static SExpr newKeyValue(String key, float value) {
return newList(new Found(key), new Found(Float.toString(value)));
}
public static SExpr newKeyValue(String key, double value) {
return newList(new Found(key), new Found(Double.toString(value)));
}
public static SExpr atom(String val) {
return SExpr.newAtom(val);
}
public static SExpr atom(Object v) {
return SExpr.newAtom(v.toString());
}
public static SExpr list() {
return new Found(Arrays.asList(new SExpr[]{}));
}
public static SExpr stringify(Object ... args) {
SExpr list = list();
for (Object o : args) {
list.append(SExpr.atom(o.toString()));
}
return list;
}
public static SExpr list(SExpr ... contents) {
return new Found(Arrays.asList(contents));
}
public static SExpr pair(String key, String value) {
return newList(new Found(key), new Found(value));
}
public static SExpr pair(String key, SExpr value) {
return newList(new Found(key), value);
}
public static SExpr pair(String key, int value) {
return newList(new Found(key), new Found(Integer.toString(value)));
}
public static SExpr pair(String key, double value) {
return newList(new Found(key), new Found(Double.toString(value)));
}
/* checks for recursive trees, */
public static SExpr deepCopy(SExpr node) {
return node.deepCopy();
}
/* end factory methods */
/* instance methods */
/* modifying TYPE OF SEXPR (type of 'this' after function is as specified by method)*/
public abstract void setList(List<SExpr> list);
public abstract void setList(SExpr ... items);
public abstract void setAtom(String val);
//list modifications
public abstract void insert(int index, SExpr item);
public abstract boolean replace(SExpr item, SExpr repl);
public abstract boolean remove(SExpr item);
public abstract void append(SExpr ... exprs);
//list retrieval
public abstract int count();
public abstract SExpr get(int i);
public abstract List<SExpr> getList();
//Only looks at child elements of this, returns a child list
//whose first node is an atom w/ value 'key'
public abstract SExpr find(String key);
public abstract SExpr firstValueOf(String key);
/* Empty vector indicates not found. Otherwise, return.firstElement() == this
* and return.lastElement() == <a list with *ANY* node being an atom of value 'key'>
* and intermediate elements representing the path.
*
* May throw IllegalStateException if called on cyclical graph.
* Uses BFS to return first shallowest valid SExpr.
*/
public abstract Vector<SExpr> recursiveFind(String key);
public abstract Vector<SExpr>[] recursiveFindAll(String key);
//atom value retrieval
public abstract String value();
public abstract int valueAsInt() throws NumberFormatException;
public abstract long valueAsLong() throws NumberFormatException;
public abstract double valueAsDouble() throws NumberFormatException;
public abstract float valueAsFloat() throws NumberFormatException;
public abstract boolean valueAsBoolean();
//type retrieval
public abstract boolean isAtom();
public abstract boolean isList();
public abstract boolean exists();
public abstract SEXPR_TYPE type();
//conversion to strings
public abstract String print();
public abstract String print(int level); //level refers to whitespace offset
public abstract String serialize();
public abstract SExpr deepCopy();
@Override
public abstract String toString();
@Override
//DOES NOT CHECK FOR CYCLICAL TREE
public boolean equals(Object another) {
if (another == this)
return true;
if (! (another instanceof SExpr) )
return false;
SExpr osexpr = (SExpr) another;
if (osexpr.type() != this.type())
return false;
if (this.isAtom()) {
return this.value().equals(osexpr.value());
}
if (this.isList()) {
if (osexpr.count() != this.count())
return false;
for (int i = 0; i < this.count(); ++i) {
if (!osexpr.get(i).equals(this.get(i)))
return false;
}
return true;
}
assert(!this.exists());
return false; //Somewhat arbitrary, but this says that two distinct (!=) 'Not Found' SExprs cannot be equal.
}
/**
* end functionality listing
* -----------------------------
* start implementation:
*/
private static class Found extends SExpr {
protected Found(List<SExpr> l) {
atom = false;
list = new ArrayList<SExpr>(l);
}
protected Found(String v) {
atom = true;
value = v;
}
private boolean atom;
private String value;
private ArrayList<SExpr> list;
@Override
public boolean isAtom() {
return atom;
}
@Override
public boolean isList() {
return !atom;
}
@Override
public boolean exists() {
return true;
}
@Override
public int count() {
return atom ? -1 : list.size();
}
@Override
public SExpr get(int i) {
if (!atom && i < list.size())
return list.get(i);
else return NOT_FOUND;
}
@Override
public void append(SExpr... exprs) {
if (!atom) {
for (SExpr sexpr : exprs) {
if (!sexpr.exists())
throw new DoesNotExistException();
list.add(sexpr);
}
}
}
@Override
public String value() {
return atom ? value : null;
}
@Override
public int valueAsInt() throws NumberFormatException {
return Integer.parseInt(value);
}
@Override
public long valueAsLong() throws NumberFormatException {
return Long.parseLong(value);
}
@Override
public double valueAsDouble() throws NumberFormatException {
return Double.parseDouble(value);
}
@Override
public float valueAsFloat() throws NumberFormatException {
return Float.parseFloat(value);
}
@Override
public boolean valueAsBoolean() {
if (value.trim().equalsIgnoreCase("true"))
return true;
if (value.trim().equalsIgnoreCase("false"))
return false;
if (value.trim().equalsIgnoreCase("1"))
return true;
if (value.trim().equalsIgnoreCase("0"))
return false;
throw new NumberFormatException("cannot parse value as boolean");
}
private final int indent = 2;
private final int linelimit = 64;
@Override
public String print() {
return print(0);
}
@Override
public String print(int level) {
String prefix = new String(new char[level * indent]).replace("\0", " ");
String s = serialize();
if (atom || s.length() + (indent * level) <= linelimit)
return prefix + s;
s = prefix + "(";
for (int i = 0; i < count(); ++i) {
String e = list.get(i).print(level + 1);
if (i > 0)
{
s += '\n';
s += e;
}
else
s += e.substring(indent * level + 1);
}
s += "\n";
s += prefix;
s += ")";
return s;
}
@Override
public String serialize() {
if (atom) {
if (indexOfFirst(SPECIAL, value) < 0)
return value;
return "\"" + value.replaceAll("\"", "\"\"") + "\"";
}
String s = "(";
for (SExpr sex : list) {
if (s.length() > 1)
s += ' ';
s += sex.serialize();
}
s += ')';
return s;
}
@Override
public SExpr find(String key) {
if (atom)
return NOT_FOUND;
for (SExpr s : list) {
if ( s.count() > 0 &&
s.get(0).isAtom() &&
s.get(0).value().equals(key)
)
return s;
}
return NOT_FOUND;
}
@Override
public SExpr firstValueOf(String key) {
return this.find(key).get(1);
}
@Override
public void setList(SExpr... items) {
atom = false;
value = null;
list = new ArrayList<>(Arrays.asList(items));
}
@Override
public void setList(List<SExpr> list) {
atom = false;
value = null;
list = new ArrayList<>(list);
}
@Override
public void setAtom(String val) {
atom = true;
list = null;
value = val;
}
@Override
public void insert(int index, SExpr item) {
if (!atom && item.exists()) {
list.add(index, item);
}
}
@Override
public boolean remove(SExpr item) {
if (!atom && list.contains(item)) {
list.remove(item);
return true;
}
return false;
}
@Override
public boolean replace(SExpr item, SExpr repl) {
if (!atom && list.contains(item)) {
int index = list.indexOf(item);
list.remove(item);
list.add(index, repl);
return true;
}
return false;
}
@Override
public List<SExpr> getList() {
return atom ? null : list;
}
private Vector<SExpr> traverse(Map<SExpr, SExpr> parents, SExpr start) {
LinkedList<SExpr> ret = new LinkedList<SExpr>();
SExpr val = start;
while (val != null) {
ret.addFirst(val);
val = parents.get(val);
}
assert(ret.getFirst() == this);
return new Vector<SExpr>(ret);
}
private ArrayList<Vector<SExpr>> internalRecursiveFind(String key, int num) {
Map<SExpr, SExpr> parents = new HashMap<SExpr, SExpr>();
LinkedList<SExpr> queue = new LinkedList<SExpr>();
ArrayList<Vector<SExpr>> matches = new ArrayList<>();
assert(!atom);
assert(num > 0);
queue.add(this);
parents.put(this, null);
while (!queue.isEmpty()) {
SExpr current = queue.removeFirst();
assert(!current.isAtom());
for (SExpr child : current.getList()) {
if (child.isAtom()) {
if (child.value().equals(key)) {
matches.add(traverse(parents, current));
if (matches.size() == num) {
return matches;
} else {
continue;
}
}
} else {
//list
if (parents.containsKey(child)) {
throw new IllegalStateException("Cyclical tree.");
}
parents.put(child, current);
queue.addLast(child);
}
}
}
return matches;
}
@Override
public Vector<SExpr> recursiveFind(String key) {
if (atom || list.isEmpty())
return new Vector<SExpr>();
ArrayList<Vector<SExpr>> matches = internalRecursiveFind(key, 1);
assert(matches.size() <= 1);
if (matches.isEmpty()) {
return new Vector<SExpr>();
} else {
return matches.get(0);
}
}
@SuppressWarnings("unchecked")
@Override
public Vector<SExpr>[] recursiveFindAll(String key) {
if (atom || list.isEmpty())
return new Vector[0];
ArrayList<Vector<SExpr>> matches = internalRecursiveFind(key, Integer.MAX_VALUE);
return matches.toArray(new Vector[matches.size()]);
}
@Override
public SExpr deepCopy() {
return _deepCopy(this, new HashSet<SExpr>());
}
@Override
public String toString() {
return atom ? String.format("SExpr.atom(\"%s\")", value) : String.format("SExpr.list(%d)", list.size());
}
@Override
public SEXPR_TYPE type() {
return atom ? SEXPR_TYPE.ATOM : SEXPR_TYPE.LIST;
}
}
private static class NotFound extends SExpr {
@Override
public boolean isAtom() {
return false;
}
@Override
public boolean isList() {
return false;
}
@Override
public boolean exists() {
return false;
}
@Override
public int count() {
return -1;
}
@Override
public SExpr get(int i) {
return this;
}
@Override
public void append(SExpr... exprs) {
throw new DoesNotExistException();
}
@Override
public String value() {
throw new DoesNotExistException();
}
@Override
public int valueAsInt() throws NumberFormatException {
throw new DoesNotExistException();
}
@Override
public long valueAsLong() throws NumberFormatException {
throw new DoesNotExistException();
}
@Override
public double valueAsDouble() throws NumberFormatException {
throw new DoesNotExistException();
}
@Override
public float valueAsFloat() throws NumberFormatException {
throw new DoesNotExistException();
}
@Override
public boolean valueAsBoolean() {
throw new DoesNotExistException();
}
@Override
public String print() {
throw new DoesNotExistException();
}
@Override
public String print(int level) {
throw new DoesNotExistException();
}
@Override
public String serialize() {
throw new DoesNotExistException();
}
@Override
public SExpr find(String key) {
return this;
}
@Override
public void setList(SExpr... items) {}
@Override
public void setAtom(String val) {}
@Override
public void insert(int index, SExpr item) {}
@Override
public boolean remove(SExpr item) {
return false;
}
@Override
public boolean replace(SExpr i, SExpr j) {
return false;
}
@Override
public List<SExpr> getList() {
return null;
}
@Override
public void setList(List<SExpr> list) {}
@Override
public Vector<SExpr> recursiveFind(String key) {
return new Vector<SExpr>(); //key not found.
}
@Override
public SExpr deepCopy() {
return NOT_FOUND;
}
@Override
public String toString() {
return "SExpr.NOTFOUND";
}
@Override
public SExpr firstValueOf(String key) {
return NOT_FOUND;
}
@SuppressWarnings("unchecked")
@Override
public Vector<SExpr>[] recursiveFindAll(String key) {
return new Vector[0];
}
@Override
public SEXPR_TYPE type() {
return SEXPR_TYPE.NOTFOUND;
}
}
//Doesn't require a throws.. or try/catch like non-Runtime exceptions.
public static class DoesNotExistException extends RuntimeException {}
private static class MutRef {
int val;
MutRef() {
val = 0;
}
}
public static final char[] SPECIAL = {' ', '(', ')', '\r', '\n', '\t'};
private static int indexOfFirst(char[] opts, String target) {
return indexOfFirstFrom(opts, target, 0);
}
private static int indexOfFirstFrom(char[] opts, String target, int from) {
String sopts = new String(opts);
for (int i = from; i < target.length(); ++i) {
if (sopts.contains(target.substring(i, i + 1)))
return i;
}
return -1;
}
private static SExpr _deserialize(String s, MutRef p) {
// Skip whitespace
while (p.val < s.length() && Character.isWhitespace(s.charAt(p.val)))
++p.val;
if (p.val >= s.length())
return NOT_FOUND;
int q;
SExpr se;
switch (s.charAt(p.val))
{
case '"':
// Atoms starting with "
String name = "";
while (p.val < s.length() && s.charAt(p.val) == '"')
{
if (name.length() > 0)
name += '"';
q = s.indexOf('"', ++p.val);
if (q < 0)
q = s.length();
name += s.substring(p.val, q);
if (q < s.length())
++q;
p.val = q;
}
return SExpr.newAtom(name);
case '(':
// Lists
++p.val;
se = SExpr.newList();
while (true)
{
SExpr e = _deserialize(s, p);
if (e == null)
break;
se.append(e);
}
if (p.val < s.length() && s.charAt(p.val) == ')')
++p.val;
return se;
case ')':
// List termination
return null;
default:
// Atoms not starting with ""
q = indexOfFirstFrom(SPECIAL, s, p.val);
if (q < 0)
q = s.length();
se = SExpr.newAtom(s.substring(p.val, q));
p.val = q;
return se;
}
}
private static SExpr _deepCopy(SExpr node, HashSet<SExpr> seen) {
if (seen.contains(node)) {
throw new IllegalStateException("Cyclical tree.");
}
if (node.isAtom()) {
return atom(node.value());
}
//node is list, construct copy.
seen.add(node);
SExpr copiedList = list();
for (SExpr child : node.getList()) {
copiedList.append(_deepCopy(child, seen));
}
return copiedList;
}
public static enum SEXPR_TYPE {
ATOM,
LIST,
NOTFOUND
}
public static void main(String[] args) {
/*
String ser = "(the list goes (on and on and \"what?()\" ()))";
SExpr s = SExpr.deserializeFrom(ser);
System.out.println("done...");
System.out.printf("%s\n",s.print()); */
SExpr top = list(
list(
atom("rqwe"),
list()
),
list(
pair("rawr", "sock"),
pair("wa", "r"),
atom("RAWR")
)
);
System.out.println("" + top.recursiveFind("rawr"));
for (SExpr s : top.recursiveFind("rawr")) {
System.out.println("------------------------------------------\n"
+ s.print());
}
top.append(top);
System.out.println("" + top.recursiveFind("rawr"));
}
}