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