| 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 |  */ | 
|---|
| 18 | package com.fmguler.ven; | 
|---|
| 19 |  | 
|---|
| 20 | import com.fmguler.ven.util.Convert; | 
|---|
| 21 | import java.util.HashMap; | 
|---|
| 22 | import java.util.LinkedList; | 
|---|
| 23 | import java.util.Map; | 
|---|
| 24 | import 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 |  */ | 
|---|
| 43 | public 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, Object 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, Object 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, Object 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, Object 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, Object 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 |  */ | 
|---|
| 302 | class 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 | } | 
|---|