Coverage Report - org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria
 
Classes in this File Line Coverage Branch Coverage Complexity
Criteria
0%
0/390
0%
0/212
2.671
Criteria$1
0%
0/1
N/A
2.671
 
 1  
 /*
 2  
  * Copyright 2006-2011 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
 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  
 package org.kuali.rice.core.framework.persistence.jpa.criteria;
 17  
 
 18  
 import org.kuali.rice.core.api.util.Truth;
 19  
 import org.kuali.rice.core.api.util.type.TypeUtils;
 20  
 
 21  
 import javax.persistence.Query;
 22  
 import java.util.ArrayList;
 23  
 import java.util.Arrays;
 24  
 import java.util.Collection;
 25  
 import java.util.HashMap;
 26  
 import java.util.Iterator;
 27  
 import java.util.LinkedHashMap;
 28  
 import java.util.List;
 29  
 import java.util.Map;
 30  
 import java.util.regex.Pattern;
 31  
 
 32  
 /**
 33  
  * A criteria builder for JPQL Query objects.
 34  
  * 
 35  
  * <p>All entities in queries generated by this class will be given an alias, and an initial entity (with an initial alias)
 36  
  * is mandatory. If no alias is given or if a constructor without an alias parameter is used, a new alias name will be
 37  
  * auto-generated.
 38  
  * 
 39  
  * <p>The interpretation of the initial entity depends on what type of query is intended. If a SELECT query is desired,
 40  
  * the initial entity will be the first to appear in the FROM clause. If a DELETE query is desired, the initial
 41  
  * entity will be the one that appears in the DELETE FROM clause. If an UPDATE query is desired, the initial entity
 42  
  * will be the one that appears in the UPDATE clause. 
 43  
  * 
 44  
  * <p>Most of the methods of this class rely on String expressions representing entity properties, or functions
 45  
  * acting on constants or entity properties. If the expression contains functions or if
 46  
  * you wish to reference the property of an entity other than the first aliased entity of the query, then the
 47  
  * String expression must include the alias of the entity in the format __JPA_ALIAS[[index]]__ or the format
 48  
  * __JPA_ALIAS[['alias']]__ , where "index" is a numeric index pointing to the Nth alias of the current query in the
 49  
  * order that the aliases were added to the query (starting at zero), and where "'alias'" is the String name of the
 50  
  * entity alias enclosed in single quotes.
 51  
  * 
 52  
  * <p>If the String expression as defined above does not contain custom JPA aliases, then the initial alias of the current
 53  
  * Criteria (and a '.' to separate the alias from the expression) will be prepended to the expression automatically.
 54  
  * 
 55  
  * <p>If the current Criteria is intended for use as a sub-query, then aliases of the parent Criteria can be referred to
 56  
  * by name (so long as they do not conflict with the alias names in the sub-Criteria) or by specifying the bitwise NOT of
 57  
  * the alias index in the parent Criteria (-1 for 0, -2 for 1, etc.). When including a sub-query in a parent query, the
 58  
  * parent Criteria will handle the updating of the alias indexes and will auto-generate new aliases as needed if any of
 59  
  * the sub-query's defined aliases are duplicates of those in the parent query.
 60  
  * 
 61  
  * <p>For Criteria objects intended to be ANDed or ORed with an existing Criteria, it is assumed that any named or indexed
 62  
  * aliases refer to exactly the same aliases as in the existing Criteria. Any auto-prepended aliases as described above
 63  
  * will always be referred to by index, so it is safe for them to be included in the Criteria to be ANDed/ORed with the
 64  
  * existing one. Both of these details also apply when adding new Criteria to the HAVING clause of an existing Criteria, or
 65  
  * when inserting the negation of another Criteria's conditions into an existing Criteria as a "NOT (...)" expression.
 66  
  * 
 67  
  * <p>Also, during query construction, the Criteria API will automatically include substrings in the format
 68  
  * __JPA_PARAM[['parameter']]__ when adding input parameters, where 'parameter' is the auto-generated name of the
 69  
  * parameter. Care should be taken to ensure that __JPA_ALIAS[[...]]__ and __JPA_PARAM[[...]]__ expressions are not
 70  
  * being added to queries that wish to interpret such String patterns literally.
 71  
  * 
 72  
  * <p>Note that Criteria instances are not thread-safe, so external synchronization is necessary if operating on them
 73  
  * from multiple threads.
 74  
  * 
 75  
  * <p>TODO: Verify if any other features need to be added to this API.
 76  
  * 
 77  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 78  
  */
 79  
 @SuppressWarnings("unchecked")
 80  
 public class Criteria {
 81  
 
 82  0
         private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(Criteria.class);
 83  0
         private static final Pattern APOS_PAT = Pattern.compile("'");
 84  0
         private static final String[] LOOKUP_WILDCARDS = {"*", "?"};
 85  0
         private static final String[] ESCAPED_LOOKUP_WILDCARDS = {"\\*", "\\?"};
 86  0
         private static final char[] JPQL_WILDCARDS = {'%', '_'};
 87  
         
 88  
         /** The String representing the beginning of a by-name or by-index reference to an entity alias. */
 89  
         public static final String JPA_ALIAS_PREFIX = "__JPA_ALIAS[[";
 90  
         /** The String representing the beginning of a by-name reference to an input parameter. */
 91  
         public static final String JPA_PARAM_PREFIX = "__JPA_PARAM[[";
 92  
         /** The String representing the termination of by-name/index references to aliases or input parameters. */
 93  
         public static final String JPA_ALIAS_SUFFIX = "]]__";
 94  
 
 95  
         private static final String JPA_INITIAL_ALIAS = JPA_ALIAS_PREFIX + "0" + JPA_ALIAS_SUFFIX;
 96  
         private static final String JPA_PARENT_INITIAL_ALIAS = JPA_ALIAS_PREFIX + "-1" + JPA_ALIAS_SUFFIX;
 97  
 
 98  
         private static final String JPA_PARAM_PREFIX_WITH_COLON = ":" + JPA_PARAM_PREFIX;
 99  0
         private static final int ALIAS_PREFIX_LEN = JPA_ALIAS_PREFIX.length();
 100  0
         private static final int PARAM_PREFIX_LEN = JPA_PARAM_PREFIX.length();
 101  0
         private static final int ALIAS_SUFFIX_LEN = JPA_ALIAS_SUFFIX.length();
 102  
         
 103  
         private Integer searchLimit;
 104  
         
 105  
         private int generatedAliasCount;
 106  
         
 107  
         private int generatedBindParamCount;
 108  
         
 109  0
         private boolean distinct = false;
 110  
         
 111  0
         private final List<String> entityAliases = new ArrayList<String>();
 112  
         
 113  0
         private final List<String> indexedAliasPlaceholders = new ArrayList<String>();
 114  
         
 115  0
         private final List<String> namedAliasPlaceholders = new ArrayList<String>();
 116  
         
 117  0
         private final Map<String,Integer> aliasIndexes = new HashMap<String,Integer>();
 118  
         
 119  0
         private final StringBuilder selectClause = new StringBuilder();
 120  
         
 121  0
         private final StringBuilder fromClause = new StringBuilder();
 122  
         
 123  0
         private final StringBuilder whereClause = new StringBuilder(50);
 124  
         
 125  0
         private final StringBuilder groupByClause = new StringBuilder();
 126  
         
 127  0
         private final StringBuilder havingClause = new StringBuilder();
 128  
         
 129  0
         private final StringBuilder orderByClause = new StringBuilder();
 130  
         
 131  0
         private final StringBuilder setClause = new StringBuilder();
 132  
         
 133  
         private final String initialEntityName;
 134  
         
 135  0
         protected Map<String, Object> params = new LinkedHashMap<String, Object>();
 136  
 
 137  
         /**
 138  
          * Constructs a new Criteria instance that includes the given initial entity. An alias will be
 139  
          * auto-generated for this entity, and the entity's alias will be included in the SELECT clause if this
 140  
          * Criteria is intended to be used as a SELECT query.
 141  
          * 
 142  
          * @param entityName The class name of the initial JPA entity.
 143  
          */
 144  
         public Criteria(String entityName) {
 145  0
                 this(entityName, "a", true);
 146  0
         }
 147  
 
 148  
         /**
 149  
          * Constructs a new Criteria instance that includes the given initial entity. The given alias
 150  
          * will be used for this entity if it is non-null, and the alias will be included in the SELECT clause
 151  
          * if this Criteria is intended to be used as a SELECT query.
 152  
          * 
 153  
          * @param entityName The class name of the initial JPA entity.
 154  
          * @param alias The alias to use for this entity; if it is null, a new alias will be auto-generated.
 155  
          */
 156  
         public Criteria(String entityName, String alias) {
 157  0
                 this(entityName, alias, true);
 158  0
         }
 159  
         
 160  
         /**
 161  
          * Constructs a new Criteria instance that includes the given initial entity. An alias will be
 162  
          * auto-generated for this entity, and the extra parameter indicates whether this alias should also be
 163  
          * added to the query's SELECT clause (if this Criteria is intended to be used as a SELECT query).
 164  
          * 
 165  
          * @param entityName The class name of the initial JPA entity.
 166  
          * @param includeEntityInSelect Indicates whether this entity's alias should be added to the SELECT clause.
 167  
          */
 168  
         public Criteria(String entityName, boolean includeEntityInSelect) {
 169  0
                 this(entityName, "a", includeEntityInSelect);
 170  0
         }
 171  
         
 172  
         /**
 173  
          * Constructs a new Criteria instance that includes the given initial entity. The given alias
 174  
          * will be used for this entity if it is non-null, and the extra parameter indicates whether the alias
 175  
          * should be added to the SELECT clause (if this Criteria is intended to be used as a SELECT query).
 176  
          * 
 177  
          * @param entityName The class name of the initial JPA entity.
 178  
          * @param alias The alias to use for this entity; if it is null, a new alias will be auto-generated.
 179  
          * @param includeEntityAliasInSelect Indicates whether this entity's alias should be added to the SELECT clause.
 180  
          */
 181  0
         public Criteria(String entityName, String alias, boolean includeEntityAliasInSelect) {
 182  0
                 this.initialEntityName = entityName;
 183  0
                 from(entityName, alias, includeEntityAliasInSelect);
 184  0
         }
 185  
 
 186  
         /*
 187  
          * Adds the given alias to the Criteria's alias lists/maps. If the alias is null or it duplicates an
 188  
          * existing one, a new alias will be auto-generated.
 189  
          * 
 190  
          * Returns the new name of the alias, or the passed-in name if non-null and no conflicts were found.
 191  
          */
 192  
         private String addAlias(String alias) {
 193  0
                 if (alias == null) {
 194  0
                         alias = "a" + (generatedAliasCount++);
 195  
                 }
 196  0
                 while (aliasIndexes.containsKey(alias)) {
 197  0
                         alias = "a" + (generatedAliasCount++);
 198  
                 }
 199  0
                 entityAliases.add(alias);
 200  0
                 aliasIndexes.put(alias, entityAliases.size() - 1);
 201  0
                 indexedAliasPlaceholders.add(new StringBuilder(30).append(JPA_ALIAS_PREFIX).append(indexedAliasPlaceholders.size()).append(JPA_ALIAS_SUFFIX).toString());
 202  0
                 namedAliasPlaceholders.add(new StringBuilder(30).append(JPA_ALIAS_PREFIX).append('\'').append(alias).append('\'').append(JPA_ALIAS_SUFFIX).toString());
 203  0
                 return alias;
 204  
         }
 205  
         
 206  
         /**
 207  
          * Adds the given expression to the SELECT clause. If no properly-referenced alias appears
 208  
          * in the String expression, it will be assumed that the expression is accessing something
 209  
          * on or through the initial entity. See the description of this class for more information
 210  
          * on how String expressions are interpreted.
 211  
          * 
 212  
          * @param resultExpression The String expression to add to the SELECT clause.
 213  
          */
 214  
         public void select(String resultExpression) {
 215  0
                 if (resultExpression.contains(JPA_ALIAS_PREFIX)) {
 216  0
                         selectClause.append((selectClause.length() > 0) ? ", " : "").append(resultExpression);
 217  
                 } else {
 218  0
                         selectClause.append((selectClause.length() > 0) ? ", " : "").append(JPA_INITIAL_ALIAS).append('.').append(resultExpression);
 219  
                 }
 220  0
         }
 221  
         
 222  
         /**
 223  
          * Adds a new JPA entity to the FROM clause. The entity will be given the provided alias if it
 224  
          * is non-null and does not conflict with any existing alias names; otherwise, a new alias name
 225  
          * will be auto-generated. An extra parameter is also included for indicating whether this
 226  
          * Criteria should automatically include the entity's alias in the SELECT clause.
 227  
          * 
 228  
          * @param entityName The class name of the new entity.
 229  
          * @param alias The alias to use for this entity.
 230  
          * @param includeEntityAliasInSelect Indicates whether to include the entity's alias in the SELECT clause.
 231  
          * @return The provided alias if it's non-null and does not conflict with any existing names, or an auto-generated alias name otherwise.
 232  
          */
 233  
         public String from(String entityName, String alias, boolean includeEntityAliasInSelect) {
 234  0
                 alias = addAlias(alias);
 235  0
                 if (includeEntityAliasInSelect) {
 236  0
                         select(namedAliasPlaceholders.get(namedAliasPlaceholders.size() - 1));
 237  
                 }
 238  0
                 fromClause.append((fromClause.length() > 0) ? ", " : " FROM ").append(entityName).append(" AS ").append(
 239  
                                 namedAliasPlaceholders.get(namedAliasPlaceholders.size() - 1));
 240  0
                 return alias;
 241  
         }
 242  
         
 243  
         /**
 244  
          * Adds a new "IN (...)" expression containing the given collection expression into the FROM clause.
 245  
          * The collection will be given the provided alias if it is non-null and does not conflict with
 246  
          * any existing alias names; otherwise, a new alias name will be auto-generated. An extra parameter
 247  
          * is also included for indicating whether this Criteria should automatically include the
 248  
          * collection's alias in the SELECT clause.
 249  
          * 
 250  
          * <p>If no properly-referenced alias appears in the String expression, it will be assumed that the
 251  
          * expression points to a collection that is accessible on or through the initial entity. See the
 252  
          * description of this class for more information on how String expressions are interpreted.
 253  
          * 
 254  
          * @param collectionName A String expression that points to an entity collection.
 255  
          * @param alias The alias to use for this collection expression.
 256  
          * @param includeCollectionAliasInSelect Indicates whether to include the collection's alias in the SELECT clause.
 257  
          * @return The provided alias if it's non-null and does not conflict with any existing names, or an auto-generated alias name otherwise.
 258  
          */
 259  
         public String fromIn(String collectionName, String alias, boolean includeCollectionAliasInSelect) {
 260  0
                 alias = addAlias(alias);
 261  0
                 if (includeCollectionAliasInSelect) {
 262  0
                         select(namedAliasPlaceholders.get(namedAliasPlaceholders.size() - 1));
 263  
                 }
 264  
                 // The optional "AS" keyword is excluded when creating the "IN (...)" expression because Hibernate will throw an exception if it exists.
 265  0
                 if (collectionName.contains(JPA_ALIAS_PREFIX)) {
 266  0
                         fromClause.append(", IN (").append(collectionName).append(") ").append(namedAliasPlaceholders.get(namedAliasPlaceholders.size() - 1));
 267  
                 } else {
 268  0
                         fromClause.append(", IN (").append(JPA_INITIAL_ALIAS).append('.').append(collectionName).append(") ").append(
 269  
                                         namedAliasPlaceholders.get(namedAliasPlaceholders.size() - 1));
 270  
                 }
 271  0
                 return alias;
 272  
         }
 273  
         
 274  
         /**
 275  
          * Adds a new JOIN to the most-recently-added entity in the FROM clause. The association
 276  
          * will be given the provided alias if it is non-null and does not conflict with
 277  
          * any existing alias names; otherwise, a new alias name will be auto-generated. An extra parameter
 278  
          * is also included for indicating whether this Criteria should automatically include the
 279  
          * association's alias in the SELECT clause.
 280  
          * 
 281  
          * <p>If no properly-referenced alias appears in the String expression, it will be assumed that the
 282  
          * expression points to an association that is accessible on or through the initial entity. See the
 283  
          * description of this class for more information on how String expressions are interpreted.
 284  
          * 
 285  
          * @param associationName A String expression that points to an entity association.
 286  
          * @param alias The alias to use for this association expression.
 287  
          * @param includeAssociationAliasInSelect Indicates whether to include the collection's alias in the SELECT clause.
 288  
          * @param innerJoin If true, the join will be an inner join; otherwise, it will be a left (outer) join.
 289  
          * @return The provided alias if it's non-null and does not conflict with any existing names, or an auto-generated alias name otherwise.
 290  
          */
 291  
         public String join(String associationName, String alias, boolean includeAssociationAliasInSelect, boolean innerJoin) {
 292  0
                 alias = addAlias(alias);
 293  0
                 if (includeAssociationAliasInSelect) {
 294  0
                         select(namedAliasPlaceholders.get(namedAliasPlaceholders.size() - 1));
 295  
                 }
 296  0
                 if (associationName.contains(JPA_ALIAS_PREFIX)) {
 297  0
                         fromClause.append(innerJoin ? " INNER JOIN " : " LEFT JOIN ").append(associationName).append(" AS ").append(
 298  
                                         namedAliasPlaceholders.get(namedAliasPlaceholders.size() - 1));
 299  
                 } else {
 300  0
                         fromClause.append(innerJoin ? " INNER JOIN " : " LEFT JOIN ").append(JPA_INITIAL_ALIAS).append('.').append(
 301  
                                         associationName).append(" AS ").append(namedAliasPlaceholders.get(namedAliasPlaceholders.size() - 1));
 302  
                 }
 303  0
                 return alias;
 304  
         }
 305  
         
 306  
         /**
 307  
          * Adds a new JOIN FETCH to the most-recently-added entity in the FROM clause. If no
 308  
          * properly-referenced alias appears in the String expression, it will be assumed that the
 309  
          * expression points to an association that is accessible on or through the initial entity. See the
 310  
          * description of this class for more information on how String expressions are interpreted.
 311  
          * 
 312  
          * @param associationName A String expression that points to an entity association.
 313  
          * @param innerJoin If true, the join will be an inner join; otherwise, it will be a left (outer) join.
 314  
          */
 315  
         public void joinFetch(String associationName, boolean innerJoin) {
 316  0
                 if (associationName.contains(JPA_ALIAS_PREFIX)) {
 317  0
                         fromClause.append(innerJoin ? " INNER JOIN FETCH " : " LEFT JOIN FETCH ").append(associationName);
 318  
                 } else {
 319  0
                         fromClause.append(innerJoin ? " INNER JOIN FETCH " : " LEFT JOIN FETCH ").append(JPA_INITIAL_ALIAS).append('.').append(associationName);
 320  
                 }
 321  0
         }
 322  
         
 323  
         /**
 324  
          * Adds a new attribute assignment expression to the SET clause (for use with UPDATE queries). It is
 325  
          * assumed that the given attribute belongs to the initial entity, so a custom alias should not be used.
 326  
          * 
 327  
          * @param attributeName An expression pointing to an attribute that is accessible on or through the initial entity.
 328  
          * @param value The value to assign to the attribute.
 329  
          */
 330  
         public void set(String attributeName, Object value) {
 331  0
                 setClause.append((setClause.length() > 0) ? ", " : " SET ").append(JPA_INITIAL_ALIAS).append('.').append(attributeName).append(
 332  
                                 " = ").append(addAttr(fixPeriods(attributeName), value));
 333  0
         }
 334  
         
 335  
         /**
 336  
          * Adds one new attribute assignment expression to the SET clause for each entry in the given Map (for use with
 337  
          * UPDATE queries). It is assumed that the attributes belong to the initial entity, so custom aliases should not be used.
 338  
          *
 339  
          * @param attributes A Map containing expressions pointing to initial-entity-accessible attributes and their associated values.
 340  
          */
 341  
         public void set(Map<String,Object> attributes) {
 342  0
                 for (Map.Entry<String,Object> attribute : attributes.entrySet()) {
 343  0
                         setClause.append((setClause.length() > 0) ? ", " : " SET ").append(JPA_INITIAL_ALIAS).append('.').append(attribute.getKey()).append(
 344  
                                         " = ").append(addAttr(fixPeriods(attribute.getKey()), attribute.getValue()));
 345  
                 }
 346  0
         }
 347  
         
 348  
         /*
 349  
          * Converts an Object into a JPQL-valid constant, or creates a new input parameter for it, depending on the Object type.
 350  
          * Booleans are converted to "true" or "false", numbers are converted to their String representations, Strings are
 351  
          * surrounded in single quotes and their existing single quotes are doubled, Class objects are converted to their
 352  
          * getName() values and are left unquoted, null values are ignored, and all other Objects are assigned to an input
 353  
          * parameter. The fixed constant or parameter name will be appended to the end of the given StringBuilder.
 354  
          */
 355  
         private void fixValue(StringBuilder queryClause, Object value) {
 356  0
                 if (value == null) { return; }
 357  0
                 Class<?> propertyType = value.getClass();
 358  0
                 if(TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType)) {
 359  0
                         queryClause.append(value.toString());
 360  0
                 } else if (TypeUtils.isStringClass(propertyType)) {
 361  0
                         queryClause.append('\'').append(fixSingleQuotes(value.toString())).append('\'');
 362  0
                 } else if (TypeUtils.isBooleanClass(propertyType)) {
 363  0
                         queryClause.append(value.toString());
 364  0
                 } else if (value instanceof Class) {
 365  0
                         queryClause.append(((Class)value).getName());
 366  
                 } else {
 367  0
                         queryClause.append(addAttr("bind_param" + (++generatedBindParamCount), value));
 368  
                 }
 369  0
         }
 370  
         
 371  
         /*
 372  
          * Fixes the single quotes of the given String by doubling them. Note that this method
 373  
          * will *not* wrap the final value in single quotes.
 374  
          */
 375  
         private String fixSingleQuotes(String value) {
 376  0
                 return APOS_PAT.matcher(value).replaceAll("''");
 377  
         }
 378  
         
 379  
         /*
 380  
          * Fixes the periods of the given String by converting them to underscores.
 381  
          */
 382  
         private String fixPeriods(String value) {
 383  0
                 return value.replace('.', '_');
 384  
         }
 385  
         
 386  
         /*
 387  
          * Fixes the search pattern by converting all non-escaped lookup wildcards ("*" and "?") into their
 388  
          * respective JPQL wildcards ("%" and "_"). Any lookup wildcards escaped by a backslash are converted
 389  
          * into their non-backslashed equivalents.
 390  
          */
 391  
         private String fixSearchPattern(String value) {
 392  0
                 StringBuilder fixedPattern = new StringBuilder(value);
 393  0
                 int valueLen = value.length();
 394  
                 String lookupWildcard;
 395  
                 String escapedLookupWildcard;
 396  
                 char jpqlWildcard;
 397  
                 // Convert all non-escaped  wildcards.
 398  0
                 for (int i = 0; i < LOOKUP_WILDCARDS.length; i++) {
 399  0
                         lookupWildcard = LOOKUP_WILDCARDS[i];
 400  0
                         escapedLookupWildcard = ESCAPED_LOOKUP_WILDCARDS[i];
 401  0
                         jpqlWildcard = JPQL_WILDCARDS[i];
 402  0
                         int wildcardIndex = fixedPattern.indexOf(lookupWildcard);
 403  0
                         int escapedWildcardIndex = fixedPattern.indexOf(escapedLookupWildcard);
 404  0
                         while (wildcardIndex != -1) {
 405  0
                                 if (wildcardIndex == 0 || escapedWildcardIndex != wildcardIndex - 1) {
 406  0
                                         fixedPattern.setCharAt(wildcardIndex, jpqlWildcard);
 407  0
                                         wildcardIndex = fixedPattern.indexOf(lookupWildcard, wildcardIndex);
 408  
                                 } else {
 409  0
                                         fixedPattern.replace(escapedWildcardIndex, wildcardIndex + 1, lookupWildcard);
 410  0
                                         wildcardIndex = fixedPattern.indexOf(lookupWildcard, wildcardIndex);
 411  0
                                         escapedWildcardIndex = fixedPattern.indexOf(escapedLookupWildcard, wildcardIndex);
 412  
                                 }
 413  
                         }
 414  
                 }
 415  0
                 return fixedPattern.toString();
 416  
         }
 417  
         
 418  
         /*
 419  
          * Automatically appends a new " AND " to the WHERE clause if adding more than one condition to it,
 420  
          * and also appends the initial entity's alias and a '.' if the expression does not contain any
 421  
          * properly-referenced aliases (as defined in the description of this class).
 422  
          */
 423  
         private void preparePrefixIfNecessary(String attribute) {
 424  0
                 whereClause.append((whereClause.length() > 0) ? " AND " : "");
 425  0
                 if (!attribute.contains(JPA_ALIAS_PREFIX)) {
 426  0
                         whereClause.append(JPA_INITIAL_ALIAS).append('.');
 427  
                 }
 428  0
         }
 429  
         
 430  
         /*
 431  
          * Generates a new input parameter name and associates it with the given value, or (if the value
 432  
          * is a Class object) just returns the value's getName() String w/out adding it to a parameter. The
 433  
          * parameter's name will resemble the "attribute" String with the periods converted to
 434  
          * underscores, unless such a parameter already exists or "attribute" contains a properly-referenced
 435  
          * alias (in which case a new unique name will be auto-generated).
 436  
          * Returns the finalized input parameter name as a "__JPA_PARAM[['...']]__" String (with a ":" prefix)
 437  
          * if the value is not a Class; otherwise, returns the getName() String of the Class object.
 438  
          */
 439  
         private String prepareAttribute(String attribute, Object value) {
 440  0
                 return ( value instanceof Class ? ((Class)value).getName() :
 441  
                         ( attribute.contains(JPA_ALIAS_PREFIX) ? addAttr("bind_param" + (++generatedBindParamCount), value) : addAttr(fixPeriods(attribute), value) ) );
 442  
         }
 443  
         
 444  
         /*
 445  
          * Generates new input parameter names using "attribute" as a base, and associates each new
 446  
          * parameter with the corresponding provided value (unless the given value is a Class object,
 447  
          * in which case the Class's getName() String will be used w/out adding it to a parameter).
 448  
          * The parameters' names will resemble the "attribute" String (with the periods converted to
 449  
          * underscores) plus the suffix "_bN" (where N is the index of the value associated with the
 450  
          * parameter), unless such parameters already exist or "attribute" contains a
 451  
          * properly-referenced alias (in which case new unique names will be auto-generated).
 452  
          * Returns an array of parameter names as "__JPA_PARAM[['...']]__" Strings (with ":" prefixes),
 453  
          * where each name corresponds to the associated value at the same index, and where the returned
 454  
          * array has a length equal to the number of provided values (but note that if a given value is a
 455  
          * Class object, its getName() String will be returned at that index instead and no input
 456  
          * parameter will be added for it).
 457  
          */
 458  
         private String[] prepareAttributes(String attribute, Object... values) {
 459  0
                 int count = values.length;
 460  0
                 String[] fixedAttrs = new String[count];
 461  0
                 if (attribute.contains(JPA_ALIAS_PREFIX)) {
 462  0
                         int tempParamCount = ++generatedBindParamCount;
 463  0
                         for (int i = 0; i < count; i++) {
 464  0
                                 fixedAttrs[i] = (values[i] instanceof Class) ? ((Class)values[i]).getName() :
 465  
                                         addAttr(new StringBuilder(20).append("bind_param").append(tempParamCount).append("_b").append(i + 1).toString(), values[i]);
 466  
                         }
 467  0
                 } else {
 468  0
                         attribute = fixPeriods(attribute);
 469  0
                         for (int i = 0; i < count; i++) {
 470  0
                                 fixedAttrs[i] = (values[i] instanceof Class) ? ((Class)values[i]).getName() :
 471  
                                         addAttr(new StringBuilder(attribute.length() + 5).append(attribute).append("_b").append(i + 1).toString(), values[i]);
 472  
                         }
 473  
                 }
 474  0
                 return fixedAttrs;
 475  
         }
 476  
         
 477  
         /*
 478  
          * Automatically appends a new " AND " to the WHERE clause if adding more than one condition to it,
 479  
          * and also appends the initial entity's alias and a '.' if the expression does not contain any
 480  
          * properly-referenced aliases (as defined in the description of this class). In addition, a new
 481  
          * input parameter name is generated, and the parameter will be associated with the given value (unless
 482  
          * the object is a Class object, in which case its getName() value will be used instead and no input
 483  
          * parameter will be created for it). The parameter's name will resemble the "attribute" String with
 484  
          * the periods converted to underscores, unless such a parameter already exists or "attribute" contains
 485  
          * a properly-referenced alias (in which case a new unique name will be auto-generated).
 486  
          * Returns the finalized input parameter name as a "__JPA_PARAM[['...']]__" String (with a ":" prefix)
 487  
          * if it is not a Class object; otherwise, its getName() String will be returned instead.
 488  
          */
 489  
         private String preparePrefixAndAttributeIfNecessary(String attribute, Object value) {
 490  0
                 whereClause.append((whereClause.length() > 0) ? " AND " : "");
 491  0
                 if (value instanceof Class) {
 492  0
                         return ((Class)value).getName();
 493  0
                 } else if (attribute.contains(JPA_ALIAS_PREFIX)) {
 494  0
                         return addAttr("bind_param" + (++generatedBindParamCount), value);
 495  
                 } else {
 496  0
                         whereClause.append(JPA_INITIAL_ALIAS).append('.');
 497  0
                         return addAttr(fixPeriods(attribute), value);
 498  
                 }
 499  
         }
 500  
         
 501  
         /*
 502  
          * Adds a new input parameter and its value to the parameter Map, auto-generating a new key if the provided one already exists.
 503  
          * Returns the finalized key as a "__JPA_PARAM[['...']]__" String with a prepended ":".
 504  
          */
 505  
         private String addAttr(String string, Object value) {
 506  0
                 while (params.containsKey(string)) {
 507  0
                         string = "bind_param" + (++generatedBindParamCount);
 508  
                 }
 509  0
                 params.put(string, value);
 510  0
                 return new StringBuilder(45).append(JPA_PARAM_PREFIX_WITH_COLON).append('\'').append(string).append('\'').append(JPA_ALIAS_SUFFIX).toString();
 511  
         }
 512  
         
 513  
         /**
 514  
          * Adds a new BETWEEN condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 515  
          * 
 516  
          * @param attribute An expression representing the subject of the BETWEEN condition.
 517  
          * @param value1 The lower value of the BETWEEN condition.
 518  
          * @param value2 The upper value of the BETWEEN condition.
 519  
          */
 520  
         public void between(String attribute, Object value1, Object value2) {
 521  0
                 preparePrefixIfNecessary(attribute);
 522  0
                 String[] fixedAttrs = prepareAttributes(attribute, value1, value2);
 523  0
                 whereClause.append(attribute).append(" BETWEEN ").append(fixedAttrs[0]).append(" AND ").append(fixedAttrs[1]);
 524  0
         }
 525  
         
 526  
         /**
 527  
          * Adds a new NOT BETWEEN condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 528  
          * 
 529  
          * @param attribute An expression representing the subject of the NOT BETWEEN condition.
 530  
          * @param value1 The lower value of the NOT BETWEEN condition.
 531  
          * @param value2 The upper value of the NOT BETWEEN condition.
 532  
          */
 533  
         public void notBetween(String attribute, Object value1, Object value2) {
 534  0
                 preparePrefixIfNecessary(attribute);
 535  0
                 String[] fixedAttrs = prepareAttributes(attribute, value1, value2);
 536  0
                 whereClause.append(attribute).append(" NOT BETWEEN ").append(fixedAttrs[0]).append(" AND ").append(fixedAttrs[1]);
 537  0
         }
 538  
         
 539  
         /**
 540  
          * Adds an equality condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 541  
          * 
 542  
          * @param attribute An expression representing the left-hand side of this condition.
 543  
          * @param value The value to check for equality with.
 544  
          */
 545  
         public void eq(String attribute, Object value) {
 546  0
                 String fixedAttr = preparePrefixAndAttributeIfNecessary(attribute, value);
 547  0
                 whereClause.append(attribute).append(" = ").append(fixedAttr);
 548  0
         }
 549  
 
 550  
         /**
 551  
          * Adds a greater-than condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 552  
          * 
 553  
          * @param attribute An expression representing the left-hand side of this condition.
 554  
          * @param value The value on the right-hand side of the greater-than condition.
 555  
          */
 556  
         public void gt(String attribute, Object value) {
 557  0
                 String fixedAttr = preparePrefixAndAttributeIfNecessary(attribute, value);
 558  0
                 whereClause.append(attribute).append(" > ").append(fixedAttr);
 559  0
         }
 560  
 
 561  
         /**
 562  
          * Adds a greater-than-or-equal condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 563  
          * 
 564  
          * @param attribute An expression representing the left-hand side of this condition.
 565  
          * @param value The value on the right-hand side of the greater-than-or-equal condition.
 566  
          */
 567  
         public void gte(String attribute, Object value) {
 568  0
                 String fixedAttr = preparePrefixAndAttributeIfNecessary(attribute, value);
 569  0
                 whereClause.append(attribute).append(" >= ").append(fixedAttr);
 570  0
         }
 571  
 
 572  
         /**
 573  
          * Adds a new LIKE condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 574  
          * If the provided value is not a String, its toString() representation will be used as the pattern.
 575  
          * 
 576  
          * @param attribute An expression representing the left-hand side of this condition.
 577  
          * @param value The pattern to compare with for "likeness".
 578  
          */
 579  
         public void like(String attribute, Object value) {
 580  0
                 String fixedAttr = preparePrefixAndAttributeIfNecessary(attribute, fixSearchPattern(value.toString()));
 581  0
                 whereClause.append(attribute).append(" LIKE ").append(fixedAttr);
 582  0
         }
 583  
 
 584  
         /**
 585  
          * Adds a new NOT LIKE condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 586  
          * If the provided value is not a String, its toString() representation will be used as the pattern.
 587  
          * 
 588  
          * @param attribute An expression representing the left-hand side of this condition.
 589  
          * @param value The pattern to compare with for "non-likeness".
 590  
          */
 591  
         public void notLike(String attribute, Object value) {
 592  0
                 String fixedAttr = preparePrefixAndAttributeIfNecessary(attribute, fixSearchPattern(value.toString()));
 593  0
                 whereClause.append(attribute).append(" NOT LIKE ").append(fixedAttr);
 594  0
         }
 595  
 
 596  
         /**
 597  
          * Adds a new LIKE condition (containing an ESCAPE clause) to the WHERE clause, ANDing it with any
 598  
          * existing conditions if necessary.
 599  
          * If the provided value is not a String, its toString() representation will be used as the pattern.
 600  
          * 
 601  
          * @param attribute An expression representing the left-hand side of this condition.
 602  
          * @param value The pattern to compare with for "likeness".
 603  
          * @param escapeChar The designated wildcard escape character for the given value.
 604  
          */
 605  
         public void likeEscape(String attribute, Object value, char escapeChar) {
 606  0
                 String fixedAttr = preparePrefixAndAttributeIfNecessary(attribute, fixSearchPattern(value.toString()));
 607  0
                 whereClause.append(attribute).append(" LIKE ").append(fixedAttr).append(" ESCAPE ").append(
 608  
                                 prepareAttribute(JPA_ALIAS_PREFIX, Character.valueOf(escapeChar)));
 609  0
         }
 610  
         
 611  
         /**
 612  
          * Adds a new NOT LIKE condition (containing an ESCAPE clause) to the WHERE clause, ANDing it with any
 613  
          * existing conditions if necessary.
 614  
          * If the provided value is not a String, its toString() representation will be used as the pattern.
 615  
          * 
 616  
          * @param attribute An expression representing the left-hand side of this condition.
 617  
          * @param value The pattern to compare with for "non-likeness".
 618  
          * @param escapeChar The designated wildcard escape character for the given value.
 619  
          */
 620  
         public void notLikeEscape(String attribute, Object value, char escapeChar) {
 621  0
                 String fixedAttr = preparePrefixAndAttributeIfNecessary(attribute, fixSearchPattern(value.toString()));
 622  0
                 whereClause.append(attribute).append(" NOT LIKE ").append(fixedAttr).append(" ESCAPE ").append(
 623  
                                 prepareAttribute(JPA_ALIAS_PREFIX, Character.valueOf(escapeChar)));
 624  0
         }
 625  
         
 626  
         /**
 627  
          * Adds a less-than condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 628  
          * 
 629  
          * @param attribute An expression representing the left-hand side of this condition.
 630  
          * @param value The value on the right-hand side of the less-than condition.
 631  
          */
 632  
         public void lt(String attribute, Object value) {
 633  0
                 String fixedAttr = preparePrefixAndAttributeIfNecessary(attribute, value);
 634  0
                 whereClause.append(attribute).append(" < ").append(fixedAttr);
 635  0
         }
 636  
 
 637  
         /**
 638  
          * Adds a less-than-or-equal condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 639  
          * 
 640  
          * @param attribute An expression representing the left-hand side of this condition.
 641  
          * @param value The value on the right-hand side of the less-than-or-equal condition.
 642  
          */
 643  
         public void lte(String attribute, Object value) {
 644  0
                 String fixedAttr = preparePrefixAndAttributeIfNecessary(attribute, value);
 645  0
                 whereClause.append(attribute).append(" <= ").append(fixedAttr);
 646  0
         }
 647  
 
 648  
         /**
 649  
          * Adds a non-equality condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 650  
          * 
 651  
          * @param attribute An expression representing the left-hand side of this condition.
 652  
          * @param value The value on the right-hand side of the non-equality condition.
 653  
          */
 654  
         public void ne(String attribute, Object value) {
 655  0
                 String fixedAttr = preparePrefixAndAttributeIfNecessary(attribute, value);
 656  0
                 whereClause.append(attribute).append(" <> ").append(fixedAttr);
 657  0
         }
 658  
 
 659  
         /**
 660  
          * Adds an IS NULL condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 661  
          * 
 662  
          * @param attribute An expression representing what needs the null check.
 663  
          */
 664  
         public void isNull(String attribute) {
 665  0
                 preparePrefixIfNecessary(attribute);
 666  0
                 whereClause.append(attribute).append(" IS NULL");
 667  0
         }
 668  
 
 669  
         /**
 670  
          * Adds an IS NOT NULL condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 671  
          * 
 672  
          * @param attribute An expression representing what needs the not-null check.
 673  
          */
 674  
         public void notNull(String attribute) {
 675  0
                 preparePrefixIfNecessary(attribute);
 676  0
                 whereClause.append(attribute).append(" IS NOT NULL");
 677  0
         }
 678  
         
 679  
         /**
 680  
          * Adds a MEMBER OF condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 681  
          * Uses an input parameter for the single-value part of the condition.
 682  
          * 
 683  
          * @param value An Object that may or may not be in the specified collection.
 684  
          * @param collection An expression representing a collection.
 685  
          */
 686  
         public void memberOf(Object value, String collection) {
 687  0
                 String fixedAttr = prepareAttribute(collection, value);
 688  0
                 preparePrefixIfNecessary(JPA_ALIAS_PREFIX);
 689  0
                 whereClause.append(fixedAttr).append(" MEMBER OF ").append(
 690  
                                 collection.contains(JPA_ALIAS_PREFIX) ? "" : JPA_INITIAL_ALIAS + ".").append(collection);
 691  0
         }
 692  
         
 693  
         /**
 694  
          * Adds a MEMBER OF condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 695  
          * Uses an expression for the single-value part of the condition.
 696  
          * 
 697  
          * @param attribute An expression pointing to an attribute that may or may not be in the given collection.
 698  
          * @param collection An expression representing a collection.
 699  
          */
 700  
         public void memberOf(String attribute, String collection) {
 701  0
                 preparePrefixIfNecessary(attribute);
 702  0
                 whereClause.append(attribute).append(" MEMBER OF ").append(
 703  
                                 collection.contains(JPA_ALIAS_PREFIX) ? "" : JPA_INITIAL_ALIAS + ".").append(collection);
 704  0
         }
 705  
         
 706  
         /**
 707  
          * Adds a NOT MEMBER OF condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 708  
          * Uses an input parameter for the single-value part of the condition.
 709  
          * 
 710  
          * @param value An Object that may or may not be in the specified collection.
 711  
          * @param collection An expression representing a collection.
 712  
          */
 713  
         public void notMemberOf(Object value, String collection) {
 714  0
                 String fixedAttr = prepareAttribute(collection, value);
 715  0
                 preparePrefixIfNecessary(JPA_ALIAS_PREFIX);
 716  0
                 whereClause.append(fixedAttr).append(" NOT MEMBER OF ").append(
 717  
                                 collection.contains(JPA_ALIAS_PREFIX) ? "" : JPA_INITIAL_ALIAS + ".").append(collection);
 718  0
         }
 719  
         
 720  
         /**
 721  
          * Adds a NOT MEMBER OF condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 722  
          * Uses an expression for the single-value part of the condition.
 723  
          * 
 724  
          * @param attribute An expression pointing to an attribute that may or may not be in the given collection.
 725  
          * @param collection An expression representing a collection.
 726  
          */
 727  
         public void notMemberOf(String attribute, String collection) {
 728  0
                 preparePrefixIfNecessary(attribute);
 729  0
                 whereClause.append(attribute).append(" NOT MEMBER OF ").append(
 730  
                                 collection.contains(JPA_ALIAS_PREFIX) ? "" : JPA_INITIAL_ALIAS + ".").append(collection);
 731  0
         }
 732  
         
 733  
         /**
 734  
          * Adds an IS EMPTY condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 735  
          * 
 736  
          * @param collection An expression representing a collection.
 737  
          */
 738  
         public void isEmpty(String collection) {
 739  0
                 preparePrefixIfNecessary(collection);
 740  0
                 whereClause.append(collection).append(" IS EMPTY");
 741  0
         }
 742  
         
 743  
         /**
 744  
          * Adds an IS NOT EMPTY condition to the WHERE clause, ANDing it with any existing conditions if necessary.
 745  
          * 
 746  
          * @param collection An expression representing a collection.
 747  
          */
 748  
         public void notEmpty(String collection) {
 749  0
                 preparePrefixIfNecessary(collection);
 750  0
                 whereClause.append(collection).append(" IS NOT EMPTY");
 751  0
         }
 752  
         
 753  
         /**
 754  
          * Converts the given Criteria into a sub-SELECT query, surrounds it in parentheses, and adds a new
 755  
          * "IN (...)" condition containing the resulting expression into the WHERE clause, while ANDing
 756  
          * the new condition with any existing conditions if necessary.
 757  
          * 
 758  
          * @param attribute An expression representing the left-hand side of the "IN (...)" condition.
 759  
          * @param in The Criteria representing the sub-query of the "IN (...)" condition.
 760  
          */
 761  
         public void in(String attribute, Criteria in) {
 762  0
                 if (in == null) { throw new IllegalArgumentException("'IN' Criteria cannot be null"); }
 763  0
                 preparePrefixIfNecessary(attribute);
 764  0
                 whereClause.append(attribute).append(" IN ");
 765  0
                 subQuery(in);
 766  0
         }
 767  
         
 768  
         /**
 769  
          * Converts the given Criteria into a sub-SELECT query, surrounds it in parentheses, and adds a new
 770  
          * "NOT IN (...)" condition containing the resulting expression into the WHERE clause, while ANDing
 771  
          * the new condition with any existing conditions if necessary.
 772  
          * 
 773  
          * @param attribute An expression representing the left-hand side of the "NOT IN (...)" condition.
 774  
          * @param in The Criteria representing the sub-query of the "NOT IN (...)" condition.
 775  
          */
 776  
         public void notIn(String attribute, Criteria notIn) {
 777  0
                 if (notIn == null) { throw new IllegalArgumentException("'NOT IN' Criteria cannot be null"); }
 778  0
                 preparePrefixIfNecessary(attribute);
 779  0
                 whereClause.append(attribute).append(" NOT IN ");
 780  0
                 subQuery(notIn);
 781  0
         }
 782  
         
 783  
         /**
 784  
          * Takes the individual elements of the provided collection, converts them to literals or assigns them to input
 785  
          * parameters as needed, and adds a new "IN (...)" condition to the WHERE clause containing those elements,
 786  
          * ANDing it with any existing conditions if necessary. String, number, boolean, and Class collection elements
 787  
          * will be converted into literals, and all other non-null collection elements will be assigned to input parameters.
 788  
          * 
 789  
          * @param attribute An expression representing the left-hand side of the "IN (...)" condition.
 790  
          * @param values The collection of values to check against.
 791  
          */
 792  
         public void in(String attribute, Collection<?> values) {
 793  0
                 preparePrefixIfNecessary(attribute);
 794  0
                 whereClause.append(attribute).append(" IN (");
 795  0
                 appendBodyOfIn(values);
 796  0
                 whereClause.append(')');
 797  0
         }
 798  
         
 799  
         /**
 800  
          * Takes the individual elements of the provided collection, converts them to literals or assigns them to input
 801  
          * parameters as needed, and adds a new "IN (...)" condition to the WHERE clause containing those elements,
 802  
          * ANDing it with any existing conditions if necessary. String, number, boolean, and Class collection elements
 803  
          * will be converted into literals, and all other non-null collection elements will be assigned to input parameters.
 804  
          *  
 805  
          * @param attribute An expression representing the left-hand side of the "NOT IN (...)" condition.
 806  
          * @param values The collection of values to check against.
 807  
          */
 808  
         public void notIn(String attribute, Collection<?> values) {
 809  0
                 preparePrefixIfNecessary(attribute);
 810  0
                 whereClause.append(attribute).append(" NOT IN (");
 811  0
                 appendBodyOfIn(values);
 812  0
                 whereClause.append(')');
 813  0
         }
 814  
 
 815  
         /**
 816  
          * Takes the provided values, converts them to literals or assigns them to input parameters as needed, and adds
 817  
          * a new "IN (...)" condition to the WHERE clause containing those elements, ANDing it with any existing
 818  
          * conditions if necessary. String, number, boolean, and Class values will be converted into literals, and all
 819  
          * other non-null values will be assigned to input parameters.
 820  
          * 
 821  
          * @param attribute An expression representing the left-hand side of the "IN (...)" condition.
 822  
          * @param values The values to check against.
 823  
          */
 824  
         public void in(String attribute, Object... values) {
 825  0
                 preparePrefixIfNecessary(attribute);
 826  0
                 whereClause.append(attribute).append(" IN (");
 827  0
                 appendBodyOfIn(Arrays.asList(values));
 828  0
                 whereClause.append(')');
 829  0
         }
 830  
         
 831  
         /**
 832  
          * Takes the provided values, converts them to literals or assigns them to input parameters as needed, and adds
 833  
          * a new "NOT IN (...)" condition to the WHERE clause containing those elements, ANDing it with any existing
 834  
          * conditions if necessary. String, number, boolean, and Class values will be converted into literals, and all
 835  
          * other non-null values will be assigned to input parameters.
 836  
          * 
 837  
          * @param attribute An expression representing the left-hand side of the "NOT IN (...)" condition.
 838  
          * @param values The values to check against.
 839  
          */
 840  
         public void notIn(String attribute, Object... values) {
 841  0
                 preparePrefixIfNecessary(attribute);
 842  0
                 whereClause.append(attribute).append(" NOT IN (");
 843  0
                 appendBodyOfIn(Arrays.asList(values));
 844  0
                 whereClause.append(')');
 845  0
         }
 846  
         
 847  
         /*
 848  
          * Creates the body of an "IN (...)" condition using the provided collection of values.
 849  
          * String, number, and boolean values are converted into literals, and all other
 850  
          * non-null values are assigned to input parameters.
 851  
          */
 852  
         private void appendBodyOfIn(Collection<?> values) {
 853  0
                 Iterator<?> valuesIter = values.iterator();
 854  0
                 if (valuesIter.hasNext()) {
 855  0
                         fixValue(whereClause, valuesIter.next());
 856  0
                         while (valuesIter.hasNext()) {
 857  0
                                 whereClause.append(", ");
 858  0
                                 fixValue(whereClause, valuesIter.next());
 859  
                         }
 860  
                 }
 861  0
         }
 862  
         
 863  
         /**
 864  
          * Takes the conditions from the given Criteria's WHERE clause, surrounds them in parentheses, and ANDs
 865  
          * the resulting expression with this instance's WHERE clause (or makes the expression become the new
 866  
          * WHERE clause of this instance if its current WHERE clause is empty). Any input parameters from the
 867  
          * given Criteria will be copied over to this Criteria, and will be renamed as needed if conflicts arise.
 868  
          * 
 869  
          * @param and The Criteria instance whose conditions should be ANDed with those in this Criteria instance.
 870  
          */
 871  
         public void and(Criteria and) {
 872  0
                 if (and == null) { throw new IllegalArgumentException("'AND' Criteria cannot be null"); }
 873  0
                 if (and.whereClause.length() > 0) {
 874  0
                         int oldLen = whereClause.length();
 875  0
                         preparePrefixIfNecessary(JPA_ALIAS_PREFIX);
 876  0
                         whereClause.append('(').append(and.whereClause).append(')');
 877  0
                         copyParams(and, oldLen, true);
 878  
                 }
 879  0
         }
 880  
 
 881  
         /**
 882  
          * Takes the conditions from the given Criteria's WHERE clause, surrounds them in parentheses, and adds
 883  
          * a new "NOT (...)" condition containing the resulting expression to this instance's WHERE clause (or
 884  
          * adds a new "AND NOT(...)" condition if the WHERE clause on the existing Criteria is not empty). Any
 885  
          * input parameters from the given Criteria will be copied over to this Criteria, and will be renamed
 886  
          * as needed if conflicts arise.
 887  
          * 
 888  
          * @param not The Criteria instance whose group of conditions should be negated.
 889  
          */
 890  
         public void not(Criteria not) {
 891  0
                 if (not == null) { throw new IllegalArgumentException("'NOT' Criteria cannot be null"); }
 892  0
                 if (not.whereClause.length() > 0) {
 893  0
                         int oldLen = whereClause.length();
 894  0
                         preparePrefixIfNecessary(JPA_ALIAS_PREFIX);
 895  0
                         whereClause.append("NOT (").append(not.whereClause).append(')');
 896  0
                         copyParams(not, oldLen, true);
 897  
                 }
 898  0
         }
 899  
         
 900  
         /**
 901  
          * Takes the conditions from the given Criteria's WHERE clause, surrounds them in parentheses, and ORs
 902  
          * the resulting expression with this instance's WHERE clause (or makes the expression become the new
 903  
          * WHERE clause of this instance if its current WHERE clause is empty). Any input parameters from the
 904  
          * given Criteria will be copied over to this Criteria, and will be renamed as needed if conflicts arise.
 905  
          * 
 906  
          * @param or The Criteria instance whose conditions should be ORed with those in this Criteria instance.
 907  
          */
 908  
         public void or(Criteria or) {
 909  0
                 if (or == null) { throw new IllegalArgumentException("'OR' Criteria cannot be null"); }
 910  0
                 if (or.whereClause.length() > 0) {
 911  0
                         int oldLen = whereClause.length();
 912  0
                         whereClause.append((oldLen > 0) ? " OR (" : "(").append(or.whereClause).append(')');
 913  0
                         copyParams(or, oldLen, true);
 914  
                 }
 915  0
         }
 916  
         
 917  
         /**
 918  
          * Takes the conditions from the given Criteria's WHERE clause, surrounds them in parentheses, and adds
 919  
          * a new "OR NOT (...)" condition containing the resulting expression to this instance's WHERE clause (or
 920  
          * just adds a new "NOT (...)" condition if its current WHERE clause is empty). Any input parameters from the
 921  
          * given Criteria will be copied over to this Criteria, and will be renamed as needed if conflicts arise.
 922  
          * 
 923  
          * @param orNot The Criteria instance whose group of conditions should be negated and then ORed with those in this Criteria instance.
 924  
          */
 925  
         public void orNot(Criteria orNot) {
 926  0
                 if (orNot == null) { throw new IllegalArgumentException("'OR NOT' Criteria cannot be null"); }
 927  0
                 if (orNot.whereClause.length() > 0) {
 928  0
                         int oldLen = whereClause.length();
 929  0
                         whereClause.append((oldLen > 0) ? " OR NOT (" : "NOT (").append(orNot.whereClause).append(')');
 930  0
                         copyParams(orNot, oldLen, true);
 931  
                 }
 932  0
         }
 933  
         
 934  
         /**
 935  
          * Converts the given Criteria into a sub-SELECT query, surrounds it in parentheses, and adds a new
 936  
          * EXISTS condition containing the resulting expression into the WHERE clause, while ANDing the new
 937  
          * condition with any existing conditions if necessary.
 938  
          * 
 939  
          * @param exists The Criteria instance representing the sub-query of the EXISTS condition.
 940  
          */
 941  
         public void exists(Criteria exists) {
 942  0
                 if (exists == null) { throw new IllegalArgumentException("'EXISTS' Criteria cannot be null"); }
 943  0
                 preparePrefixIfNecessary(JPA_ALIAS_PREFIX);
 944  0
                 whereClause.append("EXISTS ");
 945  0
                 subQuery(exists);
 946  0
     }
 947  
 
 948  
         /**
 949  
          * Converts the given Criteria into a sub-SELECT query, surrounds it in parentheses, and adds a new
 950  
          * NOT EXISTS condition containing the resulting expression into the WHERE clause, while ANDing the
 951  
          * new condition with any existing conditions if necessary.
 952  
          * 
 953  
          * @param exists The Criteria instance representing the sub-query of the NOT EXISTS condition.
 954  
          */
 955  
         public void notExists(Criteria notExists) {
 956  0
                 if (notExists == null) { throw new IllegalArgumentException("'NOT EXISTS' Criteria cannot be null"); }
 957  0
                 preparePrefixIfNecessary(JPA_ALIAS_PREFIX);
 958  0
                 whereClause.append("NOT EXISTS ");
 959  0
                 subQuery(notExists);
 960  0
     }
 961  
         
 962  
         /**
 963  
          * Inserts raw JPQL into the WHERE clause. An extra whitespace character will automatically be prepended
 964  
          * (if the WHERE clause is non-empty) and appended to the JPQL string.
 965  
          * 
 966  
          * @param jpql The raw JPQL to insert.
 967  
          */
 968  
         public void rawJpql(String jpql) {
 969  0
                 whereClause.append((whereClause.length() > 0) ? " " : "").append(jpql).append(' ');
 970  0
         }
 971  
         
 972  
         /**
 973  
          * Adds a new expression to the GROUP BY clause. If no properly-referenced alias appears in
 974  
          * the String expression, it will be assumed that the expression points to something
 975  
          * that is accessible on or through the initial entity. See the description of this class
 976  
          * for more information on how String expressions are interpreted.
 977  
          * 
 978  
          * @param attribute The expression representing what the query results should be grouped by.
 979  
          */
 980  
         public void groupBy(String attribute) {
 981  0
                 groupByClause.append((groupByClause.length() > 0) ? ", " : " GROUP BY ");
 982  0
                 if (!attribute.contains(JPA_ALIAS_PREFIX)) {
 983  0
                         groupByClause.append(JPA_INITIAL_ALIAS).append('.');
 984  
                 }
 985  0
                 groupByClause.append(attribute);
 986  0
         }
 987  
         
 988  
         /**
 989  
          * Takes the conditions from the given Criteria's WHERE clause and then ANDs them with this
 990  
          * instance's HAVING clause (or makes them become the new HAVING clause of this instance if
 991  
          * its current HAVING clause is empty). Any input parameters from the given Criteria will be
 992  
          * copied over to this Criteria, and will be renamed as needed if conflicts arise.
 993  
          * 
 994  
          * @param having The Criteria instance whose WHERE clause should represent this instance's HAVING clause (or a piece of it).
 995  
          */
 996  
         public void having(Criteria having) {
 997  0
                 if (having == null) { throw new IllegalArgumentException("'HAVING' Criteria cannot be null"); }
 998  0
                 if (having.whereClause.length() > 0) {
 999  0
                         int oldLen = havingClause.length();
 1000  0
                         havingClause.append((oldLen > 0) ? " AND " : " HAVING ").append(having.whereClause);
 1001  0
                         copyParams(having, oldLen, false);
 1002  
                 }
 1003  0
         }
 1004  
         
 1005  
         /**
 1006  
          * Adds a new expression to the ORDER BY clause. If no properly-referenced alias appears in
 1007  
          * the String expression, it will be assumed that the expression points to something
 1008  
          * that is accessible on or through the initial entity. See the description of this class
 1009  
          * for more information on how String expressions are interpreted.
 1010  
          * 
 1011  
          * @param attribute The expression representing what the query results should be ordered by.
 1012  
          * @param sortAscending If true, the order will be ascending; otherwise, it will be descending.
 1013  
          */
 1014  
         public void orderBy(String attribute, boolean sortAscending) {
 1015  0
                 orderByClause.append((orderByClause.length() > 0) ? ", " : " ORDER BY ");
 1016  0
                 if (!attribute.contains(JPA_ALIAS_PREFIX)) {
 1017  0
                         orderByClause.append(JPA_INITIAL_ALIAS).append('.');
 1018  
                 }
 1019  0
                 orderByClause.append(attribute).append(sortAscending ? " ASC" : " DESC");
 1020  0
         }
 1021  
         
 1022  
         /*
 1023  
          * Copies the input parameters from the given child Criteria to the current Criteria. Any parameter placeholders from the sub-query will
 1024  
          * be renamed accordingly if they conflict with those defined by the parent Criteria.
 1025  
          * 
 1026  
          * It is assumed that the sub-query has already been appended to the clause given by modifyWhereClause
 1027  
          * (WHERE if true, HAVING if false), starting at the index given by startIndex.
 1028  
          */
 1029  
         private void copyParams(Criteria subCriteria, int startIndex, boolean modifyWhereClause) {
 1030  0
                 final StringBuilder tempClause = modifyWhereClause ? whereClause : havingClause;
 1031  0
                 Map<String,String> modifiedNames = new HashMap<String,String>();
 1032  0
                 int paramClose = 0;
 1033  0
                 int paramIndex = tempClause.indexOf(JPA_PARAM_PREFIX_WITH_COLON, startIndex);
 1034  0
                 String newPlaceholder = null;
 1035  
                 
 1036  0
                 for (Map.Entry<String,Object> param : subCriteria.params.entrySet()) {
 1037  0
                         if (params.containsKey(param.getKey())) {
 1038  0
                                 modifiedNames.put(param.getKey(), addAttr(param.getKey(), param.getValue()));
 1039  
                         } else {
 1040  0
                                 params.put(param.getKey(), param.getValue());
 1041  
                         }
 1042  
                 }
 1043  
                 
 1044  0
                 while (paramIndex >= 0) {
 1045  0
                         paramClose = tempClause.indexOf(JPA_ALIAS_SUFFIX, paramIndex);
 1046  0
                         newPlaceholder = modifiedNames.get(tempClause.substring(paramIndex + PARAM_PREFIX_LEN + 2, paramClose - 1));
 1047  0
                         if (newPlaceholder != null) {
 1048  0
                                 tempClause.replace(paramIndex, paramClose + ALIAS_SUFFIX_LEN, newPlaceholder);
 1049  
                         }
 1050  0
                         paramIndex = tempClause.indexOf(JPA_PARAM_PREFIX_WITH_COLON, paramIndex + 1);
 1051  
                 }
 1052  0
         }
 1053  
         
 1054  
         /*
 1055  
          * Converts the given Criteria into a sub-query and appends it to the WHERE clause. Any named aliases of the sub-query will be renamed
 1056  
          * accordingly if they conflict with those defined by the parent Criteria, and all indexed aliases will be updated accordingly. In
 1057  
          * addition, the input parameters will be copied over and renamed as needed.
 1058  
          */
 1059  
         private void subQuery(Criteria subCriteria) {
 1060  0
                 Map<String,String> modifiedAliases = new HashMap<String,String>();
 1061  0
                 int count = subCriteria.entityAliases.size();
 1062  0
                 int paramIndex = 0;
 1063  0
                 int paramClose = 0;
 1064  0
                 String newPlaceholder = null;
 1065  
 
 1066  0
                 for (int i = entityAliases.size() - 1; i >= 0; i--) {
 1067  0
                         modifiedAliases.put(JPA_ALIAS_PREFIX + (~i) + JPA_ALIAS_SUFFIX, indexedAliasPlaceholders.get(i));
 1068  
                 }
 1069  0
                 for (int i = 0; i < count; i++) {
 1070  0
                         String oldAlias = subCriteria.entityAliases.get(i);
 1071  0
                         if (!addAlias(oldAlias).equals(oldAlias)) {
 1072  0
                                 modifiedAliases.put(subCriteria.namedAliasPlaceholders.get(i), namedAliasPlaceholders.get(namedAliasPlaceholders.size() - 1));
 1073  
                         }
 1074  0
                         modifiedAliases.put(subCriteria.indexedAliasPlaceholders.get(i), indexedAliasPlaceholders.get(indexedAliasPlaceholders.size() - 1));
 1075  
                 }
 1076  
                 
 1077  0
                 count = whereClause.length();
 1078  0
                 whereClause.append(subCriteria.distinct ? "(SELECT DISTINCT " : "(SELECT ").append(subCriteria.selectClause).append(subCriteria.fromClause).append(
 1079  
                                 (subCriteria.whereClause.length() > 0) ? " WHERE " : "").append(subCriteria.whereClause).append(
 1080  
                                                 subCriteria.groupByClause).append(subCriteria.havingClause).append(')');
 1081  
                 
 1082  0
                 paramIndex = whereClause.indexOf(JPA_ALIAS_PREFIX, count);
 1083  0
                 while (paramIndex >= 0) {
 1084  0
                         paramClose = whereClause.indexOf(JPA_ALIAS_SUFFIX, paramIndex) + ALIAS_SUFFIX_LEN;
 1085  0
                         newPlaceholder = modifiedAliases.get(whereClause.substring(paramIndex, paramClose));
 1086  0
                         if (newPlaceholder != null) {
 1087  0
                                 whereClause.replace(paramIndex, paramClose, newPlaceholder);
 1088  
                         }
 1089  0
                         paramIndex = whereClause.indexOf(JPA_ALIAS_PREFIX, paramIndex + 1);
 1090  
                 }
 1091  0
                 copyParams(subCriteria, count, true);
 1092  0
         }
 1093  
         
 1094  
         /**
 1095  
          * Converts the current Criteria instance into a JPQL query.
 1096  
          * 
 1097  
          * @param type An indicator of what type of query should be generated (SELECT, UPDATE, or DELETE).
 1098  
          * @return A String representing the JPQL query generated by this Criteria instance.
 1099  
          */
 1100  
         public String toQuery(QueryByCriteria.QueryByCriteriaType type) {
 1101  0
                 return toQuery(type, new String[0]);
 1102  
         }
 1103  
         
 1104  
         /**
 1105  
          * Converts the current Criteria instance into a JPQL query. If a SELECT query is desired, then
 1106  
          * alternative String expressions to insert into the SELECT clause can be specified, though doing
 1107  
          * so is not required and must be done with care (see below).
 1108  
          * 
 1109  
          * <p>WARNING! If queryAttr is non-null and has a length greater than zero, and if a SELECT query is
 1110  
          * desired, then this method will completely overwrite the SELECT clause so that it only contains
 1111  
          * the expressions given by the queryAttr array.
 1112  
          * 
 1113  
          * @param type An indicator of what type of query should be generated (SELECT, UPDATE, or DELETE).
 1114  
          * @param queryAttr (Optional) An array of String expressions to use as the values to be returned by the SELECT clause (if creating a SELECT query).
 1115  
          * @return A String representing the JPQL query generated by this Criteria instance.
 1116  
          */
 1117  
         public String toQuery(QueryByCriteria.QueryByCriteriaType type, String[] queryAttr) {
 1118  
                 StringBuilder newQuery;
 1119  0
                 int querySize = selectClause.length() + fromClause.length() + whereClause.length() + groupByClause.length() + havingClause.length() + orderByClause.length() + 7;
 1120  
                 // Construct the Criteria appropriately.
 1121  0
                 switch (type) {
 1122  
                         case SELECT :
 1123  0
                                 if (queryAttr != null && queryAttr.length > 0) {
 1124  0
                                         if (selectClause.length() > 0) { selectClause.delete(0, selectClause.length()); }
 1125  0
                                         for (int i = 0; i < queryAttr.length; i++) {
 1126  0
                                                 select(queryAttr[i]);
 1127  
                                         }
 1128  
                                 }
 1129  0
                                 newQuery = new StringBuilder(querySize + (distinct ? 16 : 7)).append(distinct ? "SELECT DISTINCT " : "SELECT ").append(
 1130  
                                                 selectClause).append(fromClause).append(whereClause.length() > 0 ? " WHERE " : "").append(whereClause).append(
 1131  
                                                                 groupByClause).append(havingClause).append(orderByClause);
 1132  0
                                 break;
 1133  
                         case UPDATE :
 1134  0
                                 newQuery = new StringBuilder(querySize + setClause.length() + 7).append("UPDATE ").append(initialEntityName).append(" AS ").append(
 1135  
                                                 getAlias()).append(setClause).append(whereClause.length() > 0 ? " WHERE " : "").append(whereClause);
 1136  0
                                 break;
 1137  
                         case DELETE :
 1138  0
                                 newQuery = new StringBuilder(querySize + 6).append("DELETE").append(fromClause).append(
 1139  
                                                 whereClause.length() > 0 ? " WHERE " : "").append(whereClause);
 1140  0
                                 break;
 1141  
                         default :
 1142  0
                                 return null;
 1143  
                 }
 1144  0
                 return fix(newQuery);
 1145  
         }
 1146  
         
 1147  
         /**
 1148  
          * Converts the current Criteria instance into a JPQL SELECT query, but replaces the existing SELECT clause with "SELECT COUNT(*)" instead.
 1149  
          * 
 1150  
          * @return A JPQL query String given by this Criteria, but with "SELECT COUNT(*)" as the SELECT clause instead.
 1151  
          */
 1152  
         public String toCountQuery() {
 1153  0
                 StringBuilder newQuery = new StringBuilder(
 1154  
                                 fromClause.length() + whereClause.length() + groupByClause.length() + havingClause.length() + orderByClause.length() + 15 + 7).append(
 1155  
                                                 "SELECT COUNT(*)").append(fromClause).append(whereClause.length() > 0 ? " WHERE " : "").append(whereClause).append(
 1156  
                                                                 groupByClause).append(havingClause).append(orderByClause);
 1157  0
                 return fix(newQuery);
 1158  
         }
 1159  
 
 1160  
         /*
 1161  
          * Converts all the named and indexed alias/parameter placeholders into the actual aliases/parameters.
 1162  
          * You can view the resulting JPQL String in the logs by configuring a log4j DEBUG output level for
 1163  
          * this class.
 1164  
          */
 1165  
         private String fix(StringBuilder newQuery) {
 1166  0
                 Map<String,String> modifiedAliases = new HashMap<String,String>();
 1167  0
                 for (int i = entityAliases.size() - 1; i >= 0; i--) {
 1168  0
                         modifiedAliases.put(indexedAliasPlaceholders.get(i), entityAliases.get(i));
 1169  0
                         modifiedAliases.put(namedAliasPlaceholders.get(i), entityAliases.get(i));
 1170  
                 }
 1171  
                 
 1172  
                 // resolve the aliases.
 1173  0
                 String newParam = null;
 1174  0
                 int paramClose = 0;
 1175  0
                 int paramIndex = newQuery.indexOf(JPA_ALIAS_PREFIX);
 1176  0
                 while (paramIndex >= 0) {
 1177  0
                         paramClose = newQuery.indexOf(JPA_ALIAS_SUFFIX, paramIndex) + ALIAS_SUFFIX_LEN;
 1178  0
                         newParam = modifiedAliases.get(newQuery.substring(paramIndex, paramClose));
 1179  0
                         if (newParam != null) {
 1180  0
                                 newQuery.replace(paramIndex, paramClose, newParam);
 1181  
                         } else {
 1182  0
                                 LOG.error("Detected an unresolvable JPA alias when constructing query: " + newQuery.substring(paramIndex, paramClose));
 1183  0
                                 throw new IllegalStateException("Detected an unresolvable alias: " + newQuery.substring(paramIndex, paramClose));
 1184  
                         }
 1185  0
                         paramIndex = newQuery.indexOf(JPA_ALIAS_PREFIX, paramIndex + 1);
 1186  
                 }
 1187  
                 
 1188  
                 // Resolve the parameters.
 1189  0
                 paramIndex = newQuery.indexOf(JPA_PARAM_PREFIX);
 1190  0
                 while (paramIndex >= 0) {
 1191  0
                         paramClose = newQuery.indexOf(JPA_ALIAS_SUFFIX, paramIndex);
 1192  0
                         newParam = newQuery.substring(paramIndex + PARAM_PREFIX_LEN + 1, paramClose - 1);
 1193  0
                         if (params.containsKey(newParam)) {
 1194  0
                                 newQuery.replace(paramIndex, paramClose + ALIAS_SUFFIX_LEN, newParam);
 1195  
                         } else {
 1196  0
                                 LOG.error("Detected an unresolvable input parameter when constructing query: " + newQuery.substring(paramIndex, paramClose + ALIAS_SUFFIX_LEN));
 1197  0
                                 throw new IllegalStateException("Detected an unresolvable input parameter: " + newQuery.substring(paramIndex, paramClose + ALIAS_SUFFIX_LEN));
 1198  
                         }
 1199  0
                         paramIndex = newQuery.indexOf(JPA_PARAM_PREFIX, paramIndex + 1);
 1200  
                 }
 1201  
                 
 1202  0
                 String finishedQuery = newQuery.toString();
 1203  0
                 if (LOG.isDebugEnabled()) {
 1204  0
                         LOG.debug("********** SEARCH JPQL QUERY **********");
 1205  0
                         LOG.debug(finishedQuery);
 1206  0
                         LOG.debug("***************************************");
 1207  
                 }
 1208  0
                 return finishedQuery;
 1209  
         }
 1210  
         
 1211  
         // Keep this package access so the QueryByCriteria can call it from this package.
 1212  
         /**
 1213  
          * Populates the given Query instance with the parameters stored in this Criteria. It is assumed that the given query
 1214  
          * is using this Criteria's toQuery() or toCountQuery() value as its JPQL String.
 1215  
          * 
 1216  
          * @param query A JPA Query instance containing this Criteria's toQuery() or toCountQuery() value for its JPQL String.
 1217  
          */
 1218  
         public void prepareParameters(Query query) {
 1219  0
                 for (Map.Entry<String, Object> param : params.entrySet()) {
 1220  0
                         Object value = param.getValue();
 1221  0
                         if (value != null) {
 1222  0
                                 if (value instanceof String) {
 1223  0
                                         if (query.getParameter(param.getKey()).getParameterType().equals(java.lang.Boolean.class)) {
 1224  0
                                                 value = Truth.strToBooleanIgnoreCase((String) value, Boolean.FALSE);
 1225  
                                         } else {
 1226  
                                                 //value = ((String)value).replaceAll("\\*", "%");
 1227  
                                         }
 1228  
                                 }
 1229  0
                                 query.setParameter(param.getKey(), value);
 1230  
                         }
 1231  0
                 }
 1232  0
         }
 1233  
         
 1234  
         /**
 1235  
          * Retrieves the current limit on the result set size for this Criteria, if any (only relevant if creating a SELECT query).
 1236  
          * 
 1237  
          * @return The current limit on the number of results to be returned by this Criteria's query, or null (default) if no such limit has been set.
 1238  
          */
 1239  
         public Integer getSearchLimit() {
 1240  0
                 return this.searchLimit;
 1241  
         }
 1242  
 
 1243  
         /**
 1244  
          * Sets the current limit on the result set size for this Criteria (only relevant if creating a SELECT query).
 1245  
          * 
 1246  
          * @param searchLimit The new limit on the number of results to be returned by this Criteria's query.
 1247  
          */
 1248  
         public void setSearchLimit(Integer searchLimit) {
 1249  0
                 this.searchLimit = searchLimit;
 1250  0
         }
 1251  
 
 1252  
         /**
 1253  
          * Sets whether or not the query should include the DISTINCT keyword in the SELECT clause, if creating a SELECT query.
 1254  
          * If this property is not set explicitly, it is assumed that DISTINCT should *not* be included in the SELECT clause.
 1255  
          * 
 1256  
          * @param distinct An indicator for whether to do a SELECT DISTINCT or just a SELECT.
 1257  
          */
 1258  
         public void distinct(boolean distinct){
 1259  0
                 this.distinct = distinct;
 1260  0
         }
 1261  
 
 1262  
         /**
 1263  
          * Retrieves the initial alias of this Criteria instance.
 1264  
          * 
 1265  
          * @return The initial alias.
 1266  
          */
 1267  
     public String getAlias() {
 1268  0
         return this.entityAliases.get(0);
 1269  
     }
 1270  
     
 1271  
     /**
 1272  
      * Retrieves the alias associated with the given index.
 1273  
      * 
 1274  
      * @param index The index pointing to a given alias.
 1275  
      * @return The alias at the given index.
 1276  
      */
 1277  
     public String getAlias(int index) {
 1278  0
             return this.entityAliases.get(index);
 1279  
     }
 1280  
     
 1281  
     /**
 1282  
      * Retrieves the index that points to the given alias.
 1283  
      * 
 1284  
      * @param alias The indexed alias.
 1285  
      * @return The index of the alias, or -1 if no such index was found.
 1286  
      */
 1287  
     public int getAliasIndex(String alias) {
 1288  0
             Integer tempIndex = aliasIndexes.get(alias);
 1289  0
             return (tempIndex != null) ? tempIndex.intValue() : -1;
 1290  
     }
 1291  
     
 1292  
     /**
 1293  
      * Retrieves a copy of all the aliases defined for this Criteria instance.
 1294  
      * The index of a given alias corresponds to the index at which the alias
 1295  
      * is located at in the returned List.
 1296  
      * 
 1297  
      * @return A List containing all the defined aliases of this Criteria instance.
 1298  
      */
 1299  
     public List<String> getAliases() {
 1300  0
             return new ArrayList(this.entityAliases);
 1301  
     }
 1302  
     
 1303  
     /**
 1304  
      * @return the name of the class of the initial Entity this Criteria will search for
 1305  
      */
 1306  
     public String getEntityName() {
 1307  0
             return this.initialEntityName;
 1308  
     }        
 1309  
 }