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