/* * #! * 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.HashMap; import java.util.Map; import net.ontopia.utils.OntopiaRuntimeException; 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.parser.ModuleIF; import net.ontopia.topicmaps.query.parser.PredicateIF; import net.ontopia.topicmaps.query.impl.utils.PredicateDrivenCostEstimator; public class StringModule implements ModuleIF { public static final String MODULE_URI = "http://psi.ontopia.net/tolog/string/"; private Map predicates; public StringModule() { predicates = new HashMap(); add(new StartsWithPredicate()); add(new ContainsPredicate()); add(new LengthPredicate()); add(new ConcatPredicate()); add(new TranslatePredicate()); add(new SubstringPredicate()); add(new SubstringAfterPredicate()); add(new SubstringBeforePredicate()); add(new EndsWithPredicate()); add(new LastIndexOfPredicate()); add(new IndexOfPredicate()); } private void add(PredicateIF predicate) { predicates.put(predicate.getName(), predicate); } public PredicateIF getPredicate(String name) { return (PredicateIF) predicates.get(name); } // --- abstract --------------------------------------------------------- abstract class AbstractPredicate implements BasicPredicateIF { public int getCost(boolean[] boundparams) { try { PredicateSignature sign = PredicateSignature.getSignature(this); for (int ix = 0; ix < boundparams.length; ix++) { Argument arg = sign.getArgument(ix); if (arg == null) throw new OntopiaRuntimeException("INTERNAL ERROR"); if (arg.mustBeBound() && !boundparams[ix]) return PredicateDrivenCostEstimator.INFINITE_RESULT; } return PredicateDrivenCostEstimator.FILTER_RESULT; } catch (InvalidQueryException e) { throw new OntopiaRuntimeException(e); } } } // --- concat(string, string, string) ----------------------------------- class ConcatPredicate extends AbstractPredicate { public String getName() { return "concat"; } public String getSignature() { return "s s! s!"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int colix1 = matches.getIndex(arguments[0]); int colix2 = matches.getIndex(arguments[1]); int colix3 = matches.getIndex(arguments[2]); if (matches.bound(colix1)) return filter(matches, colix1, colix2, colix3); else return concat(matches, colix1, colix2, colix3); } private QueryMatches filter(QueryMatches matches, int ix1, int ix2, int ix3) { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types of objects if (!(matches.data[ix][ix1] instanceof String && matches.data[ix][ix2] instanceof String && matches.data[ix][ix3] instanceof String)) continue; // check value found against value given String str = (String) matches.data[ix][ix1]; String str2 = ((String) matches.data[ix][ix2]) + matches.data[ix][ix3]; if (str == null || !str.equals(str2)) continue; // ok, add match if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = matches.data[ix]; } return result; } private QueryMatches concat(QueryMatches matches, int ix1, int ix2, int ix3) { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { if (!(matches.data[ix][ix2] instanceof String && matches.data[ix][ix3] instanceof String)) continue; Object[] newRow = (Object[]) matches.data[ix].clone(); newRow[ix1] = ((String) newRow[ix2]) + newRow[ix3]; if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = newRow; } return result; } } // --- starts-with(string, string) -------------------------------------- // needs to be public so QueryOptimizer can access it public class StartsWithPredicate extends AbstractPredicate { public String getName() { return "starts-with"; } public String getSignature() { return "s! s!"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int colix1 = matches.getIndex(arguments[0]); int colix2 = matches.getIndex(arguments[1]); return PredicateUtils.filter(matches, colix1, colix2, String.class, String.class, PredicateUtils.FILTER_STR_STARTS_WITH); } } // --- ends-with(string, string) -------------------------------------- public class EndsWithPredicate extends AbstractPredicate { public String getName() { return "ends-with"; } public String getSignature() { return "s! s!"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int colix1 = matches.getIndex(arguments[0]); int colix2 = matches.getIndex(arguments[1]); return PredicateUtils.filter(matches, colix1, colix2, String.class, String.class, PredicateUtils.FILTER_STR_ENDS_WITH); } } // --- contains(string, string) ----------------------------------------- class ContainsPredicate extends AbstractPredicate { public String getName() { return "contains"; } public String getSignature() { return "s! s!"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int colix1 = matches.getIndex(arguments[0]); int colix2 = matches.getIndex(arguments[1]); return PredicateUtils.filter(matches, colix1, colix2, String.class, String.class, PredicateUtils.FILTER_STR_CONTAINS); } } // --- length(string, number) ------------------------------------------- class LengthPredicate extends AbstractPredicate { public String getName() { return "length"; } public String getSignature() { return "s! i"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int strix = matches.getIndex(arguments[0]); int numix = matches.getIndex(arguments[1]); if (matches.bound(numix)) return PredicateUtils.filter(matches, strix, numix, String.class, Number.class, PredicateUtils.FILTER_STR_LENGTH); else return PredicateUtils.objectToOne(matches, strix, numix, String.class, PredicateUtils.STR_TO_LENGTH); } } // --- translate(string, string, string, string) ------------------------ class TranslatePredicate extends AbstractPredicate { public String getName() { return "translate"; } public String getSignature() { return "s s! s! s! s!?"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int str1ix = matches.getIndex(arguments[0]); // output int str2ix = matches.getIndex(arguments[1]); // input int str3ix = matches.getIndex(arguments[2]); // fromstr int str4ix = matches.getIndex(arguments[3]); // tostr int str5ix = -1; // deletestr if (arguments.length > 4) str5ix = matches.getIndex(arguments[4]); // FIXME: code assumes 2-3-4 are literals // create translation table TranslationTable table = new TranslationTable((String) arguments[2], (String) arguments[3], str5ix != -1 ? (String) arguments[4] : null); if (matches.bound(str1ix)) return filter(matches, str1ix, str2ix, table); else return translate(matches, str1ix, str2ix, table); } private QueryMatches filter(QueryMatches matches, int ix1, int ix2, TranslationTable table) { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types of objects if (!(matches.data[ix][ix1] instanceof String && matches.data[ix][ix2] instanceof String)) continue; // check value found against value given String str = (String) matches.data[ix][ix1]; String str2 = table.translate((String) matches.data[ix][ix2]); if (str == null || !str.equals(str2)) continue; // ok, add match if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = matches.data[ix]; } return result; } private QueryMatches translate(QueryMatches matches, int ix1, int ix2, TranslationTable table) { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { if (!(matches.data[ix][ix2] instanceof String)) continue; Object[] newRow = (Object[]) matches.data[ix].clone(); newRow[ix1] = table.translate((String) newRow[ix2]); if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = newRow; } return result; } class TranslationTable { public char[] conversion; public char lowest; public char highest; public String delete; private static final char DELETE = 0; public TranslationTable(String from, String to, String delete) { this.delete = delete; // TODO: consider array for binary search instead // find character range lowest = 0xFFFF; highest = 0; for (int ix = 0; ix < from.length(); ix++) { char ch = from.charAt(ix); if (ch < lowest) lowest = ch; if (ch > highest) highest = ch; } // make conversion table conversion = new char[(highest - lowest) + 1]; if (delete != null) { for (int ix = lowest; ix < highest; ix++) if (delete.indexOf(ix) != -1) conversion[ix - lowest] = DELETE; else conversion[ix - lowest] = (char) ix; } for (int ix = 0; ix < from.length(); ix++) { char fch = from.charAt(ix); char tch = fch; if (ix < to.length()) // FIXME: report error instead? tch = to.charAt(ix); conversion[fch - lowest] = tch; } } public String translate(String input) { char[] buf = new char[input.length()]; int pos = 0; for (int ix = 0; ix < input.length(); ix++) { char ch = input.charAt(ix); if (ch < lowest || ch > highest) { // character outside table if (delete == null || // XPath behaviour: delete all unknowns delete.indexOf(ch) != -1) // Python behaviour: delete only listed ch = DELETE; } else // character in table ch = conversion[ch - lowest]; if (ch != DELETE) buf[pos++] = ch; } return new String(buf, 0, pos); } } } // --- substring(s, s, number, number?) --------------------------------- class SubstringPredicate extends AbstractPredicate { public String getName() { return "substring"; } public String getSignature() { return "s s! i! i!?"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int resix = matches.getIndex(arguments[0]); int strix = matches.getIndex(arguments[1]); int startix = matches.getIndex(arguments[2]); int endix = -1; if (arguments.length == 4) endix = matches.getIndex(arguments[3]); if (matches.bound(resix)) return filter(matches, resix, strix, startix, endix); else return substr(matches, resix, strix, startix, endix); } private QueryMatches filter(QueryMatches matches, int ix1, int ix2, int ix3, int ix4) throws InvalidQueryException { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types of objects if (!(matches.data[ix][ix1] instanceof String && matches.data[ix][ix2] instanceof String && matches.data[ix][ix3] instanceof Integer && (ix4 == -1 || matches.data[ix][ix4] instanceof Integer))) continue; // check value found against value given String str = (String) matches.data[ix][ix1]; String str2 = ((String) matches.data[ix][ix2]); Integer int1 = ((Integer) matches.data[ix][ix3]); String sub; if (ix4 == -1) sub = str2.substring(int1.intValue()); else { Integer int2 = ((Integer) matches.data[ix][ix4]); if (int2.compareTo(int1) < 0) throw new InvalidQueryException("The 3rd and 4th parameters to " + "the the '" + getName() + "' predicate must be in " + "increasing order."); sub = str2.substring(int1.intValue(), int2.intValue()); } if (str == null || !str.equals(sub)) continue; // ok, add match if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = matches.data[ix]; } return result; } private QueryMatches substr(QueryMatches matches, int ix1, int ix2, int ix3, int ix4) throws InvalidQueryException { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types if (!(matches.data[ix][ix2] instanceof String && matches.data[ix][ix3] instanceof Integer && (ix4 == -1 || matches.data[ix][ix4] instanceof Integer))) continue; Object[] newRow = (Object[]) matches.data[ix].clone(); String str = ((String) matches.data[ix][ix2]); Integer int1 = ((Integer) matches.data[ix][ix3]); String sub; if (ix4 == -1) sub = str.substring(int1.intValue()); else { Integer int2 = ((Integer) matches.data[ix][ix4]); if (int2.compareTo(int1) < 0) throw new InvalidQueryException("The 3rd and 4th parameters to " + "the the '" + getName() + "' predicate must be in " + "increasing order."); sub = str.substring(int1.intValue(), int2.intValue()); } newRow[ix1] = sub; if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = newRow; } return result; } } // --- substring-after(s, s, s) ----------------------------------------- class SubstringAfterPredicate extends AbstractPredicate { public String getName() { return "substring-after"; } public String getSignature() { return "s s! s!"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int resix = matches.getIndex(arguments[0]); int strix = matches.getIndex(arguments[1]); int subix = matches.getIndex(arguments[2]); if (matches.bound(resix)) return filter(matches, resix, strix, subix); else return substr(matches, resix, strix, subix); } private QueryMatches filter(QueryMatches matches, int resix, int strix, int subix) { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types of objects if (!(matches.data[ix][resix] instanceof String && matches.data[ix][strix] instanceof String && matches.data[ix][subix] instanceof String)) continue; // get set values String res = (String) matches.data[ix][resix]; String str = (String) matches.data[ix][strix]; String sub = (String) matches.data[ix][subix]; // verify int pos = str.indexOf(sub); if (pos == -1 || res == null || !res.equals(str.substring(pos + sub.length()))) continue; // ok, add match if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = matches.data[ix]; } return result; } private QueryMatches substr(QueryMatches matches, int resix, int strix, int subix) { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types of objects if (!(matches.data[ix][strix] instanceof String && matches.data[ix][subix] instanceof String)) continue; // get set values String str = (String) matches.data[ix][strix]; String sub = ((String) matches.data[ix][subix]); // compute int pos = str.indexOf(sub); if (pos == -1) continue; String res = str.substring(pos + sub.length()); // make new match Object[] newRow = (Object[]) matches.data[ix].clone(); newRow[resix] = res; if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = newRow; } return result; } } // --- substring-before(s, s, s) ----------------------------------------- class SubstringBeforePredicate extends AbstractPredicate { public String getName() { return "substring-before"; } public String getSignature() { return "s s! s!"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int resix = matches.getIndex(arguments[0]); int strix = matches.getIndex(arguments[1]); int subix = matches.getIndex(arguments[2]); if (matches.bound(resix)) return filter(matches, resix, strix, subix); else return substr(matches, resix, strix, subix); } private QueryMatches filter(QueryMatches matches, int resix, int strix, int subix) { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types of objects if (!(matches.data[ix][resix] instanceof String && matches.data[ix][strix] instanceof String && matches.data[ix][subix] instanceof String)) continue; // get set values String res = (String) matches.data[ix][resix]; String str = (String) matches.data[ix][strix]; String sub = (String) matches.data[ix][subix]; // verify int pos = str.indexOf(sub); if (pos == -1 || res == null || !res.equals(str.substring(0, pos))) continue; // ok, add match if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = matches.data[ix]; } return result; } private QueryMatches substr(QueryMatches matches, int resix, int strix, int subix) { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types of objects if (!(matches.data[ix][strix] instanceof String && matches.data[ix][subix] instanceof String)) continue; // get set values String str = (String) matches.data[ix][strix]; String sub = ((String) matches.data[ix][subix]); // compute int pos = str.indexOf(sub); if (pos == -1) continue; String res = str.substring(0, pos); // make new match Object[] newRow = (Object[]) matches.data[ix].clone(); newRow[resix] = res; if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = newRow; } return result; } } // --- last-index-of(n, s!, s!) ----------------------------------------- class LastIndexOfPredicate extends AbstractPredicate { public String getName() { return "last-index-of"; } public String getSignature() { return "i s! s!"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int indexix = matches.getIndex(arguments[0]); int sourceix = matches.getIndex(arguments[1]); int targetix = matches.getIndex(arguments[2]); if (matches.bound(indexix)) return filter(matches, indexix, sourceix, targetix); else return index(matches, indexix, sourceix, targetix); } private QueryMatches filter(QueryMatches matches, int ix1, int ix2, int ix3) throws InvalidQueryException { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types of objects if (!(matches.data[ix][ix1] instanceof Integer && matches.data[ix][ix2] instanceof String && matches.data[ix][ix3] instanceof String)) continue; Integer int1 = ((Integer) matches.data[ix][ix1]); String str1 = (String) matches.data[ix][ix2]; String str2 = ((String) matches.data[ix][ix3]); int index = str1.lastIndexOf(str2); // If the predicate can't be found, this is not a match // check value found against value given if (index == -1 || int1.intValue() != index) continue; // ok, add match if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = matches.data[ix]; } return result; } private QueryMatches index(QueryMatches matches, int ix1, int ix2, int ix3) throws InvalidQueryException { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types if (!(matches.data[ix][ix2] instanceof String && matches.data[ix][ix3] instanceof String)) continue; Object[] newRow = (Object[]) matches.data[ix].clone(); String str1 = ((String) matches.data[ix][ix2]); String str2 = ((String) matches.data[ix][ix3]); int index = str1.lastIndexOf(str2); // If the predicate can't be found, this is not a match // check value found against value given if (index == -1) continue; newRow[ix1] = new Integer(index); if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = newRow; } return result; } } // --- index-of(n, s!, s!) ----------------------------------------- class IndexOfPredicate extends AbstractPredicate { public String getName() { return "index-of"; } public String getSignature() { return "i s! s!"; } public QueryMatches satisfy(QueryMatches matches, Object[] arguments) throws InvalidQueryException { PredicateSignature sign = PredicateSignature.getSignature(this); sign.verifyBound(matches, arguments, this); int indexix = matches.getIndex(arguments[0]); int sourceix = matches.getIndex(arguments[1]); int targetix = matches.getIndex(arguments[2]); if (matches.bound(indexix)) return filter(matches, indexix, sourceix, targetix); else return index(matches, indexix, sourceix, targetix); } private QueryMatches filter(QueryMatches matches, int ix1, int ix2, int ix3) throws InvalidQueryException { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types of objects if (!(matches.data[ix][ix1] instanceof Integer && matches.data[ix][ix2] instanceof String && matches.data[ix][ix3] instanceof String)) continue; Integer int1 = ((Integer) matches.data[ix][ix1]); String str1 = (String) matches.data[ix][ix2]; String str2 = ((String) matches.data[ix][ix3]); int index = str1.indexOf(str2); // If the predicate can't be found, this is not a match // check value found against value given if (index == -1 || int1.intValue() != index) continue; // ok, add match if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = matches.data[ix]; } return result; } private QueryMatches index(QueryMatches matches, int ix1, int ix2, int ix3) throws InvalidQueryException { QueryMatches result = new QueryMatches(matches); for (int ix = 0; ix <= matches.last; ix++) { // verify types if (!(matches.data[ix][ix2] instanceof String && matches.data[ix][ix3] instanceof String)) continue; Object[] newRow = (Object[]) matches.data[ix].clone(); String str1 = ((String) matches.data[ix][ix2]); String str2 = ((String) matches.data[ix][ix3]); int index = str1.indexOf(str2); // If the predicate can't be found, this is not a match // check value found against value given if (index == -1) continue; newRow[ix1] = new Integer(index); if (result.last + 1 == result.size) result.increaseCapacity(); result.last++; result.data[result.last] = newRow; } return result; } } }