]> gerrit.simantics Code Review - simantics/platform.git/blob - 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
1 /*******************************************************************************\r
2  * Copyright (c) 2014, 2015 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     Semantum Oy - initial API and implementation\r
11  *     Semantum Oy - improvements for Simantics issue #6053\r
12  *******************************************************************************/\r
13 package org.simantics.db.indexing;\r
14 \r
15 import java.io.Reader;\r
16 import java.util.HashMap;\r
17 import java.util.Map;\r
18 import java.util.concurrent.atomic.AtomicReference;\r
19 \r
20 import org.apache.lucene.analysis.Analyzer;\r
21 import org.apache.lucene.analysis.Tokenizer;\r
22 import org.apache.lucene.analysis.core.KeywordAnalyzer;\r
23 import org.apache.lucene.analysis.core.WhitespaceAnalyzer;\r
24 import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper;\r
25 import org.apache.lucene.analysis.util.CharTokenizer;\r
26 import org.apache.lucene.queryparser.classic.ParseException;\r
27 import org.apache.lucene.queryparser.classic.QueryParser;\r
28 import org.apache.lucene.search.NumericRangeQuery;\r
29 import org.apache.lucene.search.Query;\r
30 import org.apache.lucene.util.AttributeFactory;\r
31 import org.apache.lucene.util.Version;\r
32 import org.simantics.databoard.util.ObjectUtils;\r
33 import org.simantics.utils.datastructures.Pair;\r
34 \r
35 public class Queries {\r
36 \r
37     static final class LowerCaseWhitespaceTokenizer extends CharTokenizer {\r
38         /**\r
39          * Construct a new WhitespaceTokenizer. * @param matchVersion Lucene version\r
40          * to match See {@link <a href="#version">above</a>}\r
41          * \r
42          * @param in\r
43          *          the input to split up into tokens\r
44          */\r
45         public LowerCaseWhitespaceTokenizer(Version matchVersion, Reader in) {\r
46             super(matchVersion, in);\r
47         }\r
48 \r
49         /**\r
50          * Construct a new WhitespaceTokenizer using a given\r
51          * {@link org.apache.lucene.util.AttributeSource.AttributeFactory}.\r
52          *\r
53          * @param\r
54          *          matchVersion Lucene version to match See\r
55          *          {@link <a href="#version">above</a>}\r
56          * @param factory\r
57          *          the attribute factory to use for this {@link Tokenizer}\r
58          * @param in\r
59          *          the input to split up into tokens\r
60          */\r
61         public LowerCaseWhitespaceTokenizer(Version matchVersion, AttributeFactory factory, Reader in) {\r
62             super(matchVersion, factory, in);\r
63         }\r
64 \r
65         @Override\r
66         protected int normalize(int c) {\r
67             return Character.toLowerCase(c);\r
68         }\r
69 \r
70         protected boolean isTokenChar(int c) {\r
71             return !Character.isWhitespace(c);\r
72         }\r
73     }\r
74 \r
75     static final class LowerCaseWhitespaceAnalyzer extends Analyzer {\r
76 \r
77         private final Version matchVersion;\r
78 \r
79         /**\r
80          * Creates a new {@link WhitespaceAnalyzer}\r
81          * @param matchVersion Lucene version to match See {@link <a href="#version">above</a>}\r
82          */\r
83         public LowerCaseWhitespaceAnalyzer(Version matchVersion) {\r
84             this.matchVersion = matchVersion;\r
85         }\r
86 \r
87         @Override\r
88         protected TokenStreamComponents createComponents(final String fieldName, final Reader reader) {\r
89             return new TokenStreamComponents(new LowerCaseWhitespaceTokenizer(matchVersion, reader));\r
90         }\r
91     }\r
92 \r
93     private static AtomicReference<Pair<Query, String>> queryCache = new AtomicReference<>();\r
94 \r
95     final static PerFieldAnalyzerWrapper analyzer = createAnalyzer();\r
96     \r
97     static PerFieldAnalyzerWrapper createAnalyzer() {\r
98         \r
99         Map<String,Analyzer> analyzerPerField = new HashMap<>();\r
100         analyzerPerField.put("Model", new KeywordAnalyzer());\r
101         analyzerPerField.put("Parent", new KeywordAnalyzer());\r
102         analyzerPerField.put("Resource", new KeywordAnalyzer());\r
103         analyzerPerField.put("GUID", new KeywordAnalyzer());\r
104         \r
105         PerFieldAnalyzerWrapper analyzer = new PerFieldAnalyzerWrapper(new LowerCaseWhitespaceAnalyzer(Version.LUCENE_4_9), analyzerPerField);\r
106         return analyzer;\r
107         \r
108     }\r
109 \r
110     static PerFieldAnalyzerWrapper getAnalyzer() {\r
111         return analyzer;\r
112     }\r
113 \r
114     static Query parse(String search, IndexSchema schema) throws ParseException {\r
115         Pair<Query, String> cachedQuery = queryCache.get();\r
116         if (cachedQuery != null && search.equals(cachedQuery.second))\r
117             return cachedQuery.first;\r
118 \r
119         //System.err.println("parse " + search + " (cached=" + (cachedQuery != null ? cachedQuery.second : "null") + ")" );\r
120         CustomQueryParser parser = new CustomQueryParser(Version.LUCENE_4_9, "Name", getAnalyzer(), schema);\r
121         Query query = parser.parse(search);\r
122 \r
123         queryCache.set(Pair.make(query, search));\r
124         return query;\r
125     }\r
126 \r
127 \r
128     public static class CustomQueryParser extends QueryParser {\r
129 \r
130         protected final IndexSchema schema;\r
131 \r
132         public CustomQueryParser(Version version, String field, Analyzer analyzer, IndexSchema schema) {\r
133             super(version, field, analyzer);\r
134             this.schema = schema;\r
135             setAllowLeadingWildcard(true);\r
136         }\r
137 \r
138         @Override\r
139         protected Query getRangeQuery(\r
140                 String field,\r
141                 String part1,\r
142                 String part2,\r
143                 boolean startInclusive,\r
144                 boolean endInclusive) throws ParseException\r
145         {\r
146             IndexSchema.Type type = schema.typeMap.get(field);\r
147             if (IndexSchema.NUMERIC_TYPES.contains(type)) {\r
148                 boolean equalParts = ObjectUtils.objectEquals(part1, part2);\r
149                 try {\r
150                     switch (type) {\r
151                     case INT: {\r
152                         Integer min = part1 != null ? (                   Integer.valueOf(part1)) : null;\r
153                         Integer max = part2 != null ? (equalParts ? min : Integer.valueOf(part2)) : null;\r
154                         return NumericRangeQuery.newIntRange(field, min, max, startInclusive, endInclusive);\r
155                     }\r
156                     case LONG: {\r
157                         Long min = part1 != null ? (                   Long.valueOf(part1)) : null;\r
158                         Long max = part2 != null ? (equalParts ? min : Long.valueOf(part2)) : null;\r
159                         return NumericRangeQuery.newLongRange(field, min, max, startInclusive, endInclusive);\r
160                     }\r
161                     case FLOAT: {\r
162                         Float min = part1 != null ? (                   Float.valueOf(part1)) : null;\r
163                         Float max = part2 != null ? (equalParts ? min : Float.valueOf(part2)) : null;\r
164                         return NumericRangeQuery.newFloatRange(field, min, max, startInclusive, endInclusive);\r
165                     }\r
166                     case DOUBLE: {\r
167                         Double min = part1 != null ? (                   Double.valueOf(part1)) : null;\r
168                         Double max = part2 != null ? (equalParts ? min : Double.valueOf(part2)) : null;\r
169                         return NumericRangeQuery.newDoubleRange(field, min, max, startInclusive, endInclusive);\r
170                     }\r
171                     default:\r
172                         throw new ParseException("Unrecognized numeric field type '" + type + "' for field '" + field + "'"); \r
173                     }\r
174                 } catch (NumberFormatException e) {\r
175                     throw new ParseException(e.getMessage()); \r
176                 }\r
177             }\r
178             return super.getRangeQuery(field, part1, part2, startInclusive, endInclusive); \r
179         } \r
180 \r
181     }\r
182 \r
183 }\r