Remove CVS expansions
[cql-java-moved-to-github.git] / src / main / java / org / z3950 / zing / cql / CQLNode.java
1
2 package org.z3950.zing.cql;
3 import java.util.HashMap;
4 import java.util.List;
5 import java.util.Map;
6 import java.util.Properties;
7
8
9
10 /**
11  * Represents a node in a CQL parse-tree.
12  *
13  */
14 public abstract class CQLNode {
15
16     /**
17      * Returns the name of the result-set to which this query is a
18      * reference, if and only if the entire query consists only of a
19      * result-set reference.  If it's anything else, including a
20      * boolean combination of a result-set reference with something
21      * else, then null is returned instead.
22      * @return the name of the referenced result-set
23      */
24     public String getResultSetName() {
25         return null;
26     }
27
28     /**
29      * Translates a parse-tree into an XCQL document.
30      * <P>
31      * @return
32      *  A String containing an XCQL document equivalent to the
33      *  parse-tree whose root is this node.
34      */
35     public String toXCQL() {
36         StringBuilder sb = new StringBuilder();
37         toXCQLInternal(new XCQLBuilder(sb), 0);
38         return sb.toString();
39     }
40
41     void toXCQLInternal(XCQLBuilder b, int level) {
42         toXCQLInternal(b, level, null, null);
43     }
44
45     abstract void toXCQLInternal(XCQLBuilder b, int level,
46       List<CQLPrefix> prefixes, List<ModifierSet> sortkeys);
47
48     static void renderPrefixes(XCQLBuilder b,
49         int level, List<CQLPrefix> prefixes) {
50         if (prefixes == null || prefixes.size() == 0)
51             return;
52         b.indent(level).append("<prefixes>\n");
53         for (int i = 0; i < prefixes.size(); i++) {
54             CQLPrefix p = prefixes.get(i);
55             b.indent(level+1).append("<prefix>\n");
56             if (p.name != null)
57                 b.indent(level + 2).append("<name>").
58                     append(p.name).append("</name>\n");
59             b.indent(level + 2).append("<identifier>").
60                 append(p.identifier).append("</identifier>\n");
61             b.indent(level+1).append("</prefix>\n");
62         }
63         b.indent(level).append("</prefixes>\n");
64     }
65
66     static void renderSortKeys(XCQLBuilder b, int level,
67                                            List<ModifierSet> sortkeys) {
68         if (sortkeys == null || sortkeys.size() == 0)
69             return;
70         b.indent(level).append("<sortKeys>\n");
71         for (int i = 0; i < sortkeys.size(); i++) {
72             ModifierSet key = sortkeys.get(i);
73             key.toXCQLInternal(b, level+1, "key", "index");
74         }
75         b.indent(level).append("</sortKeys>\n");
76     }
77
78     /**
79      * Decompiles a parse-tree into a CQL query.
80      * <P>
81      * @return
82      *  A String containing a CQL query equivalent to the parse-tree
83      *  whose root is this node, so that compiling that query will
84      *  yield an identical tree.
85      */
86     abstract public String toCQL();
87
88     /**
89      * Renders a parse-tree into a Yaz-style PQF string.
90      * PQF, or Prefix Query Format, is a cryptic but powerful notation
91      * that can be trivially mapped, one-to-one, int Z39.50 Type-1 and
92      * Type-101 queries.  A specification for the format can be found
93      * in
94      * <A href="http://indexdata.dk/yaz/doc/tools.php#PQF"
95      *  >Chapter 7 (Supporting Tools)</A> of the
96      * <A href="http://indexdata.dk/yaz/">YAZ</A> manual.
97      * <P>
98      * @param config
99      *  A <TT>Properties</TT> object containing configuration
100      *  information that specifies the mapping from CQL indexes,
101      *  relations, etc. to Type-1 attributes.  The mapping
102      *  specification is described in the CQL-Java distribution's
103      *  sample PQF-mapping configuration file,
104      *  <TT>etc/pqf.properties</TT>, which see.
105      * @return
106      *  A String containing a PQF query equivalent to the parse-tree
107      *  whose root is this node.
108      */
109     abstract public String toPQF(Properties config)
110         throws PQFTranslationException;
111
112     /**
113      * Renders a parser-tree into a BER-endoded packet representing an
114      * equivalent Z39.50 Type-1 query.  If you don't know what that
115      * means, then you don't need this method :-)  This is useful
116      * primarily for SRW-to-Z39.50 gateways.
117      *
118      * @param config
119      *  A <TT>Properties</TT> object containing configuration
120      *  information that specifies the mapping from CQL indexes,
121      *  relations, etc. to Type-1 attributes.  The mapping
122      *  specification is described in the CQL-Java distribution's
123      *  sample PQF-mapping configuration file,
124      *  <TT>etc/pqf.properties</TT>, which see.
125      * @return
126      *  A byte array containing the BER packet.
127      * @see
128      *  <A href="ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc"
129      *          >ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc</A>
130      */
131     abstract public byte[] toType1BER(Properties config)
132         throws PQFTranslationException;
133
134     // ANS.1 classes
135     protected static final int UNIVERSAL   = 0;
136     protected static final int APPLICATION = 1;
137     protected static final int CONTEXT     = 2;
138     protected static final int PRIVATE     = 3;
139
140     // ASN.1 tag forms
141     protected static final int PRIMITIVE   = 0;
142     protected static final int CONSTRUCTED = 1;
143
144     // ASN.1 UNIVERSAL data types
145     public static final byte BOOLEAN          =  1;
146     public static final byte INTEGER          =  2;
147     public static final byte BITSTRING        =  3;
148     public static final byte OCTETSTRING      =  4;
149     public static final byte NULL             =  5;
150     public static final byte OBJECTIDENTIFIER =  6;
151     public static final byte OBJECTDESCRIPTOR =  7;
152     public static final byte EXTERNAL         =  8;
153     public static final byte ENUMERATED       = 10;
154     public static final byte SEQUENCE         = 16;
155     public static final byte SET              = 17;
156     public static final byte VISIBLESTRING    = 26;
157     public static final byte GENERALSTRING    = 27;
158
159     protected static final int putTag(int asn1class, int fldid, int form,
160                                       byte[] record, int offset) {
161         if (fldid < 31)
162             record[offset++] = (byte)(fldid + asn1class*64 + form*32);
163         else {
164             record[offset++] = (byte)(31 + asn1class*64 + form*32);
165             if (fldid < 128)
166                 record[offset++] = (byte)(fldid);
167             else {
168                 record[offset++] = (byte)(128 + fldid/128);
169                 record[offset++] = (byte)(fldid % 128);
170             }
171         }
172         return offset;
173     }
174
175     /**
176      * Put a length directly into a BER record.
177      *
178      * @param len length to put into record
179      * @return the new, incremented value of the offset parameter.
180      */
181     static final int putLen(int len, byte[] record, int offset) {
182
183         if (len < 128)
184             record[offset++] = (byte)len;
185         else {
186             int t;
187             record[offset] = (byte)(lenLen(len) - 1);
188             for (t = record[offset]; t > 0; t--) {
189                 record[offset+t] = (byte)(len & 0xff);
190                 len >>= 8;
191             }
192             t = offset;
193             offset += (record[offset]&0xff) + 1;
194             record[t] += 128; // turn on bit 8 in length byte.
195         }
196         return offset;
197     }
198
199     /**
200      * Get the length needed to represent the given length.
201      *
202      * @param length determine length needed to encode this
203      * @return length needed to encode given length
204      */
205     protected // ### shouldn't this be private?
206         static final int lenLen(int length) {
207
208         return ((length < 128) ? 1 :
209             (length < 256) ? 2 :
210                 (length < 65536L) ? 3 : 4);
211     }
212
213     /**
214      * Get the length needed to represent the given number.
215      *
216      * @param num determine length needed to encode this
217      * @return length needed to encode given number
218      */
219     protected static final int numLen(long num) {
220         num = num < 0 ? -num : num;
221         // ### Wouldn't this be better done algorithmically?
222         // Or at least with the constants expressed in hex?
223         return ((num < 128) ? 1 :
224             (num < 32768) ? 2 :
225                 (num < 8388608) ? 3 :
226                     (num < 2147483648L) ? 4 :
227                         (num < 549755813888L) ? 5 :
228                             (num < 140737488355328L) ? 6 :
229                                 (num < 36028797018963968L) ? 7 : 8);
230     }
231
232     /**
233      * Put a number into a given buffer
234      *
235      * @param num number to put into buffer
236      * @param record buffer to use
237      * @param offset offset into buffer
238      * @return the new, incremented value of the offset parameter.
239      */
240     protected static final int putNum(long num, byte record[], int offset) {
241         int cnt=numLen(num);
242
243         for (int count = cnt - 1; count >= 0; count--) {
244             record[offset+count] = (byte)(num & 0xff);
245             num >>= 8;
246         }
247         return offset+cnt;
248     }
249
250     // Used only by the makeOID() method
251     private static final Map<String, byte[]> madeOIDs =
252         new HashMap<String, byte[]>(10);
253
254     protected static final byte[] makeOID(String oid) {
255         byte[] o;
256         int dot, offset = 0, oidOffset = 0, value;
257
258         if ((o = (byte[])madeOIDs.get(oid)) == null) {
259             o = new byte[100];
260
261             // Isn't this kind of thing excruciating in Java?
262             while (oidOffset < oid.length() &&
263               Character.isDigit(oid.charAt(oidOffset)) == true) {
264                 if (offset > 90) // too large
265                     return null;
266
267                 dot = oid.indexOf('.', oidOffset);
268                 if (dot == -1)
269                     dot = oid.length();
270
271                 value = Integer.parseInt(oid.substring(oidOffset, dot));
272
273                 if (offset == 0) {  // 1st two are special
274                     if (dot == -1) // ### can't happen: -1 is reassigned above
275                         return null; // can't be this short
276                     oidOffset = dot+1; // skip past '.'
277
278                     dot = oid.indexOf('.', oidOffset);
279                     if (dot == -1)
280                         dot = oid.length();
281
282                     // ### Eh?!
283                     value = value * 40 +
284                         Integer.parseInt(oid.substring(oidOffset,dot));
285                 }
286
287                 if (value < 0x80) {
288                     o[offset++] = (byte)value;
289                 } else {
290                     int count = 0;
291                     byte bits[] = new byte[12]; // save a 84 (12*7) bit number
292
293                     while (value != 0) {
294                         bits[count++] = (byte)(value & 0x7f);
295                         value >>= 7;
296                     }
297
298                     // Now place in the correct order
299                     while (--count > 0)
300                         o[offset++] = (byte)(bits[count] | 0x80);
301
302                     o[offset++] = bits[count];
303                 }
304
305                 dot = oid.indexOf('.', oidOffset);
306                 if (dot == -1)
307                     break;
308
309                 oidOffset = dot+1;
310             }
311
312             byte[] ptr = new byte[offset];
313             System.arraycopy(o, 0, ptr, 0, offset);
314             madeOIDs.put(oid, ptr);
315             return ptr;
316         }
317         return o;
318     }
319
320     public static final byte[] makeQuery(CQLNode root, Properties properties)
321         throws PQFTranslationException {
322         byte[] rpnStructure = root.toType1BER(properties);
323         byte[] qry = new byte[rpnStructure.length+100];
324         int offset = 0;
325         offset = putTag(CONTEXT, 1, CONSTRUCTED, qry, offset);
326         qry[offset++] = (byte)(0x80&0xff);  // indefinite length
327         offset = putTag(UNIVERSAL, OBJECTIDENTIFIER, PRIMITIVE, qry, offset);
328         byte[] oid = makeOID("1.2.840.10003.3.1"); // bib-1
329         offset = putLen(oid.length, qry, offset);
330         System.arraycopy(oid, 0, qry, offset, oid.length);
331         offset += oid.length;
332         System.arraycopy(rpnStructure, 0, qry, offset, rpnStructure.length);
333         offset += rpnStructure.length;
334         qry[offset++] = 0x00;  // end of query
335         qry[offset++] = 0x00;
336         byte[] q = new byte[offset];
337         System.arraycopy(qry, 0, q, 0, offset);
338         return q;
339     }
340 }