/* * Created on 23.06.2005 for PIROL * * SVN header information: * $Author: michaudm $ * $Rev: 2259 $ * $Date: 2011-05-08 12:47:24 +0200 (So, 08. Mai 2011) $ * $Id: FormulaParser.java 2259 2011-05-08 10:47:24Z michaudm $ */ package de.fho.jump.pirol.utilities.FormulaParsing; import com.vividsolutions.jump.feature.FeatureCollection; import com.vividsolutions.jump.feature.FeatureSchema; import de.fho.jump.pirol.utilities.FormulaParsing.Operations.AdditionOperation; import de.fho.jump.pirol.utilities.FormulaParsing.Operations.DivisionOperation; import de.fho.jump.pirol.utilities.FormulaParsing.Operations.MultiplicationOperation; import de.fho.jump.pirol.utilities.FormulaParsing.Operations.PowerOfOperation; import de.fho.jump.pirol.utilities.FormulaParsing.Operations.SquareRootOperation; import de.fho.jump.pirol.utilities.FormulaParsing.Operations.SubtractionOperation; import de.fho.jump.pirol.utilities.FormulaParsing.Values.AttributeValue; import de.fho.jump.pirol.utilities.FormulaParsing.Values.ConstantValue; import de.fho.jump.pirol.utilities.attributes.AttributeInfo; import de.fho.jump.pirol.utilities.debugOutput.DebugUserIds; import de.fho.jump.pirol.utilities.debugOutput.PersonalLogger; import de.fho.jump.pirol.utilities.i18n.PirolPlugInMessages; /** * This class is a utility to parse formulas, that describe how an additional attribute value is to be calculated on a by feature basis. * Formulas thereby can contain constant values as well as attribute values, that need to be extracted for each feature.<br><br> * * Formulas are exspected to be space-separated: Each attribute name, constant value, bracket or operator has to be surrounded by empty spaces.<br><br> * A valid formulas (for a FeatureSchema that has the attributes "yield" and "grain mois") would look like this: <br><code>( 4 + 6 ) * yield</code><br> or<br><code>grain mois / 2</code>. * * @author Ole Rahn * <br> * <br>FH Osnabrück - University of Applied Sciences Osnabrück, * <br>Project: PIROL (2005), * <br>Subproject: Daten- und Wissensmanagement * * @version $Rev: 2259 $ * * @see org.openjump.core.apitools.FeatureCollectionTools#applyFormulaToFeatureCollection(FeatureCollection, AttributeInfo, FormulaValue, boolean) * */ public class FormulaParser { protected static PersonalLogger logger = new PersonalLogger(DebugUserIds.ALL); public static final String KEY_SQRT = "sqrt:"; public static final String KEY_POW = "power:"; /** * Recursively parses a given (sub-) formula into a FormulaValue, which can be an operation with * sub-FormularValues or a value. *@param formula *@param featSchema The feature schema to check attribute names, if neccessary *@return the given formula parsed into a FormulaValue or null if the given String did not contain formula information */ public static FormulaValue getValue(String formula, FeatureSchema featSchema){ logger.printDebug("parsing: " + formula); // kick out leading or trailing whitespaces formula = formula.trim(); FormulaValue value1 = null, value2 = null, theValue = null; // kick out brackets that surround the whole formula if (formula.startsWith("(") && formula.endsWith(")")) formula = FormulaParser.kickOutSurroundingBracket(formula); // if there is nothing to parse, return null if (formula.length() == 0) return null; // result should be { [formula/value], [operator], [formula/value] } String[] operation = FormulaParser.splitToFirstLevelOperation(formula); if (operation[1] == null){ // "formula" is really just a value! try { double value = Double.parseDouble(operation[0]); logger.printDebug("got value: " + value); theValue = new ConstantValue(value); } catch (Exception e) { // value could not be parsed -> is it an attribute?? if (featSchema.hasAttribute(operation[0]) || (operation[0].startsWith("\"") && operation[0].endsWith("\"")) ){ // yes! it's an attribute! String attrName = operation[0]; if ( attrName.startsWith("\"") && attrName.endsWith("\"") ) { attrName = attrName.substring(1, attrName.length() - 1); } if (featSchema.hasAttribute(attrName)) { theValue = new AttributeValue(attrName); } else { logger.printError("could not parse: " + attrName); throw new IllegalArgumentException( PirolPlugInMessages.getString("do-not-know-how-to-parse") + ": >" + attrName + "<"); } } else if (operation[0].trim().startsWith(FormulaParser.KEY_SQRT)){ theValue = new SquareRootOperation(FormulaParser.getValue(operation[0].substring(FormulaParser.KEY_SQRT.length()+1).trim(), featSchema)); } else if (operation[0].trim().startsWith(FormulaParser.KEY_POW)){ String theTwoValuesStr = operation[0].trim().substring(FormulaParser.KEY_POW.length()+1).trim(); if (theTwoValuesStr.indexOf(",") < 0){ logger.printError("damaged power of operation, can not determine exponent: >" + operation[0] + "<"); throw new IllegalArgumentException("damaged power of operation, can not determine exponent: >" + operation[0] + "<"); } String value1Str = theTwoValuesStr.substring(0,theTwoValuesStr.indexOf(",")).trim(); String value2Str = theTwoValuesStr.substring(theTwoValuesStr.indexOf(",")+1).trim(); theValue = new PowerOfOperation(FormulaParser.getValue(value1Str, featSchema), FormulaParser.getValue(value2Str, featSchema)); } else { logger.printError("could not parse: " + operation[0]); throw new IllegalArgumentException( PirolPlugInMessages.getString("do-not-know-how-to-parse") + ": >" + operation[0] + "<"); } } } else { value1 = FormulaParser.getValue(operation[0], featSchema); value2 = FormulaParser.getValue(operation[2], featSchema); if (operation[1].length()!=1) logger.printWarning("corrupted operator (?): " + operation[1]); char op = operation[1].charAt(0); switch (op){ case '*': theValue = new MultiplicationOperation(value1, value2); break; case '/': theValue = new DivisionOperation(value1, value2); break; case '+': theValue = new AdditionOperation(value1, value2); break; case '-': theValue = new SubtractionOperation(value1, value2); break; default: logger.printError("unknown operator found: " + op); throw new IllegalArgumentException("unknown operator found: " + op); } if (!theValue.isFeatureDependent()){ // identify sub-formulas that consist of constant values and turn them // into ConstantValue object, to speed up processing logger.printDebug("found constant parts: " + theValue.toString()); theValue = new ConstantValue(theValue.getValue(null)); } } return theValue; } protected static boolean isOperator( String op ){ return (op.equals("*") || op.equals("/") || op.equals("+") || op.equals("-")); } protected static boolean isBracket( String brack ){ return (brack.equals("(") || brack.equals(")")); } protected static int findFirstOccuranceOutsideABracket(String toBeFound, String formula, int fromIndex){ char[] characters = formula.toCharArray(); char char2bFound = toBeFound.charAt(0); if (toBeFound.length() != 1) logger.printWarning("string does not seem to be an operator"); int bracketOpen = 0, bracketClose = 0; int numQuote = 0; for (int i=Math.max(0, fromIndex); i<characters.length; i++){ if (characters[i] == '(') bracketOpen++; else if (characters[i] == ')') bracketClose++; else if (characters[i] == '\"') numQuote++; else if (characters[i]==char2bFound && bracketOpen==bracketClose && numQuote%2==0) return i; } return -1; } protected static int findFirstAddSubOperatorOutsideABracket(String formula, int fromIndex){ int firstAddOperator = FormulaParser.findFirstOccuranceOutsideABracket("+", formula, fromIndex); int firstSubOperator = FormulaParser.findFirstOccuranceOutsideABracket("-", formula, fromIndex); return ( firstAddOperator > -1 && firstSubOperator > -1 )?Math.min(firstAddOperator,firstSubOperator):Math.max(firstAddOperator,firstSubOperator); } protected static int findFirstMultiDivOperatorOutsideABracket(String formula, int fromIndex){ int firstMultiOperator = FormulaParser.findFirstOccuranceOutsideABracket("*", formula, fromIndex); int firstDivOperator = FormulaParser.findFirstOccuranceOutsideABracket("/", formula, fromIndex); return ( firstMultiOperator > -1 && firstDivOperator > -1 )?Math.min(firstMultiOperator,firstDivOperator):Math.max(firstMultiOperator,firstDivOperator); } protected static String[] splitToFirstLevelOperation(String formula){ String[] firstLevelOperation = new String[]{null, null, null}; int firstMultiOrDivIndex = -1; int firstAddOrSubIndex = -1; int operatorIndex = -1; // are there multiplication/divsions?? firstMultiOrDivIndex = FormulaParser.findFirstMultiDivOperatorOutsideABracket(formula, -1); firstAddOrSubIndex = FormulaParser.findFirstAddSubOperatorOutsideABracket(formula, -1); if ( firstMultiOrDivIndex < 0 && firstAddOrSubIndex < 0 ){ // no operations - just a simple value! firstLevelOperation[0] = formula; } else { if ((firstMultiOrDivIndex < 0 || firstAddOrSubIndex < 0)) { // just like operations, no priorities if (firstAddOrSubIndex > -1) operatorIndex = firstAddOrSubIndex; else { int firstMultiOperator = FormulaParser.findFirstOccuranceOutsideABracket("*", formula, -1); int firstDivOperator = FormulaParser.findFirstOccuranceOutsideABracket("/", formula, -1); if (firstMultiOperator < 0){ operatorIndex = firstDivOperator; } else { operatorIndex = firstMultiOperator; } } } else if (firstMultiOrDivIndex > -1 && firstAddOrSubIndex > -1) { // mixed operations, multiplications/divisions have priority! do not divide the formula there! operatorIndex = firstAddOrSubIndex; } firstLevelOperation[0] = formula.substring(0, operatorIndex).trim(); firstLevelOperation[1] = formula.substring(operatorIndex, Math.min(operatorIndex + 2, formula.length())).trim(); firstLevelOperation[2] = formula.substring(Math.min(operatorIndex + 2, formula.length())).trim(); logger.printDebug("----"); logger.printDebug(firstLevelOperation[0] +"; " + firstLevelOperation[1] + "; " + firstLevelOperation[2]); } return firstLevelOperation; } protected static String getFirstCompleteBracketString(String formula, int fromIndex){ formula = formula.trim(); char[] characters = formula.toCharArray(); int bracketOpen = 0, bracketClose = 0, firstOpenPos = -1; for (int i=Math.max(0, fromIndex); i<characters.length; i++){ if (characters[i] == '('){ if (bracketOpen==0) firstOpenPos = i; bracketOpen++; } else if (characters[i] == ')') bracketClose++; if ( (bracketOpen != 0 && bracketClose!=0) && i<(characters.length-1) && bracketOpen==bracketClose ){ return formula.substring(firstOpenPos, i+1).trim(); } } if (bracketOpen!=bracketClose && fromIndex>-1){ logger.printMinorError("damaged bracket found in: " + formula); throw new IllegalArgumentException("damaged bracket found in: " + formula); } return formula; } /** * deletes a bracket that surrounds the whole formula from the formula. *@param formula formula String *@return formula String without surrounding bracket */ protected static String kickOutSurroundingBracket(String formula){ formula = formula.trim(); // first check if one bracket surrounds the whole formula char[] characters = formula.toCharArray(); int bracketOpen = 0, bracketClose = 0; for (int i=0; i<characters.length; i++){ if (characters[i] == '(') bracketOpen++; else if (characters[i] == ')') bracketClose++; if ( (bracketOpen != 0 && bracketClose!=0) && i<(characters.length-1) && bracketOpen==bracketClose ){ // nope, the bracket does not surround the whole formula! return formula; } } if (bracketOpen!=bracketClose){ logger.printMinorError("damaged bracket found in: " + formula); throw new IllegalArgumentException("damaged bracket found in: " + formula); } // yes, seems like the bracket indeed surrounds the whole formula! return formula.substring(formula.indexOf("(")+1,formula.lastIndexOf(")")).trim(); } }