001: /*
002: * Created on 23.06.2005 for PIROL
003: *
004: * SVN header information:
005: * $Author: javamap $
006: * $Rev: 856 $
007: * $Date: 2007-06-18 21:15:27 -0700 (Mon, 18 Jun 2007) $
008: * $Id: FormulaParser.java 856 2007-06-19 04:15:27Z javamap $
009: */
010: package de.fho.jump.pirol.utilities.FormulaParsing;
011:
012: import com.vividsolutions.jump.feature.FeatureCollection;
013: import com.vividsolutions.jump.feature.FeatureSchema;
014:
015: import de.fho.jump.pirol.utilities.FormulaParsing.Operations.AdditionOperation;
016: import de.fho.jump.pirol.utilities.FormulaParsing.Operations.DivisionOperation;
017: import de.fho.jump.pirol.utilities.FormulaParsing.Operations.MultiplicationOperation;
018: import de.fho.jump.pirol.utilities.FormulaParsing.Operations.PowerOfOperation;
019: import de.fho.jump.pirol.utilities.FormulaParsing.Operations.SquareRootOperation;
020: import de.fho.jump.pirol.utilities.FormulaParsing.Operations.SubtractionOperation;
021: import de.fho.jump.pirol.utilities.FormulaParsing.Values.AttributeValue;
022: import de.fho.jump.pirol.utilities.FormulaParsing.Values.ConstantValue;
023: import de.fho.jump.pirol.utilities.attributes.AttributeInfo;
024: import de.fho.jump.pirol.utilities.debugOutput.DebugUserIds;
025: import de.fho.jump.pirol.utilities.debugOutput.PersonalLogger;
026: import de.fho.jump.pirol.utilities.i18n.PirolPlugInMessages;
027:
028: /**
029: * This class is a utility to parse formulas, that describe how an additional attribute value is to be calculated on a by feature basis.
030: * Formulas thereby can contain constant values as well as attribute values, that need to be extracted for each feature.<br><br>
031: *
032: * Formulas are exspected to be space-separated: Each attribute name, constant value, bracket or operator has to be surrounded by empty spaces.<br><br>
033: * 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>.
034: *
035: * @author Ole Rahn
036: * <br>
037: * <br>FH Osnabrück - University of Applied Sciences Osnabrück,
038: * <br>Project: PIROL (2005),
039: * <br>Subproject: Daten- und Wissensmanagement
040: *
041: * @version $Rev: 856 $
042: *
043: * @see de.fhOsnabrueck.jump.pirol.utilities.FeatureCollectionTools#applyFormulaToFeatureCollection(FeatureCollection, AttributeInfo, FormulaValue, boolean)
044: *
045: */
046: public class FormulaParser {
047:
048: protected static PersonalLogger logger = new PersonalLogger(
049: DebugUserIds.ALL);
050: public static final String KEY_SQRT = "sqrt:";
051: public static final String KEY_POW = "power:";
052:
053: /**
054: * Recursively parses a given (sub-) formula into a FormulaValue, which can be an operation with
055: * sub-FormularValues or a value.
056: *@param formula
057: *@param featSchema The feature schema to check attribute names, if neccessary
058: *@return the given formula parsed into a FormulaValue or null if the given String did not contain formula information
059: */
060: public static FormulaValue getValue(String formula,
061: FeatureSchema featSchema) {
062: logger.printDebug("parsing: " + formula);
063:
064: // kick out leading or trailing whitespaces
065: formula = formula.trim();
066:
067: FormulaValue value1 = null, value2 = null, theValue = null;
068:
069: // kick out brackets that surround the whole formula
070: if (formula.startsWith("(") && formula.endsWith(")"))
071: formula = FormulaParser.kickOutSurroundingBracket(formula);
072:
073: // if there is nothing to parse, return null
074: if (formula.length() == 0)
075: return null;
076:
077: // result should be { [formula/value], [operator], [formula/value] }
078: String[] operation = FormulaParser
079: .splitToFirstLevelOperation(formula);
080:
081: if (operation[1] == null) {
082: // "formula" is really just a value!
083: try {
084: double value = Double.parseDouble(operation[0]);
085: logger.printDebug("got value: " + value);
086: theValue = new ConstantValue(value);
087: } catch (Exception e) {
088: // value could not be parsed -> is it an attribute??
089: if (featSchema.hasAttribute(operation[0])
090: || (operation[0].startsWith("\"") && operation[0]
091: .endsWith("\""))) {
092: // yes! it's an attribute!
093: String attrName = operation[0];
094: if (attrName.startsWith("\"")
095: && attrName.endsWith("\"")) {
096: attrName = attrName.substring(1, attrName
097: .length() - 1);
098: }
099:
100: if (featSchema.hasAttribute(attrName)) {
101: theValue = new AttributeValue(attrName);
102: } else {
103: logger.printError("could not parse: "
104: + attrName);
105: throw new IllegalArgumentException(
106: PirolPlugInMessages
107: .getString("do-not-know-how-to-parse")
108: + ": >" + attrName + "<");
109: }
110:
111: } else if (operation[0].trim().startsWith(
112: FormulaParser.KEY_SQRT)) {
113: theValue = new SquareRootOperation(FormulaParser
114: .getValue(operation[0]
115: .substring(
116: FormulaParser.KEY_SQRT
117: .length() + 1)
118: .trim(), featSchema));
119: } else if (operation[0].trim().startsWith(
120: FormulaParser.KEY_POW)) {
121: String theTwoValuesStr = operation[0].trim()
122: .substring(
123: FormulaParser.KEY_POW.length() + 1)
124: .trim();
125:
126: if (theTwoValuesStr.indexOf(",") < 0) {
127: logger
128: .printError("damaged power of operation, can not determine exponent: >"
129: + operation[0] + "<");
130: throw new IllegalArgumentException(
131: "damaged power of operation, can not determine exponent: >"
132: + operation[0] + "<");
133: }
134:
135: String value1Str = theTwoValuesStr.substring(0,
136: theTwoValuesStr.indexOf(",")).trim();
137: String value2Str = theTwoValuesStr.substring(
138: theTwoValuesStr.indexOf(",") + 1).trim();
139:
140: theValue = new PowerOfOperation(FormulaParser
141: .getValue(value1Str, featSchema),
142: FormulaParser.getValue(value2Str,
143: featSchema));
144: } else {
145: logger.printError("could not parse: "
146: + operation[0]);
147: throw new IllegalArgumentException(
148: PirolPlugInMessages
149: .getString("do-not-know-how-to-parse")
150: + ": >" + operation[0] + "<");
151: }
152: }
153: } else {
154: value1 = FormulaParser.getValue(operation[0], featSchema);
155: value2 = FormulaParser.getValue(operation[2], featSchema);
156:
157: if (operation[1].length() != 1)
158: logger.printWarning("corrupted operator (?): "
159: + operation[1]);
160:
161: char op = operation[1].charAt(0);
162:
163: switch (op) {
164: case '*':
165: theValue = new MultiplicationOperation(value1, value2);
166: break;
167: case '/':
168: theValue = new DivisionOperation(value1, value2);
169: break;
170: case '+':
171: theValue = new AdditionOperation(value1, value2);
172: break;
173: case '-':
174: theValue = new SubtractionOperation(value1, value2);
175: break;
176: default:
177: logger.printError("unknown operator found: " + op);
178: throw new IllegalArgumentException(
179: "unknown operator found: " + op);
180:
181: }
182:
183: if (!theValue.isFeatureDependent()) {
184: // identify sub-formulas that consist of constant values and turn them
185: // into ConstantValue object, to speed up processing
186: logger.printDebug("found constant parts: "
187: + theValue.toString());
188: theValue = new ConstantValue(theValue.getValue(null));
189: }
190: }
191:
192: return theValue;
193:
194: }
195:
196: protected static boolean isOperator(String op) {
197: return (op.equals("*") || op.equals("/") || op.equals("+") || op
198: .equals("-"));
199: }
200:
201: protected static boolean isBracket(String brack) {
202: return (brack.equals("(") || brack.equals(")"));
203: }
204:
205: protected static int findFirstOccuranceOutsideABracket(
206: String toBeFound, String formula, int fromIndex) {
207: char[] characters = formula.toCharArray();
208: char char2bFound = toBeFound.charAt(0);
209:
210: if (toBeFound.length() != 1)
211: logger
212: .printWarning("string does not seem to be an operator");
213:
214: int bracketOpen = 0, bracketClose = 0;
215: int numQuote = 0;
216:
217: for (int i = Math.max(0, fromIndex); i < characters.length; i++) {
218:
219: if (characters[i] == '(')
220: bracketOpen++;
221: else if (characters[i] == ')')
222: bracketClose++;
223: else if (characters[i] == '\"')
224: numQuote++;
225:
226: else if (characters[i] == char2bFound
227: && bracketOpen == bracketClose && numQuote % 2 == 0)
228: return i;
229: }
230: return -1;
231: }
232:
233: protected static int findFirstAddSubOperatorOutsideABracket(
234: String formula, int fromIndex) {
235: int firstAddOperator = FormulaParser
236: .findFirstOccuranceOutsideABracket("+", formula,
237: fromIndex);
238: int firstSubOperator = FormulaParser
239: .findFirstOccuranceOutsideABracket("-", formula,
240: fromIndex);
241:
242: return (firstAddOperator > -1 && firstSubOperator > -1) ? Math
243: .min(firstAddOperator, firstSubOperator) : Math.max(
244: firstAddOperator, firstSubOperator);
245: }
246:
247: protected static int findFirstMultiDivOperatorOutsideABracket(
248: String formula, int fromIndex) {
249: int firstMultiOperator = FormulaParser
250: .findFirstOccuranceOutsideABracket("*", formula,
251: fromIndex);
252: int firstDivOperator = FormulaParser
253: .findFirstOccuranceOutsideABracket("/", formula,
254: fromIndex);
255:
256: return (firstMultiOperator > -1 && firstDivOperator > -1) ? Math
257: .min(firstMultiOperator, firstDivOperator)
258: : Math.max(firstMultiOperator, firstDivOperator);
259: }
260:
261: protected static String[] splitToFirstLevelOperation(String formula) {
262: String[] firstLevelOperation = new String[] { null, null, null };
263: int firstMultiOrDivIndex = -1;
264: int firstAddOrSubIndex = -1;
265: int operatorIndex = -1;
266:
267: // are there multiplication/divsions??
268: firstMultiOrDivIndex = FormulaParser
269: .findFirstMultiDivOperatorOutsideABracket(formula, -1);
270: firstAddOrSubIndex = FormulaParser
271: .findFirstAddSubOperatorOutsideABracket(formula, -1);
272:
273: if (firstMultiOrDivIndex < 0 && firstAddOrSubIndex < 0) {
274: // no operations - just a simple value!
275: firstLevelOperation[0] = formula;
276: } else {
277:
278: if ((firstMultiOrDivIndex < 0 || firstAddOrSubIndex < 0)) {
279: // just like operations, no priorities
280: if (firstAddOrSubIndex > -1)
281: operatorIndex = firstAddOrSubIndex;
282: else {
283: int firstMultiOperator = FormulaParser
284: .findFirstOccuranceOutsideABracket("*",
285: formula, -1);
286: int firstDivOperator = FormulaParser
287: .findFirstOccuranceOutsideABracket("/",
288: formula, -1);
289:
290: if (firstMultiOperator < 0) {
291: operatorIndex = firstDivOperator;
292: } else {
293: operatorIndex = firstMultiOperator;
294: }
295: }
296: } else if (firstMultiOrDivIndex > -1
297: && firstAddOrSubIndex > -1) {
298: // mixed operations, multiplications/divisions have priority! do not divide the formula there!
299: operatorIndex = firstAddOrSubIndex;
300: }
301:
302: firstLevelOperation[0] = formula
303: .substring(0, operatorIndex).trim();
304: firstLevelOperation[1] = formula.substring(operatorIndex,
305: Math.min(operatorIndex + 2, formula.length()))
306: .trim();
307: firstLevelOperation[2] = formula.substring(
308: Math.min(operatorIndex + 2, formula.length()))
309: .trim();
310:
311: logger.printDebug("----");
312: logger.printDebug(firstLevelOperation[0] + "; "
313: + firstLevelOperation[1] + "; "
314: + firstLevelOperation[2]);
315:
316: }
317:
318: return firstLevelOperation;
319: }
320:
321: protected static String getFirstCompleteBracketString(
322: String formula, int fromIndex) {
323: formula = formula.trim();
324:
325: char[] characters = formula.toCharArray();
326: int bracketOpen = 0, bracketClose = 0, firstOpenPos = -1;
327:
328: for (int i = Math.max(0, fromIndex); i < characters.length; i++) {
329: if (characters[i] == '(') {
330: if (bracketOpen == 0)
331: firstOpenPos = i;
332: bracketOpen++;
333: } else if (characters[i] == ')')
334: bracketClose++;
335:
336: if ((bracketOpen != 0 && bracketClose != 0)
337: && i < (characters.length - 1)
338: && bracketOpen == bracketClose) {
339: return formula.substring(firstOpenPos, i + 1).trim();
340: }
341: }
342:
343: if (bracketOpen != bracketClose && fromIndex > -1) {
344: logger.printMinorError("damaged bracket found in: "
345: + formula);
346: throw new IllegalArgumentException(
347: "damaged bracket found in: " + formula);
348: }
349:
350: return formula;
351: }
352:
353: /**
354: * deletes a bracket that surrounds the whole formula from the formula.
355: *@param formula formula String
356: *@return formula String without surrounding bracket
357: */
358: protected static String kickOutSurroundingBracket(String formula) {
359: formula = formula.trim();
360:
361: // first check if one bracket surrounds the whole formula
362: char[] characters = formula.toCharArray();
363: int bracketOpen = 0, bracketClose = 0;
364:
365: for (int i = 0; i < characters.length; i++) {
366: if (characters[i] == '(')
367: bracketOpen++;
368: else if (characters[i] == ')')
369: bracketClose++;
370:
371: if ((bracketOpen != 0 && bracketClose != 0)
372: && i < (characters.length - 1)
373: && bracketOpen == bracketClose) {
374: // nope, the bracket does not surround the whole formula!
375: return formula;
376: }
377: }
378:
379: if (bracketOpen != bracketClose) {
380: logger.printMinorError("damaged bracket found in: "
381: + formula);
382: throw new IllegalArgumentException(
383: "damaged bracket found in: " + formula);
384: }
385:
386: // yes, seems like the bracket indeed surrounds the whole formula!
387: return formula.substring(formula.indexOf("(") + 1,
388: formula.lastIndexOf(")")).trim();
389: }
390:
391: }
|