-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathCQLNode.java
More file actions
365 lines (320 loc) · 12.3 KB
/
CQLNode.java
File metadata and controls
365 lines (320 loc) · 12.3 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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
package org.z3950.zing.cql;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* Represents a node in a CQL parse-tree.
*
*/
public abstract class CQLNode {
private int start = -1, stop = -1;
public abstract void traverse(CQLNodeVisitor visitor);
/**
* Returns the name of the result-set to which this query is a
* reference, if and only if the entire query consists only of a
* result-set reference. If it's anything else, including a
* boolean combination of a result-set reference with something
* else, then null is returned instead.
* @return the name of the referenced result-set
*/
public String getResultSetName() {
return null;
}
public int getStart() {
return start;
}
public int getStop() {
return stop;
}
protected void setStartStop(int start, int stop) {
this.start = start;
this.stop = stop;
}
/**
* Translates a parse-tree into an XCQL document.
* @return
* A String containing an XCQL document equivalent to the
* parse-tree whose root is this node.
*/
public String toXCQL() {
StringBuilder sb = new StringBuilder();
toXCQLInternal(new XCQLBuilder(sb), 0);
return sb.toString();
}
void toXCQLInternal(XCQLBuilder b, int level) {
toXCQLInternal(b, level, null, null);
}
abstract void toXCQLInternal(XCQLBuilder b, int level,
List<CQLPrefix> prefixes, List<ModifierSet> sortkeys);
static void renderPrefixes(XCQLBuilder b,
int level, List<CQLPrefix> prefixes) {
if (prefixes == null || prefixes.size() == 0)
return;
b.indent(level).append("<prefixes>\n");
for (int i = 0; i < prefixes.size(); i++) {
CQLPrefix p = prefixes.get(i);
b.indent(level+1).append("<prefix>\n");
if (p.name != null)
b.indent(level + 2).append("<name>").
append(p.name).append("</name>\n");
b.indent(level + 2).append("<identifier>").
append(p.identifier).append("</identifier>\n");
b.indent(level+1).append("</prefix>\n");
}
b.indent(level).append("</prefixes>\n");
}
static void renderSortKeys(XCQLBuilder b, int level,
List<ModifierSet> sortkeys) {
if (sortkeys == null || sortkeys.size() == 0)
return;
b.indent(level).append("<sortKeys>\n");
for (int i = 0; i < sortkeys.size(); i++) {
ModifierSet key = sortkeys.get(i);
key.toXCQLInternal(b, level+1, "key", "index");
}
b.indent(level).append("</sortKeys>\n");
}
/**
* Decompiles a parse-tree into a CQL query.
* @return
* A String containing a CQL query equivalent to the parse-tree
* whose root is this node, so that compiling that query will
* yield an identical tree.
*/
abstract public String toCQL();
/**
* Renders a parse-tree into a Yaz-style PQF string.
* PQF, or Prefix Query Format, is a cryptic but powerful notation
* that can be trivially mapped, one-to-one, int Z39.50 Type-1 and
* Type-101 queries. A specification for the format can be found
* in
* <A href="http://indexdata.dk/yaz/doc/tools.php#PQF"
* >Chapter 7 (Supporting Tools)</A> of the
* <A href="http://indexdata.dk/yaz/">YAZ</A> manual.
* @param config
* A <code>Properties</code> object containing configuration
* information that specifies the mapping from CQL indexes,
* relations, etc. to Type-1 attributes. The mapping
* specification is described in the CQL-Java distribution's
* sample PQF-mapping configuration file,
* <code>etc/pqf.properties</code>, which see.
* @return
* A String containing a PQF query equivalent to the parse-tree
* whose root is this node.
* @throws PQFTranslationException
* If the parse-tree cannot be translated into PQF, for example
* because it contains a CQL relation that is not supported by the
* PQF configuration.
*/
abstract public String toPQF(Properties config)
throws PQFTranslationException;
/**
* Renders a parser-tree into a BER-endoded packet representing an
* equivalent Z39.50 Type-1 query. If you don't know what that
* means, then you don't need this method :-) This is useful
* primarily for SRW-to-Z39.50 gateways.
*
* @param config
* A <code>Properties</code> object containing configuration
* information that specifies the mapping from CQL indexes,
* relations, etc. to Type-1 attributes. The mapping
* specification is described in the CQL-Java distribution's
* sample PQF-mapping configuration file,
* <code>etc/pqf.properties</code>, which see.
* @return
* A byte array containing the BER packet.
* @see
* <A href="ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc"
* >ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc</A>
* @throws PQFTranslationException
* If the parse-tree cannot be translated into PQF, for example
* because it contains a CQL relation that is not supported by the
* PQF configuration.
*/
abstract public byte[] toType1BER(Properties config)
throws PQFTranslationException;
// ANS.1 classes
protected static final int UNIVERSAL = 0;
protected static final int APPLICATION = 1;
protected static final int CONTEXT = 2;
protected static final int PRIVATE = 3;
// ASN.1 tag forms
protected static final int PRIMITIVE = 0;
protected static final int CONSTRUCTED = 1;
// ASN.1 UNIVERSAL data types
public static final byte BOOLEAN = 1;
public static final byte INTEGER = 2;
public static final byte BITSTRING = 3;
public static final byte OCTETSTRING = 4;
public static final byte NULL = 5;
public static final byte OBJECTIDENTIFIER = 6;
public static final byte OBJECTDESCRIPTOR = 7;
public static final byte EXTERNAL = 8;
public static final byte ENUMERATED = 10;
public static final byte SEQUENCE = 16;
public static final byte SET = 17;
public static final byte VISIBLESTRING = 26;
public static final byte GENERALSTRING = 27;
protected static final int putTag(int asn1class, int fldid, int form,
byte[] record, int offset) {
if (fldid < 31)
record[offset++] = (byte)(fldid + asn1class*64 + form*32);
else {
record[offset++] = (byte)(31 + asn1class*64 + form*32);
if (fldid < 128)
record[offset++] = (byte)(fldid);
else {
record[offset++] = (byte)(128 + fldid/128);
record[offset++] = (byte)(fldid % 128);
}
}
return offset;
}
/**
* Put a length directly into a BER record.
*
* @param len length to put into record
* @return the new, incremented value of the offset parameter.
*/
static final int putLen(int len, byte[] record, int offset) {
if (len < 128)
record[offset++] = (byte)len;
else {
int t;
record[offset] = (byte)(lenLen(len) - 1);
for (t = record[offset]; t > 0; t--) {
record[offset+t] = (byte)(len & 0xff);
len >>= 8;
}
t = offset;
offset += (record[offset]&0xff) + 1;
record[t] += 128; // turn on bit 8 in length byte.
}
return offset;
}
/**
* Get the length needed to represent the given length.
*
* @param length determine length needed to encode this
* @return length needed to encode given length
*/
protected // ### shouldn't this be private?
static final int lenLen(int length) {
return ((length < 128) ? 1 :
(length < 256) ? 2 :
(length < 65536L) ? 3 : 4);
}
/**
* Get the length needed to represent the given number.
*
* @param num determine length needed to encode this
* @return length needed to encode given number
*/
protected static final int numLen(long num) {
num = num < 0 ? -num : num;
// ### Wouldn't this be better done algorithmically?
// Or at least with the constants expressed in hex?
return ((num < 128) ? 1 :
(num < 32768) ? 2 :
(num < 8388608) ? 3 :
(num < 2147483648L) ? 4 :
(num < 549755813888L) ? 5 :
(num < 140737488355328L) ? 6 :
(num < 36028797018963968L) ? 7 : 8);
}
/**
* Put a number into a given buffer
*
* @param num number to put into buffer
* @param record buffer to use
* @param offset offset into buffer
* @return the new, incremented value of the offset parameter.
*/
protected static final int putNum(long num, byte record[], int offset) {
int cnt=numLen(num);
for (int count = cnt - 1; count >= 0; count--) {
record[offset+count] = (byte)(num & 0xff);
num >>= 8;
}
return offset+cnt;
}
// Used only by the makeOID() method
private static final Map<String, byte[]> madeOIDs =
new HashMap<String, byte[]>(10);
protected static final byte[] makeOID(String oid) {
byte[] o;
int dot, offset = 0, oidOffset = 0, value;
if ((o = (byte[])madeOIDs.get(oid)) == null) {
o = new byte[100];
// Isn't this kind of thing excruciating in Java?
while (oidOffset < oid.length() &&
Character.isDigit(oid.charAt(oidOffset)) == true) {
if (offset > 90) // too large
return null;
dot = oid.indexOf('.', oidOffset);
if (dot == -1)
dot = oid.length();
value = Integer.parseInt(oid.substring(oidOffset, dot));
if (offset == 0) { // 1st two are special
if (dot == -1) // ### can't happen: -1 is reassigned above
return null; // can't be this short
oidOffset = dot+1; // skip past '.'
dot = oid.indexOf('.', oidOffset);
if (dot == -1)
dot = oid.length();
// ### Eh?!
value = value * 40 +
Integer.parseInt(oid.substring(oidOffset,dot));
}
if (value < 0x80) {
o[offset++] = (byte)value;
} else {
int count = 0;
byte bits[] = new byte[12]; // save a 84 (12*7) bit number
while (value != 0) {
bits[count++] = (byte)(value & 0x7f);
value >>= 7;
}
// Now place in the correct order
while (--count > 0)
o[offset++] = (byte)(bits[count] | 0x80);
o[offset++] = bits[count];
}
dot = oid.indexOf('.', oidOffset);
if (dot == -1)
break;
oidOffset = dot+1;
}
byte[] ptr = new byte[offset];
System.arraycopy(o, 0, ptr, 0, offset);
madeOIDs.put(oid, ptr);
return ptr;
}
return o;
}
public static final byte[] makeQuery(CQLNode root, Properties properties)
throws PQFTranslationException {
byte[] rpnStructure = root.toType1BER(properties);
byte[] qry = new byte[rpnStructure.length+100];
int offset = 0;
offset = putTag(CONTEXT, 1, CONSTRUCTED, qry, offset);
qry[offset++] = (byte)(0x80&0xff); // indefinite length
offset = putTag(UNIVERSAL, OBJECTIDENTIFIER, PRIMITIVE, qry, offset);
byte[] oid = makeOID("1.2.840.10003.3.1"); // bib-1
offset = putLen(oid.length, qry, offset);
System.arraycopy(oid, 0, qry, offset, oid.length);
offset += oid.length;
System.arraycopy(rpnStructure, 0, qry, offset, rpnStructure.length);
offset += rpnStructure.length;
qry[offset++] = 0x00; // end of query
qry[offset++] = 0x00;
byte[] q = new byte[offset];
System.arraycopy(qry, 0, q, 0, offset);
return q;
}
@Override
public String toString() {
return toCQL();
}
}