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