package org.geogebra.test.util;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.junit.internal.matchers.TypeSafeMatcher;
/**
* Used for comparing multivariate polynomial equations which have to be in the
* syntax of the CAS output (for example "(a * x^(2) + b * x + c) / a = 0")
*
* @author Johannes Renner
*/
public class IsEqualPolynomialEquation extends TypeSafeMatcher<String> {
private final String expected;
// Key of the outer HashMap is the variable name + the exponent (for example
// "x^(2)")
// Value of the HashMap is the factor of the variable ordered
// alphabetically (for example: +1*x*y, -14*a*b)
// the array index indicates the part of the equation (a new part begins
// after every '/' character)
private static HashMap<String, String>[] expectedTermsLeft;
private static HashMap<String, String>[] expectedTermsRight;
private static HashMap<String, String>[] testResultTermsLeft;
private static HashMap<String, String>[] testResultTermsRight;
/**
* @param expected
* the expected equation
*/
public IsEqualPolynomialEquation(String expected) {
this.expected = expected;
}
@Override
public boolean matchesSafely(String testResult) {
extractTerms(expected, true);
extractTerms(testResult, false);
return compare();
}
@Override
public void describeTo(Description description) {
description.appendValue(expected);
}
/**
* Tests if a polynomial equation is equal to another one ignoring the
* ordering of the terms on each side of the '=' character.
*
* @param expected
* the expected polynomial equation
* @return <b<true</b> if the equations are same (ignoring the term ordering
* on each side, <b>false</b> otherwise
*/
@Factory
public static <T> Matcher<String> equalToPolynomialEquation(String expected) {
return new IsEqualPolynomialEquation(expected);
}
/**
* Extracts the terms out of the equation
*
* @param equation
* the equation to handle
* @param isExpectedResult
* if true the terms will be written to the class arguments
* <code>expectedTermsLeft</code> and
* <code>expectedTermsRight</code><br/>
* if false they will be written to
* <code>testResultTermsLeft</code> and
* <code>testResultTermsRight</code>
*/
@SuppressWarnings("unchecked")
private static void extractTerms(String equation, boolean isExpectedResult) {
// remove all blanks
String equationWithoutBlanks = equation.replaceAll(" ", "");
// split the equation in the left and the right side
String[] split = equationWithoutBlanks.split("=");
// split every side in their parts
String[] leftParts = split[0].split("/");
String[] rightParts = split[1].split("/");
if (isExpectedResult) {
expectedTermsLeft = new HashMap[leftParts.length];
expectedTermsRight = new HashMap[rightParts.length];
extractTermsOfParts(leftParts, expectedTermsLeft);
extractTermsOfParts(rightParts, expectedTermsRight);
} else {
testResultTermsLeft = new HashMap[leftParts.length];
testResultTermsRight = new HashMap[rightParts.length];
extractTermsOfParts(leftParts, testResultTermsLeft);
extractTermsOfParts(rightParts, testResultTermsRight);
}
}
/**
* Extracts all terms of the given parts of the equation
*
* @param parts
* the parts to handle
* @param terms
* the array of {@link HashMap}s which contain all terms of the
* parts of the equation
*/
private static void extractTermsOfParts(String[] parts,
HashMap<String, String>[] terms) {
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
// remove parentheses if there are some around the whole part
if (part.charAt(0) == '(') {
part = part.substring(1, part.length() - 1);
}
int termNumber = 0;
String signs = "";
for (int c = 0; c < part.length(); c++) {
char toCheck = part.charAt(c);
if (toCheck == '+' || toCheck == '-') {
termNumber++;
signs += toCheck;
} else if (termNumber == 0) {
// first factor has no sign so we add one
termNumber = 1;
signs += "+";
}
}
// if the first character of the part is a sign ('+' or '-') we have
// to remove that because otherwise we would get an empty string
// after splitting it up
if (part.charAt(0) == '+' || part.charAt(0) == '-') {
part = part.substring(1);
}
String[] splitPart = part.split("[\\+\\-]");
terms[i] = new HashMap<String, String>();
for (int p = 0; p < splitPart.length; p++) {
handleTerm(splitPart[p], signs.charAt(p), terms[i]);
}
}
}
/**
* Handles one term of the equation
*
* @param term
* the term to handle
* @param sign
* the sign of the given term
* @param terms
* the {@link HashMap} which contains all terms of this part of
* the equation
*/
private static void handleTerm(String term, char sign,
HashMap<String, String> terms) {
String numericalFactor = "1";
String[] splitTerm = term.split("\\*");
LinkedList<String> variables = new LinkedList<String>();
for (int i = 0; i < splitTerm.length; i++) {
// handle the number of the factor or one variable
String s = splitTerm[i];
if (Character.isDigit(s.charAt(0))) {
numericalFactor = s;
} else {
addAlphabetically(s, variables);
}
}
for (int i = 0; i < variables.size(); i++) {
String variable = variables.get(i);
String factor = sign + numericalFactor;
for (int ii = 0; ii < variables.size(); ii++) {
if (i != ii) {
// add every variable which is in the factor to the
// factor-String
factor += "*" + variables.get(ii);
}
}
terms.put(variable, factor);
}
}
/**
* Adds one variable to the given list, so that the list is alphabetically
* ordered
*
* @param variable
* the variable to add to the list
* @param list
* the list where the given variable will be added
*/
private static void addAlphabetically(String variable,
LinkedList<String> list) {
if (list.isEmpty()) {
list.add(variable);
return;
}
Iterator<String> it = list.iterator();
int position = 0;
while (it.hasNext()) {
String current = it.next();
if (current.compareTo(variable) < 0) {
position++;
} else {
break;
}
}
list.add(position, variable);
}
/**
* Compares the expected result and the test result and returns if they are
* equal or not
*
* @return true if the equations are equal (except their term ordering),
* false otherwise
*/
private static boolean compare() {
if (expectedTermsLeft.length != testResultTermsLeft.length
|| expectedTermsRight.length != testResultTermsRight.length) {
return false;
}
return compareSide(expectedTermsLeft, testResultTermsLeft)
&& compareSide(expectedTermsRight, testResultTermsRight);
}
/**
* Compares one side of the equation
*
* @param expectedTerms
* the array of the expected terms of all parts of one side of
* the equation
* @param testResultTerms
* the array of the test result terms of all parts of one side of
* the equation
* @return true if the compared side of the expected result and the test
* result are equal, false otherwise
*/
private static boolean compareSide(HashMap<String, String>[] expectedTerms,
HashMap<String, String>[] testResultTerms) {
for (int i = 0; i < expectedTerms.length; i++) {
Set<String> keys = expectedTerms[i].keySet();
Iterator<String> it = keys.iterator();
// compare the factors of every variable
while (it.hasNext()) {
String key = it.next();
if (!expectedTerms[i].get(key).equals(
testResultTerms[i].get(key))) {
return false;
}
}
}
return true;
}
}