87511fca488ffdd03036af590264f35c43b836de
[cql-java-moved-to-github.git] / src / org / z3950 / zing / cql / CQLTermNode.java
1 // $Id: CQLTermNode.java,v 1.26 2007-06-27 22:44:40 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 terminal node in a CQL parse-tree.
10  * A term node consists of the term String itself, together with,
11  * optionally, an index string and a relation.  Neither or both of
12  * these must be provided - you can't have an index without a
13  * relation or vice versa.
14  *
15  * @version     $Id: CQLTermNode.java,v 1.26 2007-06-27 22:44:40 mike Exp $
16  */
17 public class CQLTermNode extends CQLNode {
18     private String index;
19     private CQLRelation relation;
20     private String term;
21
22     /**
23      * Creates a new term node with the specified <TT>index</TT>,
24      * <TT>relation</TT> and <TT>term</TT>.  The first two may be
25      * <TT>null</TT>, but the <TT>term</TT> may not.
26      */
27     public CQLTermNode(String index, CQLRelation relation, String term) {
28         this.index = index;
29         this.relation = relation;
30         this.term = term;
31     }
32
33     public String getIndex() { return index; }
34     public CQLRelation getRelation() { return relation; }
35     public String getTerm() { return term; }
36
37     private static boolean isResultSetIndex(String qual) {
38         return (qual.equals("srw.resultSet") ||
39                 qual.equals("srw.resultSetId") ||
40                 qual.equals("srw.resultSetName"));
41     }
42
43     public String getResultSetName() {
44         if (isResultSetIndex(index))
45             return term;
46         else
47             return null;
48     }
49
50     public String toXCQL(int level, Vector prefixes) {
51         return (indent(level) + "<searchClause>\n" +
52                 renderPrefixes(level+1, prefixes) +
53                 indent(level+1) + "<index>" + xq(index) + "</index>\n" +
54                 relation.toXCQL(level+1, new Vector<String>()) +
55                 indent(level+1) + "<term>" + xq(term) + "</term>\n" +
56                 indent(level) + "</searchClause>\n");
57     }
58
59     public String toCQL() {
60         String quotedIndex = maybeQuote(index);
61         String quotedTerm = maybeQuote(term);
62         String res = quotedTerm;
63
64         if (index != null &&
65             !index.equalsIgnoreCase("srw.serverChoice")) {
66             // ### We don't always need spaces around `relation'.
67             res = quotedIndex + " " + relation.toCQL() + " " + quotedTerm;
68         }
69
70         return res;
71     }
72
73     // ### Interaction between this and its callers is not good as
74     //  regards truncation of the term and generation of truncation
75     //  attributes.  Change the interface to fix this.
76     private Vector getAttrs(Properties config) throws PQFTranslationException {
77         Vector<String> attrs = new Vector<String>();
78
79         // Do this first so that if any other truncation or
80         // completeness attributes are generated, they "overwrite"
81         // those specified here.
82         //
83         //  ### This approach relies on an unpleasant detail of Index
84         //      Data's (admittedly definitive) implementation of PQF,
85         //      and should not relied upon.
86         //
87         String attr = config.getProperty("always");
88         if (attr != null)
89             attrs.add(attr);
90
91         attr = config.getProperty("index." + index);
92         if (attr == null)
93             throw new UnknownIndexException(index);
94         attrs.add(attr);
95
96         String rel = relation.getBase();
97         if (rel.equals("=")) {
98             rel = "eq";
99         } else if (rel.equals("<=")) {
100             rel = "le";
101         } else if (rel.equals(">=")) {
102             rel = "ge";
103         }
104         // ### Handling "any" and "all" properly would involve breaking
105         // the string down into a bunch of individual words and ORring
106         // or ANDing them together.  Another day.
107         attr = config.getProperty("relation." + rel);
108         if (attr == null)
109             throw new UnknownRelationException(rel);
110         attrs.add(attr);
111
112         Vector<Modifier> mods = relation.getModifiers();
113         for (int i = 0; i < mods.size(); i++) {
114             String type = mods.get(i).type;
115             attr = config.getProperty("relationModifier." + type);
116             if (attr == null)
117                 throw new UnknownRelationModifierException(type);
118             attrs.add(attr);
119         }
120
121         String pos = "any";
122         String text = term;
123         if (text.length() > 0 && text.substring(0, 1).equals("^")) {
124             text = text.substring(1); // ### change not seen by caller
125             pos = "first";
126         }
127         int len = text.length();
128         if (len > 0 && text.substring(len-1, len).equals("^")) {
129             text = text.substring(0, len-1); // ### change not seen by caller
130             pos = pos.equals("first") ? "firstAndLast" : "last";
131             // ### in the firstAndLast case, the standard
132             //  pqf.properties file specifies that we generate a
133             //  completeness=whole-field attributem, which means that
134             //  we don't generate a position attribute at all.  Do we
135             //  care?  Does it matter?
136         }
137
138         attr = config.getProperty("position." + pos);
139         if (attr == null)
140             throw new UnknownPositionException(pos);
141         attrs.add(attr);
142
143         attr = config.getProperty("structure." + rel);
144         if (attr == null)
145             attr = config.getProperty("structure.*");
146         attrs.add(attr);
147
148         return attrs;
149     }
150
151     public String toPQF(Properties config) throws PQFTranslationException {
152         if (isResultSetIndex(index)) {
153             // Special case: ignore relation, modifiers, wildcards, etc.
154             // There's parallel code in toType1BER()
155             return "@set " + maybeQuote(term);
156         }
157
158         Vector attrs = getAttrs(config);
159
160         String attr, s = "";
161         for (int i = 0; i < attrs.size(); i++) {
162             attr = (String) attrs.get(i);
163             s += "@attr " + Utils.replaceString(attr, " ", " @attr ") + " ";
164         }
165
166         String text = term;
167         if (text.length() > 0 && text.substring(0, 1).equals("^"))
168             text = text.substring(1);
169         int len = text.length();
170         if (len > 0 && text.substring(len-1, len).equals("^"))
171             text = text.substring(0, len-1);
172
173         return s + maybeQuote(text);
174     }
175
176     static String maybeQuote(String str) {
177        if (str == null)
178           return null;
179
180         // There _must_ be a better way to make this test ...
181         if (str.length() == 0 ||
182             str.indexOf('"') != -1 ||
183             str.indexOf(' ') != -1 ||
184             str.indexOf('\t') != -1 ||
185             str.indexOf('=') != -1 ||
186             str.indexOf('<') != -1 ||
187             str.indexOf('>') != -1 ||
188             str.indexOf('/') != -1 ||
189             str.indexOf('(') != -1 ||
190             str.indexOf(')') != -1) {
191             str = '"' + Utils.replaceString(str, "\"", "\\\"") + '"';
192         }
193
194         return str;
195     }
196
197     public byte[] toType1BER(Properties config) throws PQFTranslationException {
198         if (isResultSetIndex(index)) {
199             // Special case: ignore relation, modifiers, wildcards, etc.
200             // There's parallel code in toPQF()
201             byte[] operand = new byte[term.length()+100];
202             int offset;
203             offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
204             operand[offset++] = (byte)(0x80&0xff); // indefinite length
205             offset = putTag(CONTEXT, 31, PRIMITIVE, operand, offset); // ResultSetId
206             byte[] t = term.getBytes();
207             offset = putLen(t.length, operand, offset);
208             System.arraycopy(t, 0, operand, offset, t.length);
209             offset += t.length;
210             operand[offset++] = 0x00; // end of Operand
211             operand[offset++] = 0x00;
212             byte[] o = new byte[offset];
213             System.arraycopy(operand, 0, o, 0, offset);
214             return o;
215         }
216
217         String text = term;
218         if (text.length() > 0 && text.substring(0, 1).equals("^"))
219             text = text.substring(1);
220         int len = text.length();
221         if (len > 0 && text.substring(len-1, len).equals("^"))
222             text = text.substring(0, len-1);
223
224         String attr, attrList, term = text;
225         byte[] operand = new byte[text.length()+100];
226         int i, j, offset, type, value;
227         offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
228         operand[offset++]=(byte)(0x80&0xff); // indefinite length
229         offset = putTag(CONTEXT, 102, CONSTRUCTED, operand, offset); // AttributesPlusTerm
230         operand[offset++] = (byte)(0x80&0xff); // indefinite length
231         offset = putTag(CONTEXT, 44, CONSTRUCTED, operand, offset); // AttributeList
232         operand[offset++] = (byte)(0x80&0xff); // indefinite length
233
234         Vector attrs = getAttrs(config);
235         for(i = 0; i < attrs.size(); i++) {
236             attrList = (String) attrs.get(i);
237             java.util.StringTokenizer st =
238                 new java.util.StringTokenizer(attrList);
239             while (st.hasMoreTokens()) {
240                 attr = st.nextToken();
241                 j = attr.indexOf('=');
242                 offset = putTag(UNIVERSAL, SEQUENCE, CONSTRUCTED, operand, offset);
243                 operand[offset++] = (byte)(0x80&0xff);
244                 offset = putTag(CONTEXT, 120, PRIMITIVE, operand, offset);
245                 type = Integer.parseInt(attr.substring(0, j));
246                 offset = putLen(numLen(type), operand, offset);
247                 offset = putNum(type, operand, offset);
248
249                 offset = putTag(CONTEXT, 121, PRIMITIVE, operand, offset);
250                 value = Integer.parseInt(attr.substring(j+1));
251                 offset = putLen(numLen(value), operand, offset);
252                 offset = putNum(value, operand, offset);
253                 operand[offset++] = 0x00; // end of SEQUENCE
254                 operand[offset++] = 0x00;
255             }
256         }
257         operand[offset++] = 0x00; // end of AttributeList
258         operand[offset++] = 0x00;
259
260         offset = putTag(CONTEXT, 45, PRIMITIVE, operand, offset); // general Term
261         byte[] t = term.getBytes();
262         offset = putLen(t.length, operand, offset);
263         System.arraycopy(t, 0, operand, offset, t.length);
264         offset += t.length;
265
266         operand[offset++] = 0x00; // end of AttributesPlusTerm
267         operand[offset++] = 0x00;
268         operand[offset++] = 0x00; // end of Operand
269         operand[offset++] = 0x00;
270         byte[] o = new byte[offset];
271         System.arraycopy(operand, 0, o, 0, offset);
272         return o;
273     }
274 }