]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.db.indexing/src/org/simantics/db/indexing/Queries.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.db.indexing / src / org / simantics / db / indexing / Queries.java
diff --git a/bundles/org.simantics.db.indexing/src/org/simantics/db/indexing/Queries.java b/bundles/org.simantics.db.indexing/src/org/simantics/db/indexing/Queries.java
new file mode 100644 (file)
index 0000000..5d071d3
--- /dev/null
@@ -0,0 +1,183 @@
+/*******************************************************************************\r
+ * Copyright (c) 2014, 2015 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
+ *     Semantum Oy - initial API and implementation\r
+ *     Semantum Oy - improvements for Simantics issue #6053\r
+ *******************************************************************************/\r
+package org.simantics.db.indexing;\r
+\r
+import java.io.Reader;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.concurrent.atomic.AtomicReference;\r
+\r
+import org.apache.lucene.analysis.Analyzer;\r
+import org.apache.lucene.analysis.Tokenizer;\r
+import org.apache.lucene.analysis.core.KeywordAnalyzer;\r
+import org.apache.lucene.analysis.core.WhitespaceAnalyzer;\r
+import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper;\r
+import org.apache.lucene.analysis.util.CharTokenizer;\r
+import org.apache.lucene.queryparser.classic.ParseException;\r
+import org.apache.lucene.queryparser.classic.QueryParser;\r
+import org.apache.lucene.search.NumericRangeQuery;\r
+import org.apache.lucene.search.Query;\r
+import org.apache.lucene.util.AttributeFactory;\r
+import org.apache.lucene.util.Version;\r
+import org.simantics.databoard.util.ObjectUtils;\r
+import org.simantics.utils.datastructures.Pair;\r
+\r
+public class Queries {\r
+\r
+    static final class LowerCaseWhitespaceTokenizer extends CharTokenizer {\r
+        /**\r
+         * Construct a new WhitespaceTokenizer. * @param matchVersion Lucene version\r
+         * to match See {@link <a href="#version">above</a>}\r
+         * \r
+         * @param in\r
+         *          the input to split up into tokens\r
+         */\r
+        public LowerCaseWhitespaceTokenizer(Version matchVersion, Reader in) {\r
+            super(matchVersion, in);\r
+        }\r
+\r
+        /**\r
+         * Construct a new WhitespaceTokenizer using a given\r
+         * {@link org.apache.lucene.util.AttributeSource.AttributeFactory}.\r
+         *\r
+         * @param\r
+         *          matchVersion Lucene version to match See\r
+         *          {@link <a href="#version">above</a>}\r
+         * @param factory\r
+         *          the attribute factory to use for this {@link Tokenizer}\r
+         * @param in\r
+         *          the input to split up into tokens\r
+         */\r
+        public LowerCaseWhitespaceTokenizer(Version matchVersion, AttributeFactory factory, Reader in) {\r
+            super(matchVersion, factory, in);\r
+        }\r
+\r
+        @Override\r
+        protected int normalize(int c) {\r
+            return Character.toLowerCase(c);\r
+        }\r
+\r
+        protected boolean isTokenChar(int c) {\r
+            return !Character.isWhitespace(c);\r
+        }\r
+    }\r
+\r
+    static final class LowerCaseWhitespaceAnalyzer extends Analyzer {\r
+\r
+        private final Version matchVersion;\r
+\r
+        /**\r
+         * Creates a new {@link WhitespaceAnalyzer}\r
+         * @param matchVersion Lucene version to match See {@link <a href="#version">above</a>}\r
+         */\r
+        public LowerCaseWhitespaceAnalyzer(Version matchVersion) {\r
+            this.matchVersion = matchVersion;\r
+        }\r
+\r
+        @Override\r
+        protected TokenStreamComponents createComponents(final String fieldName, final Reader reader) {\r
+            return new TokenStreamComponents(new LowerCaseWhitespaceTokenizer(matchVersion, reader));\r
+        }\r
+    }\r
+\r
+    private static AtomicReference<Pair<Query, String>> queryCache = new AtomicReference<>();\r
+\r
+    final static PerFieldAnalyzerWrapper analyzer = createAnalyzer();\r
+    \r
+    static PerFieldAnalyzerWrapper createAnalyzer() {\r
+       \r
+       Map<String,Analyzer> analyzerPerField = new HashMap<>();\r
+       analyzerPerField.put("Model", new KeywordAnalyzer());\r
+       analyzerPerField.put("Parent", new KeywordAnalyzer());\r
+       analyzerPerField.put("Resource", new KeywordAnalyzer());\r
+       analyzerPerField.put("GUID", new KeywordAnalyzer());\r
+       \r
+        PerFieldAnalyzerWrapper analyzer = new PerFieldAnalyzerWrapper(new LowerCaseWhitespaceAnalyzer(Version.LUCENE_4_9), analyzerPerField);\r
+        return analyzer;\r
+        \r
+    }\r
+\r
+    static PerFieldAnalyzerWrapper getAnalyzer() {\r
+       return analyzer;\r
+    }\r
+\r
+    static Query parse(String search, IndexSchema schema) throws ParseException {\r
+        Pair<Query, String> cachedQuery = queryCache.get();\r
+        if (cachedQuery != null && search.equals(cachedQuery.second))\r
+            return cachedQuery.first;\r
+\r
+        //System.err.println("parse " + search + " (cached=" + (cachedQuery != null ? cachedQuery.second : "null") + ")" );\r
+        CustomQueryParser parser = new CustomQueryParser(Version.LUCENE_4_9, "Name", getAnalyzer(), schema);\r
+        Query query = parser.parse(search);\r
+\r
+        queryCache.set(Pair.make(query, search));\r
+        return query;\r
+    }\r
+\r
+\r
+    public static class CustomQueryParser extends QueryParser {\r
+\r
+        protected final IndexSchema schema;\r
+\r
+        public CustomQueryParser(Version version, String field, Analyzer analyzer, IndexSchema schema) {\r
+            super(version, field, analyzer);\r
+            this.schema = schema;\r
+            setAllowLeadingWildcard(true);\r
+        }\r
+\r
+        @Override\r
+        protected Query getRangeQuery(\r
+                String field,\r
+                String part1,\r
+                String part2,\r
+                boolean startInclusive,\r
+                boolean endInclusive) throws ParseException\r
+        {\r
+            IndexSchema.Type type = schema.typeMap.get(field);\r
+            if (IndexSchema.NUMERIC_TYPES.contains(type)) {\r
+                boolean equalParts = ObjectUtils.objectEquals(part1, part2);\r
+                try {\r
+                    switch (type) {\r
+                    case INT: {\r
+                        Integer min = part1 != null ? (                   Integer.valueOf(part1)) : null;\r
+                        Integer max = part2 != null ? (equalParts ? min : Integer.valueOf(part2)) : null;\r
+                        return NumericRangeQuery.newIntRange(field, min, max, startInclusive, endInclusive);\r
+                    }\r
+                    case LONG: {\r
+                        Long min = part1 != null ? (                   Long.valueOf(part1)) : null;\r
+                        Long max = part2 != null ? (equalParts ? min : Long.valueOf(part2)) : null;\r
+                        return NumericRangeQuery.newLongRange(field, min, max, startInclusive, endInclusive);\r
+                    }\r
+                    case FLOAT: {\r
+                        Float min = part1 != null ? (                   Float.valueOf(part1)) : null;\r
+                        Float max = part2 != null ? (equalParts ? min : Float.valueOf(part2)) : null;\r
+                        return NumericRangeQuery.newFloatRange(field, min, max, startInclusive, endInclusive);\r
+                    }\r
+                    case DOUBLE: {\r
+                        Double min = part1 != null ? (                   Double.valueOf(part1)) : null;\r
+                        Double max = part2 != null ? (equalParts ? min : Double.valueOf(part2)) : null;\r
+                        return NumericRangeQuery.newDoubleRange(field, min, max, startInclusive, endInclusive);\r
+                    }\r
+                    default:\r
+                        throw new ParseException("Unrecognized numeric field type '" + type + "' for field '" + field + "'"); \r
+                    }\r
+                } catch (NumberFormatException e) {\r
+                    throw new ParseException(e.getMessage()); \r
+                }\r
+            }\r
+            return super.getRangeQuery(field, part1, part2, startInclusive, endInclusive); \r
+        } \r
+\r
+    }\r
+\r
+}\r