Add override annotations
[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     @Override
56     public String toXCQL(int level, List<CQLPrefix> prefixes,
57                          List<ModifierSet> sortkeys) {
58         return (indent(level) + "<searchClause>\n" +
59                 renderPrefixes(level+1, prefixes) +
60                 indent(level+1) + "<index>" + xq(index) + "</index>\n" +
61                 relation.toXCQL(level+1) +
62                 indent(level+1) + "<term>" + xq(term) + "</term>\n" +
63                 renderSortKeys(level+1, sortkeys) +
64                 indent(level) + "</searchClause>\n");
65     }
66
67     @Override
68     public String toCQL() {
69         String quotedIndex = maybeQuote(index);
70         String quotedTerm = maybeQuote(term);
71         String res = quotedTerm;
72
73         if (index != null &&
74             !index.equalsIgnoreCase("srw.serverChoice") &&
75             !index.equalsIgnoreCase("cql.serverChoice")) {
76             // ### We don't always need spaces around `relation'.
77             res = quotedIndex + " " + relation.toCQL() + " " + quotedTerm;
78         }
79
80         return res;
81     }
82
83     // ### Interaction between this and its callers is not good as
84     //  regards truncation of the term and generation of truncation
85     //  attributes.  Change the interface to fix this.
86     private List<String> getAttrs(Properties config) throws PQFTranslationException {
87         List<String> attrs = new ArrayList<String>();
88
89         // Do this first so that if any other truncation or
90         // completeness attributes are generated, they "overwrite"
91         // those specified here.
92         //
93         //  ### This approach relies on an unpleasant detail of Index
94         //      Data's (admittedly definitive) implementation of PQF,
95         //      and should not relied upon.
96         //
97         String attr = config.getProperty("always");
98         if (attr != null)
99             attrs.add(attr);
100
101         attr = config.getProperty("index." + index);
102         if (attr == null)
103             attr = config.getProperty("qualifier." + index);
104         if (attr == null)
105             throw new UnknownIndexException(index);
106         attrs.add(attr);
107
108         String rel = relation.getBase();
109         if (rel.equals("=")) {
110             rel = "eq";
111         } else if (rel.equals("<=")) {
112             rel = "le";
113         } else if (rel.equals(">=")) {
114             rel = "ge";
115         }
116         // ### Handling "any" and "all" properly would involve breaking
117         // the string down into a bunch of individual words and ORring
118         // or ANDing them together.  Another day.
119         attr = config.getProperty("relation." + rel);
120         if (attr == null)
121             throw new UnknownRelationException(rel);
122         attrs.add(attr);
123
124         List<Modifier> mods = relation.getModifiers();
125         for (int i = 0; i < mods.size(); i++) {
126             String type = mods.get(i).type;
127             attr = config.getProperty("relationModifier." + type);
128             if (attr == null)
129                 throw new UnknownRelationModifierException(type);
130             attrs.add(attr);
131         }
132
133         String pos = "any";
134         String text = term;
135         if (text.length() > 0 && text.substring(0, 1).equals("^")) {
136             text = text.substring(1); // ### change not seen by caller
137             pos = "first";
138         }
139         int len = text.length();
140         if (len > 0 && text.substring(len-1, len).equals("^")) {
141             text = text.substring(0, len-1); // ### change not seen by caller
142             pos = pos.equals("first") ? "firstAndLast" : "last";
143             // ### in the firstAndLast case, the standard
144             //  pqf.properties file specifies that we generate a
145             //  completeness=whole-field attributem, which means that
146             //  we don't generate a position attribute at all.  Do we
147             //  care?  Does it matter?
148         }
149
150         attr = config.getProperty("position." + pos);
151         if (attr == null)
152             throw new UnknownPositionException(pos);
153         attrs.add(attr);
154
155         attr = config.getProperty("structure." + rel);
156         if (attr == null)
157             attr = config.getProperty("structure.*");
158         attrs.add(attr);
159
160         return attrs;
161     }
162
163     @Override
164     public String toPQF(Properties config) throws PQFTranslationException {
165         if (isResultSetIndex(index)) {
166             // Special case: ignore relation, modifiers, wildcards, etc.
167             // There's parallel code in toType1BER()
168             return "@set " + maybeQuote(term);
169         }
170
171         List<String> attrs = getAttrs(config);
172
173         String attr, s = "";
174         for (int i = 0; i < attrs.size(); i++) {
175             attr = (String) attrs.get(i);
176             s += "@attr " + Utils.replaceString(attr, " ", " @attr ") + " ";
177         }
178
179         String text = term;
180         if (text.length() > 0 && text.substring(0, 1).equals("^"))
181             text = text.substring(1);
182         int len = text.length();
183         if (len > 0 && text.substring(len-1, len).equals("^"))
184             text = text.substring(0, len-1);
185
186         return s + maybeQuote(text);
187     }
188
189     static String maybeQuote(String str) {
190        if (str == null)
191           return null;
192
193         // There _must_ be a better way to make this test ...
194         if (str.length() == 0 ||
195             str.indexOf('"') != -1 ||
196             str.indexOf(' ') != -1 ||
197             str.indexOf('\t') != -1 ||
198             str.indexOf('=') != -1 ||
199             str.indexOf('<') != -1 ||
200             str.indexOf('>') != -1 ||
201             str.indexOf('/') != -1 ||
202             str.indexOf('(') != -1 ||
203             str.indexOf(')') != -1) {
204             str = '"' + Utils.replaceString(str, "\"", "\\\"") + '"';
205         }
206
207         return str;
208     }
209
210     @Override
211     public byte[] toType1BER(Properties config) throws PQFTranslationException {
212         if (isResultSetIndex(index)) {
213             // Special case: ignore relation, modifiers, wildcards, etc.
214             // There's parallel code in toPQF()
215             byte[] operand = new byte[term.length()+100];
216             int offset;
217             offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
218             operand[offset++] = (byte)(0x80&0xff); // indefinite length
219             offset = putTag(CONTEXT, 31, PRIMITIVE, operand, offset); // ResultSetId
220             byte[] t = term.getBytes();
221             offset = putLen(t.length, operand, offset);
222             System.arraycopy(t, 0, operand, offset, t.length);
223             offset += t.length;
224             operand[offset++] = 0x00; // end of Operand
225             operand[offset++] = 0x00;
226             byte[] o = new byte[offset];
227             System.arraycopy(operand, 0, o, 0, offset);
228             return o;
229         }
230
231         String text = term;
232         if (text.length() > 0 && text.substring(0, 1).equals("^"))
233             text = text.substring(1);
234         int len = text.length();
235         if (len > 0 && text.substring(len-1, len).equals("^"))
236             text = text.substring(0, len-1);
237
238         String attr, attrList;
239         byte[] operand = new byte[text.length()+100];
240         int i, j, offset, type, value;
241         offset = putTag(CONTEXT, 0, CONSTRUCTED, operand, 0); // op
242         operand[offset++]=(byte)(0x80&0xff); // indefinite length
243         offset = putTag(CONTEXT, 102, CONSTRUCTED, operand, offset); // AttributesPlusTerm
244         operand[offset++] = (byte)(0x80&0xff); // indefinite length
245         offset = putTag(CONTEXT, 44, CONSTRUCTED, operand, offset); // AttributeList
246         operand[offset++] = (byte)(0x80&0xff); // indefinite length
247
248         List<String> attrs = getAttrs(config);
249         for(i = 0; i < attrs.size(); i++) {
250             attrList = (String) attrs.get(i);
251             java.util.StringTokenizer st =
252                 new java.util.StringTokenizer(attrList);
253             while (st.hasMoreTokens()) {
254                 attr = st.nextToken();
255                 j = attr.indexOf('=');
256                 offset = putTag(UNIVERSAL, SEQUENCE, CONSTRUCTED, operand, offset);
257                 operand[offset++] = (byte)(0x80&0xff);
258                 offset = putTag(CONTEXT, 120, PRIMITIVE, operand, offset);
259                 type = Integer.parseInt(attr.substring(0, j));
260                 offset = putLen(numLen(type), operand, offset);
261                 offset = putNum(type, operand, offset);
262
263                 offset = putTag(CONTEXT, 121, PRIMITIVE, operand, offset);
264                 value = Integer.parseInt(attr.substring(j+1));
265                 offset = putLen(numLen(value), operand, offset);
266                 offset = putNum(value, operand, offset);
267                 operand[offset++] = 0x00; // end of SEQUENCE
268                 operand[offset++] = 0x00;
269             }
270         }
271         operand[offset++] = 0x00; // end of AttributeList
272         operand[offset++] = 0x00;
273
274         offset = putTag(CONTEXT, 45, PRIMITIVE, operand, offset); // general Term
275         byte[] t = text.getBytes();
276         offset = putLen(t.length, operand, offset);
277         System.arraycopy(t, 0, operand, offset, t.length);
278         offset += t.length;
279
280         operand[offset++] = 0x00; // end of AttributesPlusTerm
281         operand[offset++] = 0x00;
282         operand[offset++] = 0x00; // end of Operand
283         operand[offset++] = 0x00;
284         byte[] o = new byte[offset];
285         System.arraycopy(operand, 0, o, 0, offset);
286         return o;
287     }
288 }