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