-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathCQLTermNode.java
More file actions
341 lines (304 loc) · 12.4 KB
/
CQLTermNode.java
File metadata and controls
341 lines (304 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
package org.z3950.zing.cql;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* Represents a terminal node in a CQL parse-tree.
* A term node consists of the term String itself, together with,
* optionally, an index string and a relation. Neither or both of
* these must be provided - you can't have an index without a
* relation or vice versa.
*
*/
public class CQLTermNode extends CQLNode {
private String index;
private CQLRelation relation;
private String term;
/**
* Creates a new term node with the specified <code>index</code>,
* <code>relation</code> and <code>term</code>. The first two may be
* <code>null</code>, but the <code>term</code> may not.
*/
public CQLTermNode(String index, CQLRelation relation, String term) {
this.index = index;
this.relation = relation;
this.term = term;
}
public String getIndex() {
return index;
}
public CQLRelation getRelation() {
return relation;
}
public String getTerm() {
return term;
}
private static boolean isResultSetIndex(String qual) {
return (qual.equals("srw.resultSet") ||
qual.equals("srw.resultSetId") ||
qual.equals("srw.resultSetName") ||
qual.equals("cql.resultSet") ||
qual.equals("cql.resultSetId") ||
qual.equals("cql.resultSetName"));
}
@Override
public void traverse(CQLNodeVisitor visitor) {
// we visit relation first to allow filtering on relation type in the visitor
relation.traverse(visitor);
visitor.onTermNode(this);
}
@Override
public String getResultSetName() {
if (isResultSetIndex(index))
return term;
else
return null;
}
@Override
void toXCQLInternal(XCQLBuilder b, int level, List<CQLPrefix> prefixes,
List<ModifierSet> sortkeys) {
b.indent(level).append("<searchClause>\n");
renderPrefixes(b, level + 1, prefixes);
b.indent(level + 1).append("<index>").xq(index).append("</index>\n");
relation.toXCQLInternal(b, level + 1);
b.indent(level + 1).append("<term>").xq(term).append("</term>\n");
renderSortKeys(b, level + 1, sortkeys);
b.indent(level).append("</searchClause>\n");
}
@Override
public String toCQL() {
String quotedIndex = toCQLTerm(index);
String quotedTerm = toCQLTerm(term);
String res = quotedTerm;
if (index != null &&
!index.equalsIgnoreCase("srw.serverChoice") &&
!index.equalsIgnoreCase("cql.serverChoice")) {
// ### We don't always need spaces around `relation'.
res = quotedIndex + " " + relation.toCQL() + " " + quotedTerm;
}
return res;
}
// ### Interaction between this and its callers is not good as
// regards truncation of the term and generation of truncation
// attributes. Change the interface to fix this.
private List<String> getAttrs(Properties config) throws PQFTranslationException {
List<String> attrs = new ArrayList<String>();
// Do this first so that if any other truncation or
// completeness attributes are generated, they "overwrite"
// those specified here.
//
// ### This approach relies on an unpleasant detail of Index
// Data's (admittedly definitive) implementation of PQF,
// and should not relied upon.
//
String attr = config.getProperty("always");
if (attr != null)
attrs.add(attr);
attr = config.getProperty("index." + index);
if (attr == null)
attr = config.getProperty("qualifier." + index);
if (attr == null)
throw new UnknownIndexException(index);
attrs.add(attr);
String rel = relation.getBase();
if (rel.equals("=")) {
rel = "eq";
} else if (rel.equals("==")) {
rel = "exact";
} else if (rel.equals("<=")) {
rel = "le";
} else if (rel.equals(">=")) {
rel = "ge";
}
// ### Handling "any" and "all" properly would involve breaking
// the string down into a bunch of individual words and ORring
// or ANDing them together. Another day.
attr = config.getProperty("relation." + rel);
if (attr == null)
throw new UnknownRelationException(rel);
attrs.add(attr);
List<Modifier> mods = relation.getModifiers();
for (int i = 0; i < mods.size(); i++) {
String type = mods.get(i).type;
attr = config.getProperty("relationModifier." + type);
if (attr == null)
throw new UnknownRelationModifierException(type);
attrs.add(attr);
}
String pos = "any";
String truncation = "none";
String text = term;
if (text.length() > 0 && text.substring(0, 1).equals("^")) {
text = text.substring(1); // ### change not seen by caller
pos = "first";
}
if (text.startsWith("*") && text.endsWith("*")) {
truncation = "both";
} else if (text.startsWith("*")) {
truncation = "left";
} else if (text.endsWith("*")) {
truncation = "right";
}
int len = text.length();
if (len > 0 && text.substring(len - 1, len).equals("^")) {
text = text.substring(0, len - 1); // ### change not seen by caller
pos = pos.equals("first") ? "firstAndLast" : "last";
// ### in the firstAndLast case, the standard
// pqf.properties file specifies that we generate a
// completeness=whole-field attribute, which means that
// we don't generate a position attribute at all. Do we
// care? Does it matter?
}
attr = config.getProperty("position." + pos);
if (attr == null)
throw new UnknownPositionException(pos);
attrs.add(attr);
attr = config.getProperty("truncation." + truncation);
if (attr == null)
throw new UnknownTruncationException(truncation);
attrs.add(attr);
attr = config.getProperty("structure." + rel);
if (attr == null)
attr = config.getProperty("structure.*");
attrs.add(attr);
return attrs;
}
@Override
public String toPQF(Properties config) throws PQFTranslationException {
if (isResultSetIndex(index)) {
// Special case: ignore relation, modifiers, wildcards, etc.
// There's parallel code in toType1BER()
return "@set " + toCQLTerm(term);
}
List<String> attrs = getAttrs(config);
String attr, s = "";
for (int i = 0; i < attrs.size(); i++) {
attr = (String) attrs.get(i);
s += "@attr " + attr.replace(" ", " @attr ") + " ";
}
String text = term;
if (text.length() > 0 && text.substring(0, 1).equals("^"))
text = text.substring(1);
int len = text.length();
if (len > 0 && text.substring(len - 1, len).equals("^"))
text = text.substring(0, len - 1);
len = text.length();
if (text.startsWith("*") && text.endsWith("*")) {
text = text.substring(1, len - 1);
} else if (text.startsWith("*")) {
text = text.substring(1);
} else if (text.endsWith("*")) {
text = text.substring(0, len - 1);
}
return s + toCQLTerm(text);
}
// ensure that a term is properly quoted for CQL output if necessary.
// If the term has a bare double-quote (") it will be
// escaped with a backslash.
static String toCQLTerm(String str) {
if (str == null) {
return null;
}
boolean quote = str.isEmpty();
boolean escaped = false;
StringBuilder sb = new StringBuilder();
for (char ch : str.toCharArray()) {
if (CQLLexer.OPS_AND_WHITESPACE.indexOf(ch) >= 0) {
quote = true;
}
if (ch == '"' && !escaped) {
sb.append('\\');
}
escaped = ch == '\\' && !escaped;
sb.append(ch);
}
if (escaped) {
// trailing backslash - escape it
sb.append('\\');
}
if (quote) {
return "\"" + sb.toString() + "\"";
} else {
return sb.toString();
}
}
@Override
public byte[] toType1BER(Properties config) throws PQFTranslationException {
if (isResultSetIndex(index)) {
// Special case: ignore relation, modifiers, wildcards, etc.
// There's parallel code in toPQF()
byte[] operand = new byte[term.length() + 100];
int offset;
offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
operand[offset++] = (byte) (0x80 & 0xff); // indefinite length
offset = putTag(CONTEXT, 31, PRIMITIVE, operand, offset); // ResultSetId
byte[] t = term.getBytes();
offset = putLen(t.length, operand, offset);
System.arraycopy(t, 0, operand, offset, t.length);
offset += t.length;
operand[offset++] = 0x00; // end of Operand
operand[offset++] = 0x00;
byte[] o = new byte[offset];
System.arraycopy(operand, 0, o, 0, offset);
return o;
}
String text = term;
if (text.length() > 0 && text.substring(0, 1).equals("^"))
text = text.substring(1);
int len = text.length();
if (len > 0 && text.substring(len - 1, len).equals("^"))
text = text.substring(0, len - 1);
String attr, attrList;
byte[] operand = new byte[text.length() + 100];
int i, j, offset, type, value;
offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
operand[offset++] = (byte) (0x80 & 0xff); // indefinite length
offset = putTag(CONTEXT, 102, CONSTRUCTED, operand, offset); // AttributesPlusTerm
operand[offset++] = (byte) (0x80 & 0xff); // indefinite length
offset = putTag(CONTEXT, 44, CONSTRUCTED, operand, offset); // AttributeList
operand[offset++] = (byte) (0x80 & 0xff); // indefinite length
List<String> attrs = getAttrs(config);
for (i = 0; i < attrs.size(); i++) {
attrList = (String) attrs.get(i);
java.util.StringTokenizer st = new java.util.StringTokenizer(attrList);
while (st.hasMoreTokens()) {
attr = st.nextToken();
j = attr.indexOf('=');
offset = putTag(UNIVERSAL, SEQUENCE, CONSTRUCTED, operand, offset);
operand[offset++] = (byte) (0x80 & 0xff);
offset = putTag(CONTEXT, 120, PRIMITIVE, operand, offset);
try {
type = Integer.parseInt(attr.substring(0, j));
} catch (NumberFormatException e) {
throw new PQFTranslationException("Bad attribute type: " + attr.substring(0, j));
}
offset = putLen(numLen(type), operand, offset);
offset = putNum(type, operand, offset);
offset = putTag(CONTEXT, 121, PRIMITIVE, operand, offset);
try {
value = Integer.parseInt(attr.substring(j + 1));
} catch (NumberFormatException e) {
throw new PQFTranslationException("Bad attribute value: " + attr.substring(j + 1));
}
offset = putLen(numLen(value), operand, offset);
offset = putNum(value, operand, offset);
operand[offset++] = 0x00; // end of SEQUENCE
operand[offset++] = 0x00;
}
}
operand[offset++] = 0x00; // end of AttributeList
operand[offset++] = 0x00;
offset = putTag(CONTEXT, 45, PRIMITIVE, operand, offset); // general Term
byte[] t = text.getBytes();
offset = putLen(t.length, operand, offset);
System.arraycopy(t, 0, operand, offset, t.length);
offset += t.length;
operand[offset++] = 0x00; // end of AttributesPlusTerm
operand[offset++] = 0x00;
operand[offset++] = 0x00; // end of Operand
operand[offset++] = 0x00;
byte[] o = new byte[offset];
System.arraycopy(operand, 0, o, 0, offset);
return o;
}
}