Added new field TypeId to dependency index for exact type searching
[simantics/platform.git] / bundles / org.simantics.db.layer0 / src / org / simantics / db / layer0 / genericrelation / IndexQueries.java
1 package org.simantics.db.layer0.genericrelation;
2
3 import java.util.Collection;
4
5 import org.simantics.datatypes.literal.GUID;
6 import org.simantics.db.Resource;
7
8 /**
9  * This class contains utilities related to queries made into Lucene indexes,
10  * such as escaping search terms.
11  * 
12  * @author Tuukka Lehtonen
13  */
14 public class IndexQueries {
15
16         /**
17          * Same as calling {@link #escape(String, boolean, boolean)} with
18          * escapeKeywords set to <code>true</code>.
19          * 
20          * @param s
21          * @param escapeWildcards
22          * @return escaped string
23          */
24         public static String escape(String s, boolean escapeWildcards) {
25                 return escape(s, escapeWildcards, true);
26         }
27
28         /**
29          * Returns a String where those characters that QueryParser expects to be
30          * escaped are escaped by a preceding <code>\</code>.
31          * 
32          * Copied from
33          * {@link org.apache.lucene.queryParser.QueryParser#escape(String)} but
34          * disabled escaping of wildcard characters '*' and '?'. Clients must escape
35          * wildcards themselves to allow use of wildcards in queries.
36          * 
37          * @param s
38          *            lucene query to escape
39          * @param escapeWildcards
40          *            <code>true</code> to escape also wildcard characters
41          * @param escapeKeywords
42          *            <code>true</code> to escape keywords like AND, OR, etc.
43          * @return escaped string
44          */
45         public static String escape(String s, boolean escapeWildcards, boolean escapeKeywords) {
46                 if (!needsEscaping(s, escapeWildcards, escapeKeywords))
47                         return s;
48
49                 StringBuilder sb = new StringBuilder(s.length() + 8);
50                 int len = s.length();
51                 // The beginning of the line is the same as the last character being
52                 // whitespace.
53                 boolean lastWhitespace = true;
54                 for (int i = 0; i < len;) {
55                         char c = s.charAt(i);
56                         // These characters are part of the query syntax and must be escaped
57                         if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == ':'
58                                         || c == '^' || c == '[' || c == ']' || c == '\"' || c == '{' || c == '}' || c == '~'
59                                         || c == '|' || c == '&' || c == '/' || c == ' ' || (escapeWildcards && (c == '*' || c == '?'))) {
60                                 sb.append('\\');
61                                 sb.append(c);
62                                 lastWhitespace = false;
63                         } else if (Character.isWhitespace(c)) {
64                                 sb.append(c);
65                                 lastWhitespace = true;
66                         } else {
67                                 if (escapeKeywords && lastWhitespace) {
68                                         int reslen = processReservedWords(s, i, sb);
69                                         if (reslen > 0) {
70                                                 i += reslen;
71                                                 lastWhitespace = false;
72                                                 continue;
73                                         }
74                                 }
75                                 sb.append(c);
76                                 lastWhitespace = false;
77                         }
78                         ++i;
79                 }
80                 return sb.toString();
81         }
82
83         /**
84          * Same logic as in {@link #escape(String, boolean, boolean)} but this one
85          * simply checks whether the input string needs escaping at all or not.
86          * 
87          * @param s
88          * @param escapeWildcards
89          * @param escapeKeywords
90          * @return
91          */
92         private static boolean needsEscaping(String s, boolean escapeWildcards, boolean escapeKeywords) {
93                 int len = s.length();
94                 // The beginning of the line is the same as the last character being
95                 // whitespace.
96                 boolean lastWhitespace = true;
97                 for (int i = 0; i < len;) {
98                         char c = s.charAt(i);
99                         // These characters are part of the query syntax and must be escaped
100                         if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == ':'
101                                         || c == '^' || c == '[' || c == ']' || c == '\"' || c == '{' || c == '}' || c == '~'
102                                         || c == '|' || c == '&' || c == '/' || c == ' ' || (escapeWildcards && (c == '*' || c == '?'))) {
103                                 return true;
104                         } else if (Character.isWhitespace(c)) {
105                                 lastWhitespace = true;
106                         } else {
107                                 if (escapeKeywords && lastWhitespace) {
108                                         int reslen = processReservedWords(s, i, null);
109                                         if (reslen > 0)
110                                                 return true;
111                                 }
112                                 lastWhitespace = false;
113                         }
114                         ++i;
115                 }
116                 return false;
117         }
118
119         private static final String[] RESERVED_WORDS = {
120                 "AND", "\\AND",
121                 "OR", "\\OR",
122                 "NOT", "\\NOT",
123         };
124
125         /**
126          * Lucene reserved words are case-sensitive for its query parser. Therefore
127          * only case-sensitive hits need to be looked for.
128          * 
129          * @param s
130          * @param fromIndex
131          * @return length of the reserved word in the input or 0 if no reserved word
132          *         in the input
133          */
134         private static int processReservedWords(String s, int fromIndex, StringBuilder sb) {
135                 final int total = RESERVED_WORDS.length;
136                 for (int w = 0; w < total; w += 2) {
137                         String word = RESERVED_WORDS[w];
138                         int len = word.length();
139                         if (s.regionMatches(false, fromIndex, word, 0, len)) {
140                                 if (sb != null) {
141                                         String replacement = RESERVED_WORDS[w+1];
142                                         sb.append(replacement);
143                                 }
144                                 return len;
145                         }
146                 }
147                 return 0;
148         }
149
150         /**
151          * Returns a String where those characters that QueryParser expects to be
152          * escaped are escaped by a preceding <code>\</code>.
153          */
154         public static String escape(String s) {
155                 return escape(s, false);
156         }
157
158         public static StringBuilder escapeTerm(String field, String term, boolean escapeWildcards, StringBuilder result) {
159                 if (field != null)
160                         result.append(field).append(':');
161                 result.append( escape(term, escapeWildcards) );
162                 return result;
163         }
164
165         public static String escapeTerm(String field, String term, boolean escapeWildcards) {
166                 return escapeTerm(field, term, escapeWildcards, new StringBuilder()).toString();
167         }
168
169         public static StringBuilder quoteTerm(String field, String term, StringBuilder result) {
170                 if (field != null)
171                         result.append(field).append(':');
172                 result.append("\"");
173                 result.append(term.replaceAll("(\"|\\\\)", "\\\\$0"));
174                 result.append("\"");
175                 return result;
176         }
177
178         public static String quoteTerm(String term) {
179                 return quoteTerm(null, term, new StringBuilder(term.length()*2)).toString();
180         }
181
182         public static String quoteTerm(String field, String term) {
183                 return quoteTerm(field, term,
184                                 new StringBuilder(
185                                                 term.length()*2
186                                                 + (field != null ? field.length() + 1 : 0))
187                                 ).toString();
188         }
189
190         public static StringBuilder appendLongTerm(StringBuilder sb, String field, long term) {
191                 return sb.append(field).append(':').append(term);
192         }
193
194         public static String longTerm(String field, long term) {
195                 return appendLongTerm(new StringBuilder(), field, term).toString();
196         }
197
198         public static StringBuilder appendResourceIdTerm(StringBuilder sb, String field, Resource term) {
199                 return appendLongTerm(sb, field, term.getResourceId());
200         }
201
202         public static String resourceIdTerm(String field, Resource term) {
203                 return appendLongTerm(new StringBuilder(), field, term.getResourceId()).toString();
204         }
205
206         private static String join(String withString, String... exps) {
207                 if (exps.length == 0)
208                         return "";
209                 StringBuilder sb = new StringBuilder(128);
210                 for (int i = 0; i < exps.length - 1; ++i) {
211                         sb.append(exps[i]).append(withString);
212                 }
213                 sb.append(exps[exps.length - 1]);
214                 return sb.toString();
215         }
216
217         public static String and(String exp1, String exp2) {
218                 return exp1 + " AND " + exp2;
219         }
220
221         public static String and(String... exps) {
222                 return join(" AND ", exps);
223         }
224
225         public static String or(String exp1, String exp2) {
226                 return exp1 + " OR " + exp2;
227         }
228
229         public static String or(String... exps) {
230                 return join(" OR ", exps);
231         }
232
233         public static String idFromGUID(GUID guid) {
234                 return guid != null ? guid.indexString() : "";
235         }
236
237         public static String toResourceIdString(Resource r, Collection<Resource> rs) {
238                 StringBuilder sb = new StringBuilder();
239                 sb.append(r.getResourceId());
240                 for (Resource rr : rs)
241                         sb.append(' ').append(rr.getResourceId());
242                 return sb.toString();
243         }
244
245         public static String toResourceIdString(Collection<Resource> rs) {
246                 if (rs.isEmpty())
247                         return "";
248                 StringBuilder sb = new StringBuilder();
249                 boolean first = true;
250                 for (Resource rr : rs) {
251                         if (!first)
252                                 sb.append(' ');
253                         first = false;
254                         sb.append(rr.getResourceId());
255                 }
256                 return sb.toString();
257         }
258
259 //      public static void main(String[] args) {
260 //              System.out.println("esc: " + escape("AND01", true, true));
261 //              System.out.println("esc: " + escape("AND 01", true, true));
262 //              System.out.println("esc: " + escape(" AND 01", true, true));
263 //      }
264
265 }