+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.browsing.ui.common.views;\r
+\r
+import java.nio.CharBuffer;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+\r
+/**\r
+ * Default implementation of IFilterStrategy.\r
+ * \r
+ * <p>\r
+ * It implements simple search semantics with only the special wildcard\r
+ * characters '*' ( 0 to n any characters) and '?' (any one character)\r
+ * recognized. In order to allow the filter to pass arbitrary prefixes, the\r
+ * client has to give a '*' prefix in the filter string. On the contrary, the\r
+ * client does not have to specify a '*' in order to pass arbitrary suffixes -\r
+ * arbitrary suffixes are allowed by default by this strategy.\r
+ * \r
+ * <p>\r
+ * This strategy forces the filter string to lowercase.\r
+ * \r
+ * TODO: implement case-insensitiveness properly, not by forcing the search string into lower case. Also Fix FilterSelectionRequestQueryProcessor after doing this.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class DefaultFilterStrategy implements IFilterStrategy {\r
+\r
+ private static final boolean DEBUG = false;\r
+\r
+ boolean implicitPreAsterisk = true;\r
+\r
+ public DefaultFilterStrategy() {\r
+ this(true);\r
+ }\r
+\r
+ public DefaultFilterStrategy(boolean implicitPreAsterisk) {\r
+ this.implicitPreAsterisk = implicitPreAsterisk;\r
+ }\r
+\r
+ private static StringBuilder addSearchWord(StringBuilder sb, String pattern) {\r
+ if (DEBUG)\r
+ System.out.println("addSearchWord(" + pattern + ") to '" + sb.toString() + "'");\r
+\r
+ if (pattern == null || pattern.isEmpty())\r
+ return sb;\r
+ if (sb.length() > 0)\r
+ sb.append('|');\r
+ sb.append('(');\r
+ sb.append(pattern);\r
+ sb.append(')');\r
+ return sb;\r
+ }\r
+\r
+ private static String toString(CharBuffer cb) {\r
+ cb.limit(cb.position());\r
+ cb.reset();\r
+ if (DEBUG)\r
+ System.out.println("toString(" + cb + ")");\r
+ String result = cb.toString();\r
+ cb.limit(cb.capacity());\r
+ return result;\r
+ }\r
+\r
+ public static String toSinglePatternString(String filter, boolean implicitPreAsterisk) {\r
+ if (!filter.isEmpty()) {\r
+ // Force searching in lowercase.\r
+ filter = filter.toLowerCase();\r
+\r
+ // Construct a regular expression from the specified text.\r
+ String regExFilter = filter\r
+ .replace("\\", "\\\\") // \ -> \\\r
+ .replace(".", "\\.") // . -> \.\r
+ .replace("*", ".*") // * -> Any 0..n characters\r
+ .replace("?", ".") // ? -> Any single character\r
+ .replace("+", "\\+") // + -> \+\r
+ .replace("(", "\\(") // ( -> \(\r
+ .replace(")", "\\)") // ) -> \)\r
+ .replace("[", "\\[") // [ -> \[\r
+ .replace("]", "\\]") // ] -> \]\r
+ .replace("{", "\\{") // { -> \{\r
+ .replace("}", "\\}") // } -> \}\r
+ .replace("^", "\\^") // ^ -> \^\r
+ .replace("$", "\\$") // $ -> \$\r
+ .replace("|", ".*|") // $ -> \$\r
+ //.replace("|", "\\|") // | -> \|\r
+ .replace("&&", "\\&&") // && -> \&&\r
+ ;\r
+\r
+ if (implicitPreAsterisk)\r
+ if (!regExFilter.startsWith(".*"))\r
+ regExFilter = ".*" + regExFilter ;\r
+ if (!regExFilter.endsWith(".*"))\r
+ regExFilter += ".*" ;\r
+\r
+ return regExFilter;\r
+ }\r
+ return null;\r
+ }\r
+\r
+ public static String defaultToPatternString(String filter, boolean implicitPreAsterisk) {\r
+ if (filter.isEmpty())\r
+ return null;\r
+\r
+ CharBuffer buf = CharBuffer.allocate(filter.length()*2);\r
+ buf.mark();\r
+ StringBuilder sb = new StringBuilder(filter.length()*2);\r
+ boolean inQuote = false;\r
+ int len = filter.length();\r
+ for (int i = 0; i < len;) {\r
+ char ch = filter.charAt(i);\r
+ if (DEBUG)\r
+ System.out.println("char[" + i + "]: '" + ch + "'");\r
+\r
+ if (ch == '"') {\r
+ if (!inQuote) {\r
+ if (DEBUG)\r
+ System.out.println("begin quoted text");\r
+ inQuote = true;\r
+ } else {\r
+ if (DEBUG)\r
+ System.out.println("end quoted text");\r
+ inQuote = false;\r
+ addSearchWord(sb, toSinglePatternString( toString(buf), implicitPreAsterisk ));\r
+ }\r
+ ++i;\r
+ continue;\r
+ } else if (ch == '\\') {\r
+ // Next character is escaped, i.e. taken as is.\r
+ ++i;\r
+ if (i >= len)\r
+ // Unexpected end-of-string\r
+ break;\r
+\r
+ ch = filter.charAt(i);\r
+ if (DEBUG)\r
+ System.out.println("append escaped character '" + ch + "'");\r
+\r
+ buf.append(ch);\r
+ ++i;\r
+ break;\r
+ } else if (ch == ' ') {\r
+ if (inQuote) {\r
+ if (DEBUG)\r
+ System.out.println("append char '" + ch + "'");\r
+ buf.append(ch);\r
+ ++i;\r
+ } else {\r
+ if (buf.position() > 0) {\r
+ addSearchWord(sb, toSinglePatternString( toString(buf), implicitPreAsterisk ));\r
+ }\r
+ ++i;\r
+ }\r
+ } else {\r
+ if (DEBUG)\r
+ System.out.println("append char '" + ch + "'");\r
+ buf.append(ch);\r
+ ++i;\r
+ }\r
+ }\r
+ if (buf.position() > 0) {\r
+ addSearchWord(sb, toSinglePatternString( toString(buf), implicitPreAsterisk ));\r
+ }\r
+\r
+ //sb.append(".*");\r
+\r
+ return sb.toString();\r
+ }\r
+\r
+ @Override\r
+ public String toPatternString(String filter) {\r
+ return defaultToPatternString(filter, implicitPreAsterisk);\r
+ }\r
+\r
+ public static Pattern compilePattern(String s) {\r
+ IFilterStrategy st = new DefaultFilterStrategy(true);\r
+ System.out.println("compilePattern(" + s + ")");\r
+ String regex = st.toPatternString(s);\r
+ System.out.println(s + " -> " + regex);\r
+ Pattern p = Pattern.compile(regex);\r
+ return p;\r
+ }\r
+\r
+ public static void test(String pattern, String... testStrings) {\r
+ Pattern p = compilePattern(pattern);\r
+ for (String test : testStrings) {\r
+ System.out.print("\ttesting '" + test + "'");\r
+ Matcher m = p.matcher(test);\r
+ if (m.matches()) {\r
+ System.out.println(" - MATCHES");\r
+ } else {\r
+ System.out.println(" - NO MATCH");\r
+ }\r
+ }\r
+ }\r
+\r
+ static String[] TEST = {\r
+ "foo bar baz biz boz",\r
+ "biz bar baz foo boz",\r
+ "foo",\r
+ "bar",\r
+ " foo bar ",\r
+ "quux",\r
+ " quux ",\r
+ " quux foo",\r
+ };\r
+\r
+ public static void main(String[] args) {\r
+ test("foo$");\r
+ test(".");\r
+ test("*");\r
+ test("?");\r
+ test("+");\r
+ test("^");\r
+ test("&");\r
+ test("&&");\r
+ test("&&&");\r
+ test("|");\r
+ test("(");\r
+ test(")");\r
+ test("[");\r
+ test("]");\r
+ test("{");\r
+ test("}");\r
+ test("()");\r
+ test("[]");\r
+ test("{}");\r
+ test("\\\\");\r
+ test("\\");\r
+\r
+ test("foo bar", TEST);\r
+ test("\"foo bar\"", TEST);\r
+ test("\"foo bar\" quux", TEST);\r
+ test("*\"foo bar\" *quux", TEST);\r
+ test("\"*foo bar\" *quux", TEST);\r
+ }\r
+\r
+}\r