/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * !# */ package net.ontopia.topicmaps.query.impl.basic; import java.util.List; import java.util.Map; import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.ParseException; import net.ontopia.topicmaps.impl.utils.Argument; import net.ontopia.topicmaps.query.core.InvalidQueryException; import net.ontopia.topicmaps.query.impl.utils.PredicateSignature; import net.ontopia.topicmaps.query.impl.utils.PredicateDrivenCostEstimator; import net.ontopia.topicmaps.query.parser.ModuleIF; import net.ontopia.topicmaps.query.parser.PredicateIF; import net.ontopia.utils.OntopiaRuntimeException; public class NumbersModule implements ModuleIF { public static final String MODULE_URI = "http://psi.ontopia.net/tolog/numbers/"; private Map<String, PredicateIF> predicates; public NumbersModule() { predicates = new HashMap<String, PredicateIF>(); add(new NumbersValuePredicate()); add(new NumbersFormatPredicate()); add(new NumbersAbsolutePredicate()); add(new NumbersAddPredicate()); add(new NumbersSubtractPredicate()); add(new NumbersMultiplyPredicate()); add(new NumbersDividePredicate()); add(new NumbersMinimumPredicate()); add(new NumbersMaximumPredicate()); } private void add(PredicateIF predicate) { predicates.put(predicate.getName(), predicate); } public PredicateIF getPredicate(String name) { return predicates.get(name); } // --- NumberSupport -- enum for supported Number types, currently Integer and Float public enum NumberSupport { INTEGER(32), FLOAT(320); private int precisionRanking; private NumberSupport(int precisionRanking) { this.precisionRanking = precisionRanking; } private Class toClass() { switch (this) { case INTEGER: return Integer.class; case FLOAT: return Float.class; default: return null; } } private static NumberSupport fromClass(Class klass) { if (Integer.class.isAssignableFrom(klass)) return INTEGER; if (Float.class.isAssignableFrom(klass)) return FLOAT; return null; } public static NumberSupport getHighestPrecision(Number[] values) throws InvalidQueryException { int highestPrecision = 0; NumberSupport highestPrecisionNumber = null; for (Number value : values) { NumberSupport currentNumber = NumberSupport.fromClass(value.getClass()); int currentPrecision = currentNumber.precisionRanking; if (currentPrecision > highestPrecision) { highestPrecision = currentPrecision; highestPrecisionNumber = currentNumber; } } return highestPrecisionNumber; } public static Integer[] castToIntegers(Number[] values) { Integer[] result = new Integer[values.length]; for (int i = 0; i < values.length; i++) { result[i] = values[i].intValue(); } return result; } public static Float[] castToFloats(Number[] values) { Float[] result = new Float[values.length]; for (int i = 0; i < values.length; i++) { result[i] = values[i].floatValue(); } return result; } public static Number castToSupportedClass(Number value) { return (Integer.class.isAssignableFrom(value.getClass())) ? value : value.floatValue(); } }; // --- AbstractPredicate abstract class AbstractPredicate implements BasicPredicateIF { public int getCost(boolean[] boundparams) { try { int signLenghth = -1; PredicateSignature sign = PredicateSignature.getSignature(this); for (int ix = 0; ix < boundparams.length; ix++) { Argument arg = sign.getArgument(ix); if (arg != null) { signLenghth = ix; } else { Argument lastArg = sign.getArgument(signLenghth); if (lastArg.isRepeatable()) { arg = lastArg; } } if (arg == null) { throw new InvalidQueryException("Cost for predicate '" + getName() + "' cannot be calculated. Argument " + ix + " is null."); } if (arg.mustBeBound() && !boundparams[ix]) { return PredicateDrivenCostEstimator.INFINITE_RESULT; } } return PredicateDrivenCostEstimator.FILTER_RESULT; } catch (InvalidQueryException e) { throw new OntopiaRuntimeException(e); } } protected void addMatch(QueryMatches result, Object[] row) { if (result.last + 1 == result.size) { result.increaseCapacity(); } result.data[++result.last] = row; } } // --- AbstractNumbersPredicate -- works with exactly one unbound argument abstract class AbstractNumbersPredicate extends AbstractPredicate { private Integer unboundArgument = null; protected void calculateUnboundArgument() throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); int ix; for (Argument arg = sign.getArgument(ix = 0); arg != null; arg = sign.getArgument(++ix)) { if (!arg.mustBeBound()) { if (unboundArgument == null) { unboundArgument = ix; } else { throw new InvalidQueryException("AbstractNumbersPredicate works only with predicates with one unbound argument, " + "predicate '" + getName() + "' has multiple unbound arguments in signature '" + getSignature() + "'"); } } } if (unboundArgument == null) { throw new InvalidQueryException("AbstractNumbersPredicate works only with predicates with one unbound argument, " + "predicate '" + getName() + "' has no unbound arguments in signature '" + getSignature() + "'"); } } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { if (unboundArgument == null) { calculateUnboundArgument(); } PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int[] colix = new int[arguments.length]; for (int i = 0; i < arguments.length; i++) { colix[i] = matches.getIndex(arguments[i]); } int unboundColumn = colix[unboundArgument]; QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { Object[] row = matches.data[ix]; List<Number> boundValues = new ArrayList<Number>(); for (int i = 0; i < arguments.length; i++) { if (i != unboundArgument) { boundValues.add((Number) row[colix[i]]); } } Number[] boundValuesArray = boundValues.toArray(new Number[boundValues.size()]); Number resultValue = calculateResult(boundValuesArray); if (matches.bound(unboundColumn)) { if (resultValue.equals((Number) row[unboundColumn])) { addMatch(result, row); } } else { Object[] newRow = row.clone(); newRow[unboundColumn] = resultValue; addMatch(result, newRow); } } return result; } protected Number calculateResult(Number[] values) throws InvalidQueryException { switch (NumberSupport.getHighestPrecision(values)) { case INTEGER: return calculateResult(NumberSupport.castToIntegers(values)); case FLOAT: return calculateResult(NumberSupport.castToFloats(values)); default: return null; } } protected abstract Number calculateResult(Integer[] values) throws InvalidQueryException; protected abstract Number calculateResult(Float[] values) throws InvalidQueryException; } // --- AbstractNumbersArrayPickerPredicate -- returns element from array, in stead of calculated result abstract class AbstractNumbersArrayPickerPredicate extends AbstractNumbersPredicate { protected Number calculateResult(Number[] values) throws InvalidQueryException { switch (NumberSupport.getHighestPrecision(values)) { case INTEGER: return values[calculateResult(NumberSupport.castToIntegers(values)).intValue()]; case FLOAT: return values[calculateResult(NumberSupport.castToFloats(values)).intValue()]; default: return null; } } } // --- value(string, result, pattern?, locale?) class NumbersValuePredicate extends AbstractPredicate { public String getName() { return "value"; } public String getSignature() { return "s! n s!? s!?"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); if (arguments.length == 3) { throw new InvalidQueryException("Variable 'locale' must be provided" + " when 'pattern' is used in predicate " + this.getName()); } int colix1 = matches.getIndex(arguments[0]); int colix2 = matches.getIndex(arguments[1]); int colix3 = (arguments.length > 3) ? matches.getIndex(arguments[2]) : -1; int colix4 = (arguments.length > 3) ? matches.getIndex(arguments[3]) : -1; QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { Object[] row = matches.data[ix]; Number resultValue = (arguments.length > 3) ? calculateResult((String) row[colix1], (String) row[colix3], (String) row[colix4]) : calculateResult((String) row[colix1]); if (matches.bound(colix2)) { if (resultValue.equals((Number) row[colix2])) { addMatch(result, row); } } else { Object[] newRow = row.clone(); newRow[colix2] = resultValue; addMatch(result, newRow); } } return result; } private Number calculateResult(String value) throws InvalidQueryException { try { return Integer.parseInt(value); } catch (NumberFormatException fallthrough) { try { return Float.parseFloat(value); } catch (NumberFormatException e) { throw new InvalidQueryException(e); } } } private Number calculateResult(String value, String pattern, String locale) throws InvalidQueryException { try { Locale l = new Locale(locale); DecimalFormatSymbols symbols = new DecimalFormatSymbols(l); DecimalFormat formatter = new DecimalFormat(pattern, symbols); return calculateResult(value, formatter); } catch (IllegalArgumentException e) { throw new InvalidQueryException(e); } } private Number calculateResult(String value, DecimalFormat formatter) throws InvalidQueryException { try { Number result = formatter.parse(value); return NumberSupport.castToSupportedClass(result); } catch (ParseException e) { throw new InvalidQueryException(e); } } } // --- format(number, result, pattern?, locale?) class NumbersFormatPredicate extends AbstractPredicate { public String getName() { return "format"; } public String getSignature() { return "n! s s!? s!?"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); if (arguments.length == 3) { throw new InvalidQueryException("Variable 'locale' must be provided" + " when 'pattern' is used in predicate " + this.getName()); } int colix1 = matches.getIndex(arguments[0]); int colix2 = matches.getIndex(arguments[1]); int colix3 = (arguments.length > 3) ? matches.getIndex(arguments[2]) : -1; int colix4 = (arguments.length > 3) ? matches.getIndex(arguments[3]) : -1; try { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { Object[] row = matches.data[ix]; String resultValue = (arguments.length > 3) ? calculateResult((Number) row[colix1], (String) row[colix3], (String) row[colix4]) : calculateResult((Number) row[colix1]); if (matches.bound(colix2)) { if (resultValue.equals((String) row[colix2])) { addMatch(result, row); } } else { Object[] newRow = row.clone(); newRow[colix2] = resultValue; addMatch(result, newRow); } } return result; } catch (IllegalArgumentException e) { throw new InvalidQueryException(e); } } private String calculateResult(Number value) { return value.toString(); } private String calculateResult(Number value, String pattern, String locale) throws InvalidQueryException { try { Locale l = new Locale(locale); DecimalFormatSymbols symbols = new DecimalFormatSymbols(l); DecimalFormat formatter = new DecimalFormat(pattern, symbols); return formatter.format(value); } catch (IllegalArgumentException e) { throw new InvalidQueryException(e); } } } // --- absolute(number, result) class NumbersAbsolutePredicate extends AbstractNumbersPredicate { public String getName() { return "absolute"; } public String getSignature() { return "n! n"; } protected Number calculateResult(Integer[] values) { return Math.abs(values[0]); } protected Number calculateResult(Float[] values) { return Math.abs(values[0]); } } // --- add(result, number, number+) class NumbersAddPredicate extends AbstractNumbersPredicate { public String getName() { return "add"; } public String getSignature() { return "n n! n!+"; } protected Number calculateResult(Integer[] values) { int result = 0; for (int value : values) { result += value; } return result; } protected Number calculateResult(Float[] values) { float result = 0.0f; for (float value : values) { result += value; } return result; } } // --- subtract(result, number, number+) class NumbersSubtractPredicate extends AbstractNumbersPredicate { public String getName() { return "subtract"; } public String getSignature() { return "n n! n!+"; } protected Number calculateResult(Integer[] values) { int result = 0; int first = -1; for (int value : values) { result -= (first * value); first = 1; } return result; } protected Number calculateResult(Float[] values) { float result = 0.0f; int first = -1; for (float value : values) { result -= (first * value); first = 1; } return result; } } // --- multiply(result, number, number+) class NumbersMultiplyPredicate extends AbstractNumbersPredicate { public String getName() { return "multiply"; } public String getSignature() { return "n n! n!+"; } protected Number calculateResult(Integer[] values) { int result = 1; for (int value : values) { result *= value; } return result; } protected Number calculateResult(Float[] values) { float result = 1.0f; for (float value : values) { result *= value; } return result; } } // --- divide(result, number, number+) class NumbersDividePredicate extends AbstractNumbersPredicate { public String getName() { return "divide"; } public String getSignature() { return "n n! n!+"; } protected Number calculateResult(Integer[] values) throws InvalidQueryException { try { int result = 1; boolean first = true; for (int value : values) { result = (first) ? (result * value) : (result / value); first = false; } return result; } catch (ArithmeticException e) { // e.g. devide by 0 throw new InvalidQueryException(e); } } protected Number calculateResult(Float[] values) throws InvalidQueryException { try { float result = 1.0f; boolean first = true; for (float value : values) { result = (first) ? (result * value) : (result / value); first = false; } return result; } catch (ArithmeticException e) { // e.g. devide by 0 throw new InvalidQueryException(e); } } } // --- min(result, number, number+) class NumbersMinimumPredicate extends AbstractNumbersArrayPickerPredicate { public String getName() { return "min"; } public String getSignature() { return "n n! n!+"; } protected Number calculateResult(Integer[] values) { Integer minimum = values[0]; int index = 0; int counter = 0; for (int value : values) { if (value < minimum) { minimum = value; index = counter; } counter++; } return index; } protected Number calculateResult(Float[] values) { Float minimum = values[0]; int index = 0; int counter = 0; for (float value : values) { if (value < minimum) { minimum = value; index = counter; } counter++; } return index; } } // --- max(result, number, number+) class NumbersMaximumPredicate extends AbstractNumbersArrayPickerPredicate { public String getName() { return "max"; } public String getSignature() { return "n n! n!+"; } protected Number calculateResult(Integer[] values) { Integer maximum = values[0]; int index = 0; int counter = 0; for (int value : values) { if (value > maximum) { maximum = value; index = counter; } counter++; } return index; } protected Number calculateResult(Float[] values) { Float maximum = values[0]; int index = 0; int counter = 0; for (float value : values) { if (value > maximum) { maximum = value; index = counter; } counter++; } return index; } } }