/*
* Copyright (C) 2011 Alasdair C. Hamilton
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package ket.math;
import java.awt.Color; // TODO: Remove toBox dependence on awt?
import java.util.*;
import ket.display.*;
import ket.display.box.Box;
import ket.display.box.BoxTools;
import ket.math.convert.Like;
import ket.math.purpose.IntegerValue;
import ket.math.purpose.NumberValue;
import ket.math.purpose.SymbolicState;
import ket.math.purpose.VariableState;
import ket.math.purpose.VariableToken;
import ket.math.purpose.Word;
/*
* A branch represents any collection of arguments that undergo a mapping. It
* also serves as a container class to store other child arguments.
*/
public class Branch extends Argument implements Parent {
/**
* The mapping is described by an instance of Function while null
* functions imply an unknown type of mapping.
*/
Function function;
/**
* A list of arguments that undergo the mapping.
*/
Vector<Argument> args;
//////////////////
// CONSTRUCTORS //
//////////////////
public Branch() {
super();
function = null;
args = new Vector<Argument>();
}
public Branch(Function function) {
super();
this.function = function;
args = new Vector<Argument>();
}
public Branch(Function function, Argument[] args) {
super();
this.function = function;
this.args = new Vector<Argument>();
for (Argument child : args) {
append(child);
}
}
public Branch(Function function, Vector<Argument> args) {
super();
this.function = function;
this.args = new Vector<Argument>();
for (Argument child : args) {
append(child);
}
}
public Branch(Function function, Argument first) {
this(function);
append(first);
}
public Branch(Function function, Argument first, Argument last) {
this(function);
append(first);
append(last);
}
///////////////////
// OTHER METHODS //
///////////////////
///////////////////////
// LOW LEVEL METHODS //
///////////////////////
@Override
public int size() {
return args.size();
}
@Override
public int recursiveSize() {
int total = 0;
for (Argument a : args) {
total += a.recursiveSize();
}
return total;
}
@Override
public String getValue() { // Used?
return null;
}
@Override
public Function getFunction() {
return function;
}
@Override
public State getState() {
return null;
}
public void setFunction(Function function) {
this.function = function;
}
public Argument lastChild() {
return size()>0 ? args.lastElement() : null;
}
public Argument firstChild() {
return size()>0 ? args.firstElement() : null;
}
/**
* Return the index (counting from zero) of a given child, or -1 if it
* is not found.
*/
public int getIndex(Argument argument) { // TODO: Rename to getChildIndex() to avoid ambiguity with super.super.getIndex().
for (int i=0; i<args.size(); i++) {
if (argument==args.get(i)) {
return i;
}
}
return -1;
}
public void reverseChildren() {
Collections.reverse(args);
}
public Branch cloneBranch() {
Branch branch = new Branch(this.getFunction());
for (int i=0; i<args.size(); i++) {
Argument argument = Argument.cloneArgument(args.get(i));
branch.append(argument);
}
if (isBold()) {
branch.setBold(true);
}
return branch;
}
public Argument getChild(int index) {
if (size()==0) {
return null;
} else if (index<0) {
return args.get((size()-index)%size());
} else {
return size()>0 ? args.get(index%size()) : null;
}
}
public Branch getChildBranch(int index) {
Argument child = getChild(index);
return child instanceof Branch ? (Branch) child : null;
}
/**
* Return a shallow copy of the functions arguments.
*/
public Vector<Argument> getChildren() {
return new Vector<Argument>(args);
}
/////////////////
// RE-ORGANIZE //
/////////////////
public void cyclicallyPermuteLeft() {
if (args.size()<=1) {
return;
}
Argument argument = args.remove(0);
args.add(argument);
}
public void cyclicallyPermuteRight() {
if (args.size()<=1) {
return;
}
Argument argument = args.remove(args.size()-1);
args.add(0, argument);
}
public void invert() {
if (function==null) {
return;
}
//- Function inverse = function.getInverse();
Function inverse = Type.getInverse(function);
if (inverse!=null) {
setFunction(inverse);
}
}
/**
* Remove this branch from its parent replacing it with with its
* children in the same order.
*/
@Override
public void removeIntermediate() {
if (isRoot()) {
getEquation().setRoot(firstChild());
} else {
Branch parentBranch = getParentBranch();
Collections.reverse(args);
for (Argument child : getChildren()) { // Shallow copy.
this.removeChild(child.getIndex());
int index = this.getIndex();
parentBranch.addAfter(index, child);
}
this.remove();
}
}
/////////////////
// ADD METHODS //
/////////////////
/**
* Append an argument after a given index in the argument list.
*/
public void addAfter(int index, Argument argument) {
argument.remove();
args.add(index+1, argument);
argument.setParent(this);
}
public void prepend(Argument argument) {
argument.remove();
args.add(0, argument);
argument.setParent(this);
}
public void append(Argument argument) {
argument.remove();
args.add(argument);
argument.setParent(this);
}
public void appendAll(Vector<Argument> args) {
for (Argument a : args) {
append(a);
}
}
public void prependAll(Vector<Argument> args) {
Vector<Argument> r = new Vector<Argument>(args);
Collections.reverse(r);
for (Argument a : r) {
prepend(a);
}
}
////////////////////
// REMOVE METHODS //
////////////////////
public void removeChild(int index) {
Argument argument = args.remove(index);
argument.setParent(null);
}
public void empty() {
while (args.size()>0) {
removeChild(0);
}
}
////////////////////
// SWAP ARGUMENTS //
////////////////////
public boolean swapArgumentLeft(Argument argument) {
Argument previous = argument.getPreviousSibling();
if (previous==null) {
previous = args.lastElement();
}
return swapArguments(argument, previous);
}
public boolean swapArgumentRight(Argument argument) {
Argument next = argument.getNextSibling();
if (next==null) {
next = args.firstElement();
}
return swapArguments(argument, next);
}
///////////////////
// NUMBER ACCESS //
///////////////////
/**
* Extract double values of all arguments and return them in a double
* array if possible and null if any arguments are not instances of
* number value.
*/
public double[] argsToDoubleArray() {
if (size()==0) {
return null;
}
for (Argument argument : args) {
State state = argument.getState();
if ( ! (state instanceof NumberValue)) {
return null;
}
}
double[] copy = new double[size()];
int i=0;
for (Argument argument : args) {
State state = argument.getState();
NumberValue numberValue = (NumberValue) state;
copy[i++] = numberValue.getDouble();
}
return copy;
}
/**
* Extract integer values from all arguments into an array and return
* them if possible and null if any arguments are not instances of
* integer value.
*/
public int[] argsToIntArray() {
if (size()==0) {
return null;
}
for (Argument argument : args) {
State state = argument.getState();
if ( ! (state instanceof IntegerValue)) {
return null;
}
}
int[] copy = new int[size()];
int i=0;
for (Argument argument : args) {
State state = argument.getState();
IntegerValue integerValue = (IntegerValue) state;
copy[i++] = integerValue.getInt();
}
return copy;
}
////////////////////////////
// OUTPUT FORMATS //
////////////////////////////
/**
* Determine whether the current branch needs to be displayed with
* brackets around it for clarity.
*/
public boolean hasBracket(boolean strict) {
Function parentFunction = getParentFunction();
if (areBracketsExcluded(parentFunction, strict)) {
return false;
}
if (areBracketsRequired(parentFunction)) {
return true;
}
return function.getPrecedence() < parentFunction.getPrecedence();
}
private boolean areBracketsExcluded(Function parentFunction, boolean strict) {
boolean excludesBrackets = this.isRoot();
excludesBrackets |= hasPrefixAndPostfix();
excludesBrackets |= parentFunction==null || parentFunction.displayBrackets();
return excludesBrackets;
}
private boolean areBracketsRequired(Function parentFunction) {
boolean requiresBrackets = function==null;
requiresBrackets |= Like.nestedFractionShape(parentFunction, function);
requiresBrackets |= Like.nestedMinusLike(parentFunction, function);
return requiresBrackets;
}
/**
* Eg |x> or |x| like operations are self bracketing.
*/
private boolean hasPrefixAndPostfix() {
if ( ! (function instanceof SymbolicFunction) ) {
return false;
}
SymbolicFunction symbolicFunction = (SymbolicFunction) function;
return symbolicFunction.isPrefix() && symbolicFunction.isPostfix();
}
@Override
public String toHTML() {
String html;
if (getFunction()==null) {
html = Symbol.UNKNOWN.toHTML();
html += Symbol.COLON.toHTML();
html += Function.BRACKET.toHTML(args);
} else {
html = function.toHTML(args);
}
return hasBracket(true) ? "("+html+")" : html;
}
@Override
public Box toBox(long settings, ColourScheme colourScheme) {
if ( ! isVisible() ) {
return super.toBox(settings, colourScheme);
}
// Only include scaling information to the argument of
// a branch if scaling isn't already done by brackets.
Function nonNullFunction = getFunction()==null ? Function.UNKNOWN : function;
long innerSettings = isBold() ? Box.BOLD_FONT : 0L;
boolean noBracketFlag = (Box.SUPPRESS_BRACKETS&settings)==Box.SUPPRESS_BRACKETS;
//- Color colour = colourScheme.getBoxColour(this);
if (!noBracketFlag && hasBracket(false)) {
// Note: Only pass RELATIVE_FONT_FLAGS once (in bracket).
Box box = nonNullFunction.toBox(this, innerSettings, colourScheme, args);
box.setArgument(this);
Box bracketedBox = BoxTools.roundBrackets(this, box, settings, colourScheme);
bracketedBox.setProperty(Box.PRESERVE_ASPECT_RATIO|Box.VERTICAL_STRETCH);
bracketedBox.setArgument(this);
return bracketedBox;
} else {
Box box = nonNullFunction.toBox(this, settings|innerSettings, colourScheme, args);
box.setArgument(this); //? Redundant
return box;
}
}
public Function getNonNullFunction() {
return getFunction()!=null ? function : Function.UNKNOWN;
}
@Override
public String toPrefixNotation() {
if (function==Function.VECTOR) { // Special case (let [x,3] (println x)) rather than (let (vector x e) (println x)).
String notation = "[";
String gap = "";
for (Argument c : args) {
notation += gap + c.toPrefixNotation();
gap = " ";
}
return notation + "]";
}
String notation = "(";
if (function==Function.EQUALS ||
function==Function.ADD || function==Function.MINUS ||
function==Function.TIMES || function==Function.FRACTION ||
function==Function.LESS_EQUALS || function==Function.LESS_THAN ||
function==Function.GREATER_EQUALS || function==Function.GREATER_THAN) {
SymbolicState infix = ((SymbolicFunction) function).getInfix();
notation += infix.getName(); //i.e. +, -, * or /.
} else if (function==null) {
notation += Function.UNKNOWN.getName();
} else {
notation += function.getName();
}
for (Argument c : args) {
notation += " " + c.toPrefixNotation();
}
return notation + ")";
}
@Override
/**
* The same as toString() except that there are never outer brackets around this argument.
*/
public String toEditString() {
String text = getNonNullFunction().toString(args);
return TextTools.removeWhitespace(text);
}
@Override
public String toString() {
String edit = toEditString();
return hasBracket(true) ? "("+edit+")" : edit;
}
public String getName() {
return getFunction()==null ? "<null>" : function.getName();
}
@Override
public String toVerboseString() {
String name = this.getName();
String argsString = args.toString();
return "branch"+(function!=null?"'"+name+"'":"") + argsString;
}
@Override
public String toLatex() {
String string;
if (function==null) {
string = "\\invdiameter{null} \\! \\left(";
for (int i=0; i<args.size(); i++) {
if (i!=0) {
string += ", ";
}
string += args.get(i).toLatex();
}
string += "\\right)";
} else {
string = function.toLatex(args);
if (hasBracket(false)) {
string = "\\left(" + string + "\\right)"; // Backslash-bang is negative spacing!
}
}
return "{" + string + "}";
}
/////////////////////////////
// EXTRACTING SUB-BRANCHES //
/////////////////////////////
public void subBranchBefore(int index, Function function) {
subBranchBetween(-1, index, function);
}
/**
* Remove all arguments after index and add them to a new branch which
* is returned.
*/
public void subBranchAfter(int index, Function function) {
subBranchBetween(index, args.size(), function);
}
public void subBranchBetween(int from, int to, Function function) {
int indices[] = new int[]{from, to};
subBranchBetween(indices, function);
}
/**
* The in-between indices are moved to a new branch of given function
* while the indices themselves are removed. Divide argument list into
* a series of sub branches, each defined by
* indices[i]+1 ... indices [i+1]-1
*/
public void subBranchBetween(int indices[], Function function) {
Branch branch = new Branch(function);
for (int i=indices.length-1; i>0; i--) {
Branch child = new Branch();
int firstIndex = indices[i-1]+1;
for (int j=firstIndex; j<indices[i]; j++) {
Argument argument = args.get(firstIndex);
this.removeChild(firstIndex);
child.append(argument);
}
if (i!=1) {
this.removeChild(indices[i-1]);
}
if (child.size()==1) {
branch.prepend(child.firstChild());
} else if (child.size()>1) {
branch.prepend(child);
}
}
this.addAfter(indices[0], branch);
}
////////////////
// COMPARISON //
////////////////
/**
* Check if the state of a given argument is equal to value.
*/
@Override
public boolean elementEquals(double value) {
return false;
}
/**
* Check that this object equals the given argument, regardless of any
* child components.
*/
@Override
public boolean elementEquals(Argument argument) {
if (argument==null) {
return false;
}
return function==argument.getFunction();
}
/**
* Return true if each child elementEquals the corresponding other branch's argument.
*/
public boolean childElementsEqual(Branch branch) { //-
if (this.size()==branch.size()) {
boolean childrenEqual = true;
for (int i=0; i<this.size(); i++) {
if ( ! this.getChild(i).elementEquals(branch.getChild(i)) ) {
childrenEqual = false;
}
}
return childrenEqual;
} else {
return false;
}
}
/**
* This follows the same model of AbstractList of multiplying each
* child by 31 and treating nulls as zero. This approach is also nested: being applied to all child branches as well.
*/
@Override
public int hashCode() {
int functionHash = function==null ? 0 : function.hashCode();
// Note the args vector automatically performs a weighted sum of each argument and multiplies by 31.
return functionHash + 31*args.hashCode();
}
@Override
public boolean matches(String pattern) {
// Note: While there is a function.matches(pattern) method, note
// that function can be null and arguments should be handled
// here.
return getName().equals(pattern);
}
/*-
@Override
public boolean equals(Object o) {
if ( ! (o instanceof Argument) ) { // Should really just be branches.
return false;
}
return subBranchEquals( (Argument) o );
}
*/
/**
* Check if this branch and its children are of equal size, function
* type and children.
*/
@Override
public boolean subBranchEquals(Argument argument) {
if ( ! (argument instanceof Branch) ) {
return false;
}
Branch branch = (Branch) argument;
if (function!=branch.getFunction()) {
return false;
}
if (this.size()!=branch.size()) {
return false;
}
for (int i=0; i<args.size(); i++) {
if ( ! this.getChild(i).subBranchEquals(branch.getChild(i))) {
return false;
}
}
return true;
}
///////////////////
// TO BE REMOVED //
///////////////////
/**
* Any branch that has a null child with a single grandchild inside can
* be skipped.
*/
public void removeIntermdiateNullChildBranches() {
ArgumentVector vector = new ArgumentVector(this, ArgumentVector.BRANCHES_ONLY);
for (Branch child : vector.toBranchVector()) { //?
if (child.getFunction()==null && child.size()==1) {
child.replace(child.getChild(0));
}
}
}
/**
* When splitting lists of tokens, there may be empty end bits which
* correspond to assuming an argument that isn't there. These appear
* as empty, null branches this method eliminates.
*/
public void removeNullChildren() {
ArgumentVector vector = new ArgumentVector(this, ArgumentVector.BRANCHES_ONLY);
for (Branch child : vector.toBranchVector()) {
if (child.getFunction()==null && child.size()==0) {
// TODO: check that this is not called for
// functions that need two or more arguments
// and in which case add some kind of
// placeholder thingy.
child.remove();
}
}
}
/**
* Sort the arguments of a branch into their natural order.
*/
public boolean sort() {
int hash = hashCode();
Collections.sort((List<Argument>) args);
return hash!=hashCode();
}
/**
* If an argument is branch[-](argument) then return argument,
* otherwise return null.
*/
@Override
public Argument getNegativeFunctionArg() { // TODO: Rethink this approach.
if (getFunction()==Function.MINUS) {
if (size()==1) {
return firstChild();
}
}
return null;
}
public Branch moveFirstChildOut() {
//- Function function = this.getFunction();
//- Branch newBranch = this.addIntermediaryParent(function);
Branch newBranch = this.addIntermediaryParent(this.getFunction());
newBranch.prepend(this.firstChild());
return newBranch;
}
public Branch moveLastChildOut() {
Function function = this.getFunction();
//- Branch newBranch = new Branch(function);
//- this.addIntermediaryParent(newBranch, false);
Branch newBranch = this.addIntermediaryParent(function);
newBranch.append(this.lastChild());
return newBranch;
}
public boolean isLeaf() {
boolean folded = ! isVisible();
boolean childless = size()==0;
return folded || childless;
}
/////////////////////
// PROBLEM METHODS //
/////////////////////
public Argument getNextArgument(Argument argument) {
int index = getIndex(argument) + 1;
return (0<index && index<args.size()) ? args.get(index) : null;
}
public Argument getPreviousArgument(Argument argument) {
int index = getIndex(argument) - 1;
return (index >= 0) ? args.get(index) : null;
}
public boolean addBefore(Argument current, Argument newArgument) {
int indexOf = getIndex(current);
newArgument.remove();
args.add(indexOf, newArgument);
newArgument.setParent(this);
return true;
}
// TODO: This is ambiguous as it could conceptually be applied to any
// argument written as
// current.addAfter(newArgument).
// which enforces the found-indexOf check performed below.
public boolean addAfter(Argument current, Argument newArgument) {
int indexOf = getIndex(current) + 1;
newArgument.remove();
args.add(indexOf, newArgument);
newArgument.setParent(this);
return true;
}
public boolean swapArguments(Argument a, Argument b) { // Rename to swapChildren().
int i = getIndex(a);
int j = getIndex(b);
if (i==-1 || j==-1) {
return false;
}
Collections.swap(args, i, j);
return true;
}
@Override
public void replaceArgument(Argument original, Argument replacement) {
int index = getIndex(original);
original.setParent(null);
args.setElementAt(replacement, index);
replacement.setParent(this);
}
public VariableToken asVariableToken(KnownArguments knownArguments) {
Function function = this.getFunction();
VariableState state = knownArguments.functionToState(function);
if (state==null) {
state = new Word(function.getName());
}
return new VariableToken(state);
}
}