View Javadoc
1   /*
2    * Copyright 2008 FatWire Corporation. All Rights Reserved.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.fatwire.gst.foundation.facade.assetapi;
18  
19  import java.util.Arrays;
20  import java.util.LinkedList;
21  import java.util.List;
22  
23  import com.fatwire.assetapi.common.AssetAccessException;
24  import com.fatwire.assetapi.query.Condition;
25  import com.fatwire.assetapi.query.ConditionFactory;
26  import com.fatwire.assetapi.query.OpTypeEnum;
27  
28  import org.apache.commons.lang.StringUtils;
29  
30  /**
31   * Builds up a Condition from a string.
32   * <p/>
33   * Sample query strings are:
34   * <ul>
35   * <li>name='foo'</li>
36   * <li>name = 'foo'</li>
37   * <li>name = foo</li>
38   * <li>name= 'foo bar'</li>
39   * <li>size=[1,2]</li>
40   * <li>size{10,250}</li>
41   * <li>name!='foo'</li>
42   * </ul>
43   * Whitespace is not significant outside single quotes.
44   * 
45   * 
46   * @author Dolf Dijkstra
47   * @since Mar 29, 2011
48   */
49  public class ConditionParser {
50  
51      enum Operator {
52          EQUALS("="), NOT_EQUALS("!="), LESS_THAN("<"), LESS_THAN_EQUALS("<="), GREATER_THAN(">"), GREATER_THAN_EQUALS(
53                  ">="), BETWEEN("={"), BETWEEN_EXCLUDING("=!{"), LIKE("~"), RICHTEXT("#");
54          private final String op;
55  
56          Operator(final String op) {
57              this.op = op;
58          }
59      };
60  
61      private enum State {
62          ATTRIBUTE, OP, VALUE
63  
64      };
65  
66      interface ParserState {
67          ParserState parse(char c);
68  
69          String toValue();
70      }
71  
72      static class AttributeState implements ParserState {
73          private final StringBuilder value = new StringBuilder();
74          private boolean quoted = false;
75          private final ParserState next;
76  
77          AttributeState(final ParserState next) {
78              this.next = next;
79          }
80  
81          public ParserState parse(final char c) {
82              if (Character.isWhitespace(c)) {
83                  if (quoted) {
84                      value.append(c);
85                  } else {
86                      return next;
87                  }
88              } else if (c == '"' || c == '\'') {
89                  if (quoted) {
90                      quoted = false;
91                      return next;
92                  } else {
93                      quoted = true;
94                  }
95              } else if ("=!<>~{}".indexOf(c) != -1) {
96                  if (quoted) {
97                      value.append(c);
98                  } else if (value.length() > 0) {
99                      next.parse(c);
100                     return next;
101                 }
102             } else {
103                 value.append(c);
104 
105             }
106             return this;
107 
108         }
109 
110         public String toValue() {
111             return value.toString();
112         }
113     }
114 
115     static class OperatorState implements ParserState {
116         private final StringBuilder value = new StringBuilder();
117         private final ParserState next;
118 
119         OperatorState(final ParserState next) {
120             this.next = next;
121         }
122 
123         public ParserState parse(final char c) {
124             if ("=!<>~{".indexOf(c) != -1) {
125                 value.append(c);
126             } else {
127                 next.parse(c);
128                 return next;
129             }
130             return this;
131         }
132 
133         public String toValue() {
134             return value.toString();
135         }
136 
137     }
138 
139     static class ValueState implements ParserState {
140         private final StringBuilder value = new StringBuilder();
141         private boolean quoted = false;
142 
143         public ParserState parse(final char c) {
144             if (Character.isWhitespace(c)) {
145                 if (quoted) {
146                     value.append(c);
147                 } else if (value.length() > 0) {
148                     value.append(c);
149                 }
150             } else if (c == '"' || c == '\'') {
151                 quoted = !quoted;
152                 value.append(c);
153             } else if ("{}".indexOf(c) != -1) {
154                 // brackets must always be quoted in values
155                 if (quoted) {
156                     value.append(c);
157                 }
158             } else {
159                 value.append(c);
160 
161             }
162             return this;
163         }
164 
165         public String toValue() {
166             return value.toString();
167         }
168 
169     }
170 
171     public Condition parse(final String s) {
172         final ValueState valueState = new ValueState();
173         final OperatorState operatorState = new OperatorState(valueState);
174         final AttributeState attributeState = new AttributeState(operatorState);
175         ParserState state = attributeState;
176 
177         final char[] c = s.trim().toCharArray();
178         for (int i = 0; i < c.length; i++) {
179             state = state.parse(c[i]);
180         }
181         final String attName = attributeState.toValue();
182         if (StringUtils.isBlank(attName)) {
183             throw new IllegalArgumentException("No attribute name found in '" + s + "'.");
184         }
185 
186         OpTypeEnum opType;
187         try {
188             opType = toOpType(operatorState.toValue());
189         } catch (final Exception e) {
190             final IllegalArgumentException e2 = new IllegalArgumentException("No operator found in '" + s + "'. "
191                     + e.getMessage());
192             e2.initCause(e);
193             throw e2;
194         }
195 
196         final String value = valueState.toValue();
197         if (opType == OpTypeEnum.BETWEEN) {
198 
199             final String[] parts = valueSplit(value);
200             if (parts.length != 2) {
201                 throw new IllegalArgumentException("Between condition does not two comma-seperated values in '" + s
202                         + "'. ");
203             }
204             try {
205                 return new ConditionFactory().createBetweenCondition(attName, parts[0], parts[1]);
206             } catch (final AssetAccessException e) {
207                 final RuntimeAssetAccessException e1 = new RuntimeAssetAccessException(e.getMessage());
208                 e1.initCause(e);
209                 throw e1;
210             }
211         } else if (opType == OpTypeEnum.EQUALS && value.startsWith("[") && value.endsWith("]")) {
212 
213             final String[] parts = valueSplit(value.substring(1, value.length() - 1));
214             if (parts.length < 1) {
215                 throw new IllegalArgumentException("Equals condition with multiple values does have any values: '" + s
216                         + "'. ");
217             }
218 
219             return ConditionFactory.createCondition(attName, opType, Arrays.asList(parts));
220         } else if (opType == OpTypeEnum.NOT_EQUALS && value.startsWith("[") && value.endsWith("]")) {
221 
222             final String[] parts = valueSplit(value.substring(1, value.length() - 1));
223             if (parts.length < 1) {
224                 throw new IllegalArgumentException("Equals condition with multiple values does have any values: '" + s
225                         + "'. ");
226             }
227             Condition condition = null;
228             for (final String part : parts) {
229                 final Condition cc = ConditionFactory.createCondition(attName, opType, part);
230                 if (condition == null) {
231                     condition = cc;
232                 } else {
233                     condition = condition.and(cc);
234                 }
235             }
236             return condition;
237         }
238 
239         return ConditionFactory.createCondition(attName, opType, unquote(value));
240 
241     }
242 
243     private String unquote(final String value) {
244         if (StringUtils.isBlank(value)) {
245             return value;
246         }
247         final char c = value.charAt(0);
248         if (c == '\'' || c == '"') {
249             if (value.length() < 3) {
250                 return "";
251             }
252             return value.substring(1, value.length() - 1);
253         }
254         return value;
255     }
256 
257     public String[] valueSplit(final String s) {
258         final List<String> list = new LinkedList<String>();
259         boolean quoted = false;
260         final char[] c = s.toCharArray();
261         final StringBuilder cur = new StringBuilder();
262         for (int i = 0; i < c.length; i++) {
263             if (c[i] == ',') {
264                 if (quoted) {
265                     cur.append(c[i]);
266                 } else {
267                     list.add(cur.toString());
268                     cur.setLength(0);
269                 }
270             } else if (c[i] == '\'') {
271                 quoted = !quoted;
272             } else {
273                 cur.append(c[i]);
274             }
275 
276         }
277         list.add(cur.toString());
278         return list.toArray(new String[0]);
279     }
280 
281     OpTypeEnum toOpType(final StringBuilder op) {
282         return toOpType(op.toString().toLowerCase());
283     }
284 
285     OpTypeEnum toOpType(final String op) {
286         if (StringUtils.isBlank(op)) {
287             throw new IllegalArgumentException("Operator can  not be blank.");
288         }
289         if ("=".equals(op)) {
290             return OpTypeEnum.EQUALS;
291         } else if ("!=".equals(op)) {
292             return OpTypeEnum.NOT_EQUALS;
293         } else if ("<".equals(op)) {
294             return OpTypeEnum.LESS_THAN;
295         } else if (">".equals(op)) {
296             return OpTypeEnum.GREATER_THAN;
297         } else if ("{".equals(op)) {
298             return OpTypeEnum.BETWEEN;
299         } else if ("~".equals(op)) {
300             return OpTypeEnum.LIKE;
301         } else if ("#".equals(op)) {
302             return OpTypeEnum.RICHTEXT;
303 
304         }
305         throw new IllegalArgumentException("Can't decode operator in " + op);
306     }
307 
308 }