/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.browsing.ui.common.views; import java.nio.CharBuffer; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Default implementation of IFilterStrategy. * *

* It implements simple search semantics with only the special wildcard * characters '*' ( 0 to n any characters) and '?' (any one character) * recognized. In order to allow the filter to pass arbitrary prefixes, the * client has to give a '*' prefix in the filter string. On the contrary, the * client does not have to specify a '*' in order to pass arbitrary suffixes - * arbitrary suffixes are allowed by default by this strategy. * *

* This strategy forces the filter string to lowercase. * * TODO: implement case-insensitiveness properly, not by forcing the search string into lower case. Also Fix FilterSelectionRequestQueryProcessor after doing this. * * @author Tuukka Lehtonen */ public class DefaultFilterStrategy implements IFilterStrategy { private static final boolean DEBUG = false; boolean implicitPreAsterisk = true; public DefaultFilterStrategy() { this(true); } public DefaultFilterStrategy(boolean implicitPreAsterisk) { this.implicitPreAsterisk = implicitPreAsterisk; } private static StringBuilder addSearchWord(StringBuilder sb, String pattern) { if (DEBUG) System.out.println("addSearchWord(" + pattern + ") to '" + sb.toString() + "'"); if (pattern == null || pattern.isEmpty()) return sb; if (sb.length() > 0) sb.append('|'); sb.append('('); sb.append(pattern); sb.append(')'); return sb; } private static String toString(CharBuffer cb) { cb.limit(cb.position()); cb.reset(); if (DEBUG) System.out.println("toString(" + cb + ")"); String result = cb.toString(); cb.limit(cb.capacity()); return result; } public static String toSinglePatternString(String filter, boolean implicitPreAsterisk) { if (!filter.isEmpty()) { // Force searching in lowercase. filter = filter.toLowerCase(); // Construct a regular expression from the specified text. String regExFilter = filter .replace("\\", "\\\\") // \ -> \\ .replace(".", "\\.") // . -> \. .replace("*", ".*") // * -> Any 0..n characters .replace("?", ".") // ? -> Any single character .replace("+", "\\+") // + -> \+ .replace("(", "\\(") // ( -> \( .replace(")", "\\)") // ) -> \) .replace("[", "\\[") // [ -> \[ .replace("]", "\\]") // ] -> \] .replace("{", "\\{") // { -> \{ .replace("}", "\\}") // } -> \} .replace("^", "\\^") // ^ -> \^ .replace("$", "\\$") // $ -> \$ .replace("|", ".*|") // $ -> \$ //.replace("|", "\\|") // | -> \| .replace("&&", "\\&&") // && -> \&& ; if (implicitPreAsterisk) if (!regExFilter.startsWith(".*")) regExFilter = ".*" + regExFilter ; if (!regExFilter.endsWith(".*")) regExFilter += ".*" ; return regExFilter; } return null; } public static String defaultToPatternString(String filter, boolean implicitPreAsterisk) { if (filter.isEmpty()) return null; CharBuffer buf = CharBuffer.allocate(filter.length()*2); buf.mark(); StringBuilder sb = new StringBuilder(filter.length()*2); boolean inQuote = false; int len = filter.length(); for (int i = 0; i < len;) { char ch = filter.charAt(i); if (DEBUG) System.out.println("char[" + i + "]: '" + ch + "'"); if (ch == '"') { if (!inQuote) { if (DEBUG) System.out.println("begin quoted text"); inQuote = true; } else { if (DEBUG) System.out.println("end quoted text"); inQuote = false; addSearchWord(sb, toSinglePatternString( toString(buf), implicitPreAsterisk )); } ++i; continue; } else if (ch == '\\') { // Next character is escaped, i.e. taken as is. ++i; if (i >= len) // Unexpected end-of-string break; ch = filter.charAt(i); if (DEBUG) System.out.println("append escaped character '" + ch + "'"); buf.append(ch); ++i; break; } else if (ch == ' ') { if (inQuote) { if (DEBUG) System.out.println("append char '" + ch + "'"); buf.append(ch); ++i; } else { if (buf.position() > 0) { addSearchWord(sb, toSinglePatternString( toString(buf), implicitPreAsterisk )); } ++i; } } else { if (DEBUG) System.out.println("append char '" + ch + "'"); buf.append(ch); ++i; } } if (buf.position() > 0) { addSearchWord(sb, toSinglePatternString( toString(buf), implicitPreAsterisk )); } //sb.append(".*"); return sb.toString(); } @Override public String toPatternString(String filter) { return defaultToPatternString(filter, implicitPreAsterisk); } public static Pattern compilePattern(String s) { IFilterStrategy st = new DefaultFilterStrategy(true); System.out.println("compilePattern(" + s + ")"); String regex = st.toPatternString(s); System.out.println(s + " -> " + regex); Pattern p = Pattern.compile(regex); return p; } public static void test(String pattern, String... testStrings) { Pattern p = compilePattern(pattern); for (String test : testStrings) { System.out.print("\ttesting '" + test + "'"); Matcher m = p.matcher(test); if (m.matches()) { System.out.println(" - MATCHES"); } else { System.out.println(" - NO MATCH"); } } } static String[] TEST = { "foo bar baz biz boz", "biz bar baz foo boz", "foo", "bar", " foo bar ", "quux", " quux ", " quux foo", }; public static void main(String[] args) { test("foo$"); test("."); test("*"); test("?"); test("+"); test("^"); test("&"); test("&&"); test("&&&"); test("|"); test("("); test(")"); test("["); test("]"); test("{"); test("}"); test("()"); test("[]"); test("{}"); test("\\\\"); test("\\"); test("foo bar", TEST); test("\"foo bar\"", TEST); test("\"foo bar\" quux", TEST); test("*\"foo bar\" *quux", TEST); test("\"*foo bar\" *quux", TEST); } }