/*
* ParameterTree.java
* Copyright 2007 (C) Andrew Wilson <nuance@users.sourceforge.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Created on 10 March 2007
*
* $Id$
*
*/
package pcgen.util;
import org.nfunk.jep.ParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ParameterTree
{
String contents;
ParameterTree left = null;
ParameterTree right = null;
public static final String orString = "[or]";
public static final String andString = "[and]";
static String orPatString = "\\[or\\]";
static String andPatString = "\\[and\\]";
private static String leftBracket = "(";
private static String leftPatString = "\\(";
private static String rightPatString = "\\)";
static String patString = "(" + leftPatString + "|" + rightPatString+ "|" + orPatString + "|" + andPatString + ")";
static Pattern pat = Pattern.compile(patString);
// the grouping pattern & matcher
private static final String parenString = "(" + leftPatString + "|" + rightPatString + ")";
private static final Pattern parenPattern = Pattern.compile(parenString);
// the opertor pattern & matcher
private static final String operatorString = "(" + orPatString + "|" + andPatString + ")";
private static final Pattern operatorPattern = Pattern.compile(operatorString);
private static int getIndexOfClosingParen (
final String s,
final int start) throws ParseException
{
final Matcher aMat = parenPattern.matcher(s);
aMat.find(start);
int level = 1;
while (level > 0) {
if (!aMat.find()) {
throw new ParseException("unbalanced parenthesis in " + s);
}
if (leftBracket.equalsIgnoreCase(aMat.group())) {
level++;
} else {
level--;
}
}
return aMat.end();
}
public static ParameterTree makeTree (final String source) throws ParseException
{
final Matcher mat = ParameterTree.pat.matcher(source);
if (mat.find()) {
return makeTree(source, false);
} else {
return new ParameterTree(source);
}
}
private static ParameterTree makeTree (
final String source,
final boolean operatorNeeded) throws ParseException
{
final Matcher pM = parenPattern.matcher(source);
final boolean hasP = pM.find();
ParameterTree t;
if (hasP) {
final String pre = source.substring(0, pM.start());
final int end = getIndexOfClosingParen(source, pM.start());
if (pre.isEmpty()) {
final String inside = source.substring(pM.end(), end - 1);
t = makeTree(inside, false);
} else {
t = toTree(pre, operatorNeeded);
final Matcher rM = operatorPattern.matcher(t.getContents());
if (rM.find()) {
if (t.getRightTree() == null) {
// Since we found an operator in the root of the tree, but
// the right subtree is null, then the parens must contain
// a complete expression (or the whole thing is illegal)
// so make a tree from the contents and append it here
// remember to strip off the outer parens.
final String inside = source.substring(pM.end(), end - 1);
t.setRightTree(makeTree(inside, false));
} else {
// The root of the tree generated from the first section of the
// string has something in its right sub tree. That means the
// parenthesised expression is a part of that string (since
// if it ended with an operator that would be in a separate
// "root")
final StringBuilder rNodeContents = new StringBuilder();
rNodeContents.append(t.getRightTree().getContents());
rNodeContents.append(source.substring(pM.start(), end));
}
} else {
// root of the generated tree doesn't contain an operator, so
// the paren expression should be tacked onto it.
final String parenExp = source.substring(pM.end() - 1, end);
final StringBuilder rNodeContents = new StringBuilder();
rNodeContents.append(t.getContents()).append(parenExp);
t.setContents(rNodeContents.toString());
}
}
if (end < source.length()) {
final String sEnd = source.substring(end);
final ParameterTree r = makeTree(sEnd,true);
ParameterTree c = r;
final Matcher cM = operatorPattern.matcher(r.getContents());
if (!cM.find()) {
throw new ParseException("expected \"" + source.substring(end) + "\" to begin with an operator");
}
while (c.getLeftTree() != null) {
c = c.getLeftTree();
}
c.setLeftTree(t);
t = r;
}
} else {
t = toTree(source, operatorNeeded);
}
return t;
}
private static ParameterTree toTree(
final String source,
final boolean operatorNeeded) throws ParseException
{
String s = source;
// the opertor matcher
Matcher oM = operatorPattern.matcher(s);
ParameterTree cT = new ParameterTree(""); //current Tree
boolean hasO = oM.find();
// this is for operators, obviously
if (hasO && operatorNeeded) {
if (oM.start() != 0) {
throw new ParseException("expected \"" + s + "\" to begin with an operator");
} else {
cT = new ParameterTree(oM.group());
final int end = oM.end();
s = s.substring(end);
oM = operatorPattern.matcher(s);
hasO = oM.find();
}
}
int start = 0;
while (hasO) {
final String pre = s.substring(start, oM.start());
final ParameterTree P = new ParameterTree(pre); // pre Tree
final ParameterTree R = new ParameterTree(oM.group()); // root Tree - must be an operator (it matched)
// is the "root" of the current tree an operator
final Matcher cM = operatorPattern.matcher(cT.getContents());
if (cM.find()) {
cT.setRightTree(P);
R.setLeftTree(cT);
// right branch of R is null
} else {
R.setLeftTree(P);
// can discard current tree, it's empty (first iteration)
}
// root becomes new current tree
cT = R;
start = oM.end();
hasO = oM.find();
}
// no more operators, but string is not empty
if (start < s.length()) {
final ParameterTree p = new ParameterTree(s.substring(start));
final Matcher cM = operatorPattern.matcher(cT.getContents());
if (cM.find()) {
// current tree has operator in root
cT.setRightTree(p);
} else {
// current tree is the default empty tree created above
cT = p;
}
}
return cT;
}
/**
* @param data The value that will end up in the node of the tree
*/
ParameterTree(final String data) {
super();
this.contents = data;
}
public void setContents(final String data) {
this.contents = data;
}
/**
* @return the Contents
*/
public String getContents() {
return contents;
}
/**
* @return the left subtree
*/
public ParameterTree getLeftTree() {
return left;
}
/**
* @param l the ParameterTree to add as the left sub tree
*/
public void setLeftTree(final ParameterTree l) {
left = l;
}
/**
* @return the right subtree
*/
public ParameterTree getRightTree() {
return right;
}
/**
* @param r the ParameterTree to add as the right sub tree
*/
public void setRightTree(final ParameterTree r) {
right = r;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(200);
sb.append("[");
sb.append(contents);
sb.append(" ");
if (left != null) {
sb.append(left.toString());
}
if (right != null) {
sb.append(right.toString());
}
sb.append("]");
return sb.toString();
}
}