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