source: trunk/fmgVen/src/com/fmguler/ven/Criteria.java @ 30

Last change on this file since 30 was 30, checked in by fmguler, 13 years ago

Fixes #3 - Criteria object is mostly implemented. An override of Ven.list() having criteria parameter is added. Old codebase only had string based criteria. In this commit, I am also adding typed criteria as a new feauture. With this new criteria system, user can create criteria object like;

new Criteria().eq(attr,value).like(attr,value).and().gt(attr, value).or().orderAsc(attr).orderDesc(attr).limit(limit, offset);

The orderings and the limit/offset values are not taken into account in the Ven (these are trivial, I will do them later).

Not thouroughly tested, because complex scenarios are only possible when applying to a real problem in a real application (which I am planning to do next).

File size: 11.7 KB
Line 
1/*
2 *  fmgVen - A Convention over Configuration Java ORM Tool
3 *  Copyright 2011 Fatih Mehmet Güler
4 *
5 *  Licensed under the Apache License, Version 2.0 (the "License");
6 *  you may not use this file except in compliance with the License.
7 *  You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 *  under the License.
17 */
18package com.fmguler.ven;
19
20import com.fmguler.ven.util.Convert;
21import java.util.HashMap;
22import java.util.LinkedList;
23import java.util.Map;
24import java.util.Stack;
25
26/**
27 * Criteria object, used to filter the result of a query according to some criteria.
28 * <p>
29 * Can be used in two modes; as string criteria or typed criteria.
30 * The string criteria is used like;
31 * <pre>
32 * new Criteria().add("and SomeDomainObject.anotherDomainObjects.name like :p1").param("p1", "a%");
33 * </pre>
34 * And the typed criteria is used like;
35 * <pre>
36 * new Criteria().eq(attr,value).like(attr,value).and().gt(attr, value).or().orderAsc(attr).orderDesc(attr).limit(limit, offset);
37 * </pre>
38 *
39 * This criteria object is used to filter SQL results by adding the "Criteria.criteriaToSQL()" output to the where clause.
40 *
41 * @author Fatih Mehmet Güler
42 */
43public class Criteria {
44    private StringBuffer criteriaStringBuffer = new StringBuffer(); //for string criteria
45    private LinkedList criterionList = new LinkedList(); //for typed criteria
46    private Map parameters = new HashMap(); //the parameters used in criteria string
47    private int limit = 20;
48    private int offset = 0;
49    int paramCount = 0;
50
51    //--------------------------------------------------------------------------
52    //String Criteria Methods
53    /**
54     * Add a criteria string
55     */
56    public Criteria add(String criteriaString) {
57        this.criteriaStringBuffer.append(" ").append(criteriaString);
58        return this;
59    }
60
61    /**
62     * Add the parameter used in criteria string
63     */
64    public Criteria param(String parameter, Object value) {
65        this.parameters.put(parameter, value);
66        return this;
67    }
68
69    /**
70     * Converts criteria string to SQL
71     * @return the criteria string as SQL where clause
72     */
73    public String criteriaStringToSQL() {
74        StringBuffer resultBuffer = new StringBuffer();
75        String criteriaString = criteriaStringBuffer.toString();
76        criteriaString = criteriaString.replaceAll("\\(", "\\( ");
77        criteriaString = criteriaString.replaceAll("\\)", " \\)");
78
79        String[] parts = criteriaString.split(" ");
80        for (int i = 0; i < parts.length; i++) {
81            if (!parts[i].startsWith(":") && parts[i].indexOf(".") >= 0) {
82                int lastDot = parts[i].lastIndexOf('.');
83                resultBuffer.append(" ");
84                int u = parts[i].length();
85                resultBuffer.append(Convert.toDB(parts[i].substring(0, lastDot).replace('.', '_')));
86                resultBuffer.append(Convert.toDB(parts[i].substring(lastDot, u)));
87            } else {
88                resultBuffer.append(" ").append(parts[i]);
89            }
90        }
91        return resultBuffer.toString();
92    }
93
94    /**
95     * @return the parameter map
96     */
97    public Map getParameters() {
98        return parameters;
99    }
100
101    //--------------------------------------------------------------------------
102    //Typed Criteria Methods
103    /**
104     * Add a criterion where an attribue equals a value (SQL = operator)
105     */
106    public Criteria eq(String attribute, String value) {
107        criterionList.add(new Criterion(attribute, Criterion.OP_EQUALS, value));
108        return this;
109    }
110
111    /**
112     * Add a criterion where an attribue is like a value (SQL like operator)
113     */
114    public Criteria like(String attribute, String value) {
115        criterionList.add(new Criterion(attribute, Criterion.OP_LIKE, value));
116        return this;
117    }
118
119    /**
120     * Add a criterion where an attribue is similar to a value (SQL similar to operator)
121     */
122    public Criteria similarto(String attribute, String value) {
123        criterionList.add(new Criterion(attribute, Criterion.OP_SIMILAR_TO, value));
124        return this;
125    }
126
127    /**
128     * Add a criterion where an attribue is greater than a value (SQL > operator)
129     */
130    public Criteria gt(String attribute, String value) {
131        criterionList.add(new Criterion(attribute, Criterion.OP_GREATER_THAN, value));
132        return this;
133    }
134
135    /**
136     * Add a criterion where an attribue is less than a value (SQL < operator)
137     */
138    public Criteria lt(String attribute, String value) {
139        criterionList.add(new Criterion(attribute, Criterion.OP_LESS_THAN, value));
140        return this;
141    }
142
143    /**
144     * Add a criterion where an attribue is null (SQL is null operator)
145     */
146    public Criteria isNull(String attribute) {
147        criterionList.add(new Criterion(attribute, Criterion.OP_ISNULL, null));
148        return this;
149    }
150
151    /**
152     * Add: order by attribute asc
153     */
154    public Criteria orderAsc(String attribute) {
155        //Not implemented yet.
156        return this;
157    }
158
159    /**
160     * Add: order by attribute desc
161     */
162    public Criteria orderDesc(String attribute) {
163        //Not implemented yet.
164        return this;
165    }
166
167    /**
168     * Add limit and offset to the clause
169     */
170    public Criteria limit(int limit, int offset) {
171        this.limit = limit;
172        this.offset = offset;
173        return this;
174    }
175
176    //--------------------------------------------------------------------------
177    //Connectives
178    /**
179     * Connect previous two criteria with logical and
180     * (as in postfix notation)
181     */
182    public Criteria and() {
183        criterionList.add(new Criterion(Criterion.CONN_AND));
184        return this;
185    }
186
187    /**
188     * Connect previous two criteria with logical or
189     * (as in postfix notation)
190     */
191    public Criteria or() {
192        criterionList.add(new Criterion(Criterion.CONN_OR));
193        return this;
194    }
195
196    /**
197     * Connect previous criterion with logical not
198     * (as in postfix notation)
199     */
200    public Criteria not() {
201        criterionList.add(new Criterion(Criterion.CONN_NOT));
202        return this;
203    }
204
205    /**
206     * Converts typed criteria to SQL
207     * @return the typed criteria as SQL where clause
208     */
209    public String criteriaToSQL() {
210        LinkedList clone = (LinkedList)criterionList.clone();
211        Stack stack = new Stack();
212        while (!clone.isEmpty()) {
213            Criterion criterion = (Criterion)clone.removeFirst();
214            if (!criterion.isConnective()) {
215                String s = "(" + convertOperatorToSQL(criterion) + ")";
216                stack.push(s);
217            } else {
218                String conn = criterion.connective;
219                if (conn.equals(Criterion.CONN_AND)) {
220                    String s1 = (String)stack.pop();
221                    String s2 = (String)stack.pop();
222                    String sr = "(" + s1 + " and " + s2 + ")";
223                    stack.push(sr);
224                } else if (conn.equals(Criterion.CONN_OR)) {
225                        String s1 = (String)stack.pop();
226                        String s2 = (String)stack.pop();
227                        String sr = "(" + s1 + " or " + s2 + ")";
228                        stack.push(sr);
229                    } else if (conn.equals(Criterion.CONN_NOT)) {
230                            String s = (String)stack.pop();
231                            String sr = "not (" + s + ")";
232                            stack.push(sr);
233                        }
234            }
235        }
236
237        if (stack.isEmpty()) return "1=1";
238
239        String result = (String)stack.pop();
240        return result;
241    }
242
243    //convert the attribute-operator-value criterion to SQL
244    private String convertOperatorToSQL(Criterion criterion) {
245        String attribute = convertAttributeToAlias(criterion.attribute);
246        String operator = criterion.operator;
247        Object value = criterion.value;
248        String result = "";
249
250        if (value == null) {
251            //no value e.g. is null operator
252            result = attribute + " " + operator;
253        } else {
254            String paramName = genParamName();
255            parameters.put(paramName, value);
256            result = attribute + " " + operator + " :" + paramName + "";
257        }
258
259        return result;
260    }
261
262    //convert attribute to SQL alias
263    protected String convertAttributeToAlias(String attribute) {
264        //we convert like: x.y.z.t to x_y_z.t
265        StringBuffer resultBuffer = new StringBuffer();
266        int lastDot = attribute.lastIndexOf('.');
267        resultBuffer.append(" ");
268        int u = attribute.length();
269        resultBuffer.append(Convert.toDB(attribute.substring(0, lastDot).replace('.', '_')));
270        resultBuffer.append(Convert.toDB(attribute.substring(lastDot, u)));
271        return resultBuffer.toString();
272    }
273
274    //generate parameter for typed criteria
275    private String genParamName() {
276        return "__p" + paramCount++;
277    }
278
279    //test the criteria object
280    public static void main(String[] args) {
281        Criteria criteria = new Criteria() //criteria object
282                .like("SomeDomainObject.anotherDomainObjects.name", "a%") //attribute like value
283                .eq("SomeDomainObject.name", "sdo1") //attribute equals value
284                .and() //connect previous criteria with and
285                .isNull("SomeDomainObject.description") //attribute is null
286                .or(); //connect previous criteria with and
287
288        //print the resulting where clause SQL
289        System.out.println(criteria.criteriaToSQL());
290
291        //the result is;
292        //(( some_domain_object.description is null) or (( some_domain_object.name = :__p1) and ( some_domain_object_another_domain_objects.name like :__p0)))
293    }
294}
295
296/**
297 * Each criterion used in the criteria.
298 * Used to hold attribute-operator-value triplet.
299 * Used internally.
300 * @author Fatih Mehmet Güler
301 */
302class Criterion {
303    //available logical connectives
304    public static final String CONN_AND = "and";
305    public static final String CONN_OR = "or";
306    public static final String CONN_NOT = "not";
307    //available operators
308    public static final String OP_EQUALS = "=";
309    public static final String OP_LESS_THAN = "<";
310    public static final String OP_GREATER_THAN = ">";
311    public static final String OP_LIKE = "like";
312    public static final String OP_SIMILAR_TO = "similar to";
313    public static final String OP_NOT = "not";
314    public static final String OP_ISNULL = "is null";
315    //attribute-operator-value triplet
316    public String attribute;
317    public String operator;
318    public Object value;
319    //logical connective
320    public String connective = null;
321
322    public Criterion(String attribute, String operator, Object value) {
323        this.attribute = attribute;
324        this.operator = operator;
325        this.value = value;
326    }
327
328    public Criterion(String connective) {
329        this.connective = connective;
330    }
331
332    public boolean isConnective() {
333        return connective != null;
334    }
335
336    /**
337     * @return string representation of this criterion
338     */
339    public String toString() {
340        if (isConnective()) return connective;
341        return attribute + " " + operator + " " + value;
342    }
343}
Note: See TracBrowser for help on using the repository browser.