/*
* 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 ketUI.modes;
import java.util.*;
import ket.*;
import ket.math.*;
import ket.math.convert.ArgumentParser;
import ketUI.Clipboard;
import ketUI.Document;
public class Search {
public static final boolean FORWARDS = false;
public static final boolean BACKWARDS = true;
// TODO: Use dependency injection as its value changes dynamically.
final Clipboard clipboard;
final KnownArguments knownArguments;
final Modes modes;
LoopIterator<Argument> searchMatches;
String pattern;
boolean direction;
public Search(KnownArguments knownArguments, Clipboard clipboard, Modes modes) {
this.knownArguments = knownArguments;
this.clipboard = clipboard;
this.modes = modes;
this.searchMatches = null;
}
/**
* Search for a given argument/symbol/branch pattern within the current
* equationList, starting from the current argument.
*
* @param pattern A string representation of a pattern of an argument,
* or equation sub-branch, to denote the sought particular.
* @param direction Set this variable to <I>true</I> in order to search the list of equations backwards (upwards).
* @return If the search finds one or more matches then true is returned.
*/
public boolean search(String pattern, boolean direction) {
this.pattern = pattern;
this.direction = direction;
Vector<Argument> matches = new Vector<Argument>();
Integer index = null;
Address currentAddress = getCurrent().getAddress();
Argument parsedPattern = parsePattern(pattern);
Function charFunction = getFunctionByChar(pattern);
//- boolean before = true;
//- boolean after = false;
for (Equation equation : getSelection().getEquationList().getEquations()) {
Argument root = equation.getRoot();
Vector<Argument> argsVector = new ArgumentVector(root, ArgumentVector.INCLUDE_ROOT);
for (Argument argument : argsVector) {
/*- if (before && !currentAddress.follows(argument)) {
before = false;
}
if (!after && currentAddress.precedes(argument)) {
after = true;
} */
if ( ! isMatch(argument, parsedPattern, pattern, charFunction) ) {
continue;
}
if (direction==FORWARDS && !currentAddress.precedes(argument)) {
index = matches.size() + 1;
} else if (index==null && !currentAddress.follows(argument)) {
index = matches.size() - 2;
}
matches.add(argument);
}
}
if (matches.size()==0) {
return false;
}
if (index==null) {
index=0;
}
searchMatches = new LoopIterator<Argument>(matches, index, direction);
if (direction==FORWARDS) {
getNextMatch();
} else {
getPreviousMatch();
}
return true;
}
public boolean searchAgain() {
if (this.pattern==null) return false;
return search(this.pattern, this.direction);
}
public boolean searchAgainst() {
if (this.pattern==null) return false;
return search(this.pattern, !this.direction);
}
/**
* If the search pattern is one of a predefined set of characters,
* return the corresponding function and null otherwise.
*/
private Function getFunctionByChar(String pattern) {
String trimmedPattern = pattern.trim();
Function charFunction = null;
if (trimmedPattern.length()!=1) {
return null;
}
char c = trimmedPattern.charAt(0);
return knownArguments.getFunctionByChar(c);
}
private boolean isMatch(Argument argument, Argument parsedPattern, String pattern, Function charFunction) {
State searchState = parsedPattern.getState();
Symbol searchSymbol = searchState instanceof Symbol ? (Symbol) searchState : null;
if (argument.matches(pattern)) {
// An explicit match by name or by value.
// TODO: Is this to general for branches: their
// children are ignored and can match tokens?
return true;
} else if (parsedPattern==null) {
return false;
} else if (argument.subBranchEquals(parsedPattern)) {
return true;
} else if (charFunction!=null) {
if (argument.getFunction()==charFunction) {
return true;
}
}
Purpose argumentPurpose = argument.getPurpose();
if (searchSymbol==null) {
return false;
}
if (argumentPurpose==null) {
return false;
}
Set<Symbol> set = argumentPurpose.getMatchSymbols();
return set.contains(searchSymbol);
}
private Argument parsePattern(String pattern) {
Document document = modes.getDocument();
MathCollection mathCollection = document.getMathCollection();
return ArgumentParser.parseArgument(pattern, knownArguments, clipboard, mathCollection);
}
private void moveTo(Argument argument) { // TODO: Simplify
assert argument != null;
//- if (argument!=null) { //?
getSelection().setCurrent(argument);
//- }
}
private Argument getNextMatch() {
if (searchMatches==null) {
return null;
}
Argument nextArgument = searchMatches.next();
moveTo(nextArgument);
return nextArgument;
}
private Argument getPreviousMatch() {
if (searchMatches==null) {
return null;
}
Argument previousArgument = searchMatches.previous();
moveTo(previousArgument);
return previousArgument;
}
public Vector<Equation> getEquationMatches() {
if (searchMatches==null) {
return null;
}
Vector<Argument> args = searchMatches.getArgs();
Vector<Equation> equations = new Vector<Equation>();
for (Argument argument : args) {
equations.add(argument.getEquation());
}
return equations;
}
private Selection getSelection() {
return modes.getSelection();
}
private Argument getCurrent() {
return getSelection().getCurrent();
}
public boolean hasMatch(Argument a) {
if (a==null || searchMatches==null) return false;
return a.isIn(searchMatches.getArgs());
}
public void searchBackwardsForCurrent() {
modes.getSearch().search(getCurrent().toString(), Search.BACKWARDS);
}
public void searchForwardsForCurrent() {
modes.getSearch().search(getCurrent().toString(), Search.FORWARDS);
}
}