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