0001: /*
0002: * Copyright 2004-2007 Gary Bentley
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License"); you may
0005: * not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: * http://www.apache.org/licenses/LICENSE-2.0
0008: *
0009: * Unless required by applicable law or agreed to in writing, software
0010: * distributed under the License is distributed on an "AS IS" BASIS,
0011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0012: * See the License for the specific language governing permissions and
0013: * limitations under the License.
0014: */
0015: package org.josql;
0016:
0017: import java.io.StringReader;
0018: import java.io.BufferedReader;
0019:
0020: import java.util.Map;
0021: import java.util.HashMap;
0022: import java.util.SortedMap;
0023: import java.util.LinkedHashMap;
0024: import java.util.List;
0025: import java.util.ArrayList;
0026: import java.util.Iterator;
0027: import java.util.Collections;
0028: import java.util.Comparator;
0029: import java.util.LinkedHashSet;
0030: import java.util.Collection;
0031:
0032: import org.josql.parser.JoSQLParser;
0033:
0034: import org.josql.expressions.*;
0035:
0036: import org.josql.functions.*;
0037:
0038: import org.josql.internal.*;
0039:
0040: import org.josql.events.*;
0041:
0042: /**
0043: * This class provides the ability for a developer to apply an arbitrary SQL statement
0044: * (using suitable syntax) to a collection of Java objects.
0045: * <p>
0046: * Basic usage:
0047: * <pre>
0048: * Query q = new Query ();
0049: * q.parse (myStatement);
0050: * List results = q.execute (myObjects);
0051: * </pre>
0052: * <p>
0053: * An example statement would look like:
0054: * <pre>
0055: * SELECT lastModified,
0056: * name
0057: * FROM java.io.File
0058: * WHERE name LIKE '%.html'
0059: * </pre>
0060: * <p>
0061: * The JoSQL functionality is large and complex, whilst basic queries like the one above are
0062: * perfectly possible, very complex queries are also possible, for example:
0063: * <pre>
0064: * SELECT name,
0065: * formatDate(lastModified),
0066: * formatNumber(length),
0067: * formatNumber(length - @avg_length),
0068: * formatTimeDuration(@max_last_modified - lastModified)
0069: * FROM java.io.File
0070: * WHERE lastModified > @avg_last_modified
0071: * AND length > @avg_length
0072: * AND lower(name) LIKE '%.html'
0073: * GROUP BY path
0074: * ORDER BY name, lastModified DESC
0075: * EXECUTE ON ALL avg (:_allobjs, length) avg_length,
0076: * avg (:_allobjs, lastModified) avg_last_modified,
0077: * max (:_allobjs, lastModified) max_last_modified
0078: * </pre>
0079: * <p>
0080: * Note: the "EXECUTE ON ALL" syntax is an extension used by JoSQL because it has no notion
0081: * of "aggregate functions".
0082: * <p>
0083: * For full details of how a query works and what is possible, see the
0084: * <a href="http://josql.sourceforge.net/manual/index.html">JoSQL User Manual</a>.
0085: * <p>
0086: * Please note that the package structure for JoSQL is deliberate, JoSQL is designed to be a
0087: * black box and lightweight thus only the <code>Query</code> object and associated exceptions
0088: * are exposed in the main package. Also, the class structure for JoSQL is not designed to
0089: * exactly represent the SQL statement passed to it, rather the classes are optimised for
0090: * ease of execution of the statement. If you wish to have a completely accurate Java object
0091: * view of ANY SQL statement then please see:
0092: * <a href="http://sourceforge.net/projects/jsqlparser">JSqlParser</a>
0093: * which will provide what you need.
0094: */
0095: public class Query {
0096:
0097: public static String QUERY_BIND_VAR_NAME = "_query";
0098: public static String PARENT_BIND_VAR_NAME = "_parent";
0099: public static String CURR_OBJ_VAR_NAME = "_currobj";
0100: public static String ALL_OBJS_VAR_NAME = "_allobjs";
0101: public static String GRPBY_OBJ_VAR_NAME = "_grpby";
0102: public static String GRPBY_OBJ_VAR_NAME_SYNONYM = "_groupby";
0103: public static final String INT_BIND_VAR_PREFIX = "^^^";
0104:
0105: public static final String ALL = "ALL";
0106: public static final String RESULTS = "RESULTS";
0107: public static final String GROUP_BY_RESULTS = "GROUP_BY_RESULTS";
0108: public static final String WHERE_RESULTS = "WHERE_RESULTS";
0109: public static final String HAVING_RESULTS = "HAVING_RESULTS";
0110:
0111: public static final String ORDER_BY_ASC = "ASC";
0112: public static final String ORDER_BY_DESC = "DESC";
0113:
0114: public static final List nullQueryList = new ArrayList();
0115:
0116: static {
0117:
0118: Query.nullQueryList.add(new Object());
0119:
0120: }
0121:
0122: private List bfhs = new ArrayList();
0123: private Map bfhsMap = new HashMap();
0124:
0125: private char wildcardChar = '%';
0126:
0127: private Map aliases = new HashMap();
0128: private List groupBys = null;
0129: private Comparator orderByComp = null;
0130: private Comparator groupOrderByComp = null;
0131: private Grouper grouper = null;
0132: private List orderBys = null;
0133: private List groupOrderBys = null;
0134: private List cols = null;
0135: private boolean retObjs = false;
0136: private Expression where = null;
0137: private Expression having = null;
0138: private Map bindVars = null;
0139: private String query = null;
0140: private boolean wantTimings = false;
0141: private List functionHandlers = null;
0142: private int anonVarIndex = 1;
0143: private Expression from = null;
0144: private Class objClass = null;
0145: private Limit limit = null;
0146: private Limit groupByLimit = null;
0147: private Map executeOn = null;
0148: private boolean isParsed = false;
0149: private boolean distinctResults = false;
0150: private ClassLoader classLoader = null;
0151: private Query parent = null;
0152: private Map listeners = new HashMap();
0153:
0154: // Execution data.
0155: private transient Object currentObject = null;
0156: private transient List allObjects = null;
0157: private transient List currGroupBys = null;
0158:
0159: private QueryResults qd = null;
0160:
0161: /**
0162: * Return the WHERE clause expression.
0163: *
0164: * @return The WHERE clause as an expression.
0165: */
0166: public Expression getWhereClause() {
0167:
0168: return this .where;
0169:
0170: }
0171:
0172: /**
0173: * Return the HAVING clause expression.
0174: *
0175: * @return The HAVING clause as an expression.
0176: */
0177: public Expression getHavingClause() {
0178:
0179: return this .having;
0180:
0181: }
0182:
0183: /**
0184: * Return the {@link Comparator} we will use to do the ordering of the results, may be null.
0185: *
0186: * @return The Comparator.
0187: */
0188: public Comparator getOrderByComparator() {
0189:
0190: return this .orderByComp;
0191:
0192: }
0193:
0194: public FunctionHandler getFunctionHandler(String id) {
0195:
0196: if (this .parent != null) {
0197:
0198: return this .parent.getFunctionHandler(id);
0199:
0200: }
0201:
0202: return (FunctionHandler) this .bfhsMap.get(id);
0203:
0204: }
0205:
0206: private void initFunctionHandlers() {
0207:
0208: FunctionHandler o = new CollectionFunctions();
0209: o.setQuery(this );
0210:
0211: this .bfhsMap.put(CollectionFunctions.HANDLER_ID, o);
0212:
0213: this .bfhs.add(o);
0214:
0215: o = new StringFunctions();
0216: o.setQuery(this );
0217:
0218: this .bfhsMap.put(StringFunctions.HANDLER_ID, o);
0219:
0220: this .bfhs.add(o);
0221:
0222: o = new ConversionFunctions();
0223: o.setQuery(this );
0224:
0225: this .bfhsMap.put(ConversionFunctions.HANDLER_ID, o);
0226:
0227: this .bfhs.add(o);
0228:
0229: o = new FormattingFunctions();
0230: o.setQuery(this );
0231:
0232: this .bfhsMap.put(FormattingFunctions.HANDLER_ID, o);
0233:
0234: this .bfhs.add(o);
0235:
0236: o = new GroupingFunctions();
0237: o.setQuery(this );
0238:
0239: this .bfhsMap.put(GroupingFunctions.HANDLER_ID, o);
0240:
0241: this .bfhs.add(o);
0242:
0243: o = new MiscellaneousFunctions();
0244: o.setQuery(this );
0245:
0246: this .bfhsMap.put(MiscellaneousFunctions.HANDLER_ID, o);
0247:
0248: this .bfhs.add(o);
0249:
0250: }
0251:
0252: public Map getExecuteOnFunctions() {
0253:
0254: return this .executeOn;
0255:
0256: }
0257:
0258: public void setExecuteOnFunctions(Map ex) {
0259:
0260: this .executeOn = ex;
0261:
0262: }
0263:
0264: public String getAnonymousBindVariableName() {
0265:
0266: if (this .parent != null) {
0267:
0268: return this .parent.getAnonymousBindVariableName();
0269:
0270: }
0271:
0272: String n = Query.INT_BIND_VAR_PREFIX + this .anonVarIndex;
0273:
0274: this .anonVarIndex++;
0275:
0276: return n;
0277:
0278: }
0279:
0280: public List getDefaultFunctionHandlers() {
0281:
0282: if (this .parent != null) {
0283:
0284: return this .parent.getDefaultFunctionHandlers();
0285:
0286: }
0287:
0288: return new ArrayList(this .bfhs);
0289:
0290: }
0291:
0292: public List getFunctionHandlers() {
0293:
0294: if (this .parent != null) {
0295:
0296: return this .parent.getFunctionHandlers();
0297:
0298: }
0299:
0300: return this .functionHandlers;
0301:
0302: }
0303:
0304: public void addFunctionHandler(Object o) {
0305:
0306: if (this .parent != null) {
0307:
0308: this .parent.addFunctionHandler(o);
0309:
0310: }
0311:
0312: if (this .functionHandlers == null) {
0313:
0314: this .functionHandlers = new ArrayList();
0315:
0316: }
0317:
0318: if (o instanceof FunctionHandler) {
0319:
0320: FunctionHandler fh = (FunctionHandler) o;
0321:
0322: fh.setQuery(this );
0323:
0324: }
0325:
0326: this .functionHandlers.add(o);
0327:
0328: }
0329:
0330: public void setFrom(Expression exp) {
0331:
0332: this .from = exp;
0333:
0334: }
0335:
0336: public Expression getFrom() {
0337:
0338: return this .from;
0339:
0340: }
0341:
0342: public void setClassName(String n) {
0343:
0344: ConstantExpression ce = new ConstantExpression();
0345: this .from = ce;
0346: ce.setValue(n);
0347:
0348: }
0349:
0350: public void setOrderByColumns(List cols) {
0351:
0352: this .orderBys = cols;
0353:
0354: }
0355:
0356: public void setGroupByLimit(Limit g) {
0357:
0358: this .groupByLimit = g;
0359:
0360: }
0361:
0362: public void setGroupByOrderColumns(List cols) {
0363:
0364: this .groupOrderBys = cols;
0365:
0366: }
0367:
0368: public List getGroupByColumns() {
0369:
0370: return this .groupBys;
0371:
0372: }
0373:
0374: public void setGroupByColumns(List cols) {
0375:
0376: this .groupBys = cols;
0377:
0378: }
0379:
0380: public List getColumns() {
0381:
0382: return this .cols;
0383:
0384: }
0385:
0386: public void setColumns(List cols) {
0387:
0388: this .cols = cols;
0389:
0390: }
0391:
0392: /**
0393: * Set the expression for the HAVING clause.
0394: * Caution: do NOT use this method unless you are sure about what you are doing!
0395: *
0396: * @param be The expression.
0397: */
0398: public void setHaving(Expression be) {
0399:
0400: this .having = be;
0401:
0402: }
0403:
0404: /**
0405: * Set the expression for the WHERE clause.
0406: * Caution: do NOT use this method unless you are sure about what you are doing!
0407: *
0408: * @param be The expression.
0409: */
0410: public void setWhere(Expression be) {
0411:
0412: this .where = be;
0413:
0414: }
0415:
0416: /**
0417: * Create a new blank Query object.
0418: */
0419: public Query() {
0420:
0421: this .initFunctionHandlers();
0422:
0423: }
0424:
0425: public void setWantTimings(boolean v) {
0426:
0427: this .wantTimings = v;
0428:
0429: }
0430:
0431: protected void addTiming(String id, double time) {
0432:
0433: if (this .wantTimings) {
0434:
0435: if (this .qd == null) {
0436:
0437: return;
0438:
0439: }
0440:
0441: if (this .qd.timings == null) {
0442:
0443: this .qd.timings = new LinkedHashMap();
0444:
0445: }
0446:
0447: this .qd.timings.put(id, new Double(time));
0448:
0449: }
0450:
0451: }
0452:
0453: /**
0454: * Get the value of an indexed bind variable.
0455: *
0456: * @param index The index.
0457: * @return The value.
0458: */
0459: public Object getVariable(int index) {
0460:
0461: if (this .parent != null) {
0462:
0463: return this .parent.getVariable(index);
0464:
0465: }
0466:
0467: return this .getVariable(Query.INT_BIND_VAR_PREFIX + index);
0468:
0469: }
0470:
0471: /**
0472: * Get the class that the named variable has.
0473: *
0474: * @param name The name of the variable.
0475: * @return The Class.
0476: */
0477: public Class getVariableClass(String name) {
0478:
0479: String n = name.toLowerCase();
0480:
0481: if (n.equals(Query.QUERY_BIND_VAR_NAME)) {
0482:
0483: // Return the query itself!
0484: return Query.class;
0485:
0486: }
0487:
0488: if (n.equals(Query.PARENT_BIND_VAR_NAME)) {
0489:
0490: // Return the query itself!
0491: return Query.class;
0492:
0493: }
0494:
0495: if (n.equals(Query.CURR_OBJ_VAR_NAME)) {
0496:
0497: // May be null if we aren't processing a while/having expression.
0498: return this .objClass;
0499:
0500: }
0501:
0502: if (n.equals(Query.ALL_OBJS_VAR_NAME)) {
0503:
0504: // May change depending upon when it is called.
0505: return List.class;
0506:
0507: }
0508:
0509: if (this .parent != null) {
0510:
0511: return this .parent.getVariableClass(n);
0512:
0513: }
0514:
0515: if (this .bindVars == null) {
0516:
0517: return Object.class;
0518:
0519: }
0520:
0521: Object v = (Object) this .bindVars.get(n);
0522:
0523: if (v == null) {
0524:
0525: return Object.class;
0526:
0527: }
0528:
0529: return v.getClass();
0530:
0531: }
0532:
0533: /**
0534: * Get the value of a group by variable from the current group bys.
0535: *
0536: * @param ind The variable index.
0537: * @return The value.
0538: */
0539: public Object getGroupByVariable(int ind) {
0540:
0541: // Get the current group bys.
0542: if (this .currGroupBys != null) {
0543:
0544: return this .currGroupBys.get(ind - 1);
0545:
0546: }
0547:
0548: return null;
0549:
0550: }
0551:
0552: /**
0553: * Get the value of a named bind variable.
0554: *
0555: * @param name The name of the bind variable.
0556: * @return The value.
0557: */
0558: public Object getVariable(String name) {
0559:
0560: String n = name.toLowerCase();
0561:
0562: if (n.startsWith(":")) {
0563:
0564: n = n.substring(1);
0565:
0566: }
0567:
0568: if (n.equals(Query.QUERY_BIND_VAR_NAME)) {
0569:
0570: // Return the query itself!
0571: return this ;
0572:
0573: }
0574:
0575: if (n.equals(Query.PARENT_BIND_VAR_NAME)) {
0576:
0577: // Return the parent query.
0578: return this .parent;
0579:
0580: }
0581:
0582: if (n.equals(Query.CURR_OBJ_VAR_NAME)) {
0583:
0584: // May be null if we aren't processing a while/having expression.
0585: return this .currentObject;
0586:
0587: }
0588:
0589: if (n.equals(Query.ALL_OBJS_VAR_NAME)) {
0590:
0591: // May change depending upon when it is called.
0592: return this .allObjects;
0593:
0594: }
0595:
0596: if (this .parent != null) {
0597:
0598: return this .parent.getVariable(name);
0599:
0600: }
0601:
0602: if (this .bindVars == null) {
0603:
0604: return null;
0605:
0606: }
0607:
0608: return this .bindVars.get(n);
0609:
0610: }
0611:
0612: /**
0613: * Set the value of a named bind variable.
0614: *
0615: * @param name The name.
0616: * @param v The value.
0617: */
0618: public void setVariable(String name, Object v) {
0619:
0620: if (this .parent != null) {
0621:
0622: this .parent.setVariable(name, v);
0623:
0624: return;
0625:
0626: }
0627:
0628: if (this .bindVars == null) {
0629:
0630: this .bindVars = new HashMap();
0631:
0632: }
0633:
0634: if (name.startsWith(":")) {
0635:
0636: name = name.substring(1);
0637:
0638: }
0639:
0640: this .bindVars.put(name.toLowerCase(), v);
0641:
0642: }
0643:
0644: /**
0645: * Set the value of an indexed bind variable.
0646: *
0647: * @param index The index.
0648: * @param v The value.
0649: */
0650: public void setVariable(int index, Object v) {
0651:
0652: if (this .parent != null) {
0653:
0654: this .parent.setVariable(index, v);
0655:
0656: return;
0657:
0658: }
0659:
0660: this .setVariable(Query.INT_BIND_VAR_PREFIX + index, v);
0661:
0662: }
0663:
0664: /**
0665: * Get all the bind variables as a Map.
0666: *
0667: * @return The name/value mappings of the bind variables.
0668: */
0669: public Map getVariables() {
0670:
0671: if (this .parent != null) {
0672:
0673: return this .parent.getVariables();
0674:
0675: }
0676:
0677: return this .bindVars;
0678:
0679: }
0680:
0681: /**
0682: * A helper method that will evaluate the WHERE clause for the object passed in.
0683: *
0684: * @param o The object to evaluate the WHERE clause against.
0685: * @return The result of calling: Expression.isTrue(Object,Query) for the WHERE clause.
0686: */
0687: public boolean isWhereTrue(Object o) throws QueryExecutionException {
0688:
0689: if (this .where == null) {
0690:
0691: // A null where means yes!
0692: return true;
0693:
0694: }
0695:
0696: return this .where.isTrue(o, this );
0697:
0698: }
0699:
0700: /**
0701: * Set the bind variables in one go.
0702: *
0703: * @param bVars The bind variable name/value mappings.
0704: */
0705: public void setVariables(Map bVars) {
0706:
0707: if (this .parent != null) {
0708:
0709: this .parent.setVariables(bVars);
0710:
0711: return;
0712:
0713: }
0714:
0715: this .bindVars = bVars;
0716:
0717: }
0718:
0719: /**
0720: * Execute all the expressions for the specified type, either: {@link #ALL} or:
0721: * {@link #RESULTS}. If the expressions are aliased then the results will be
0722: * available in the save results upon completion.
0723: *
0724: * @param l The List of objects to execute the functions on.
0725: * @param t The type of expressions to execute.
0726: * @throws QueryExecutionException If there is an issue with executing one of the
0727: * expressions or if the Query hasn't been inited yet.
0728: */
0729: public void doExecuteOn(List l, String t)
0730: throws QueryExecutionException {
0731:
0732: if (this .executeOn == null) {
0733:
0734: // Do nothing.
0735: return;
0736:
0737: }
0738:
0739: if (!this .isParsed) {
0740:
0741: throw new QueryExecutionException(
0742: "Query has not been initialised.");
0743:
0744: }
0745:
0746: if (this .executeOn != null) {
0747:
0748: // Set the "all objects".
0749: this .allObjects = l;
0750:
0751: long s = System.currentTimeMillis();
0752:
0753: List fs = (List) this .executeOn.get(t);
0754:
0755: if (fs != null) {
0756:
0757: // Execute each one in turn.
0758: int si = fs.size();
0759:
0760: for (int i = 0; i < si; i++) {
0761:
0762: AliasedExpression f = (AliasedExpression) fs.get(i);
0763:
0764: Object o = f.getValue(null, this );
0765:
0766: String af = f.getAlias();
0767:
0768: if (af != null) {
0769:
0770: this .setSaveValue(af, o);
0771:
0772: }
0773:
0774: }
0775:
0776: this .addTiming("Total time to execute: " + si
0777: + " expression(s) on " + t + " objects", System
0778: .currentTimeMillis()
0779: - s);
0780:
0781: }
0782:
0783: }
0784:
0785: }
0786:
0787: /**
0788: * This method will be called at the end of a query execution to clean up the
0789: * transient objects used throughout execution.
0790: */
0791: private void clearResults() {
0792:
0793: this .qd = null;
0794:
0795: this .currentObject = null;
0796: this .allObjects = null;
0797: this .currGroupBys = null;
0798:
0799: }
0800:
0801: /**
0802: * Execute this query on the specified objects.
0803: *
0804: * @param objs The list of objects to execute the query on.
0805: * @return The list of objects that match the query.
0806: * @throws QueryExecutionException If the query cannot be executed.
0807: */
0808: public QueryResults execute(List objs)
0809: throws QueryExecutionException {
0810:
0811: if ((objs == null) && (this .objClass != null)) {
0812:
0813: throw new QueryExecutionException(
0814: "List of objects must be non-null when an object class is specified.");
0815:
0816: }
0817:
0818: this .qd = new QueryResults();
0819:
0820: if ((this .objClass == null) && (objs == null)) {
0821:
0822: objs = Query.nullQueryList;
0823:
0824: }
0825:
0826: this .allObjects = objs;
0827:
0828: // See if we have any expressions that are to be executed on
0829: // the complete set.
0830: this .doExecuteOn(objs, Query.ALL);
0831:
0832: this .evalWhereClause();
0833:
0834: // See if we have any functions that are to be executed on
0835: // the results...
0836: this .doExecuteOn(this .qd.results, Query.RESULTS);
0837:
0838: // If we have a "having" clause execute it here...
0839: this .evalHavingClause();
0840:
0841: // Now perform the group by operation.
0842: if (this .grouper != null) {
0843:
0844: this .evalGroupByClause();
0845:
0846: return this .qd;
0847:
0848: }
0849:
0850: // Now perform the order by.
0851: this .evalOrderByClause();
0852:
0853: // Finally, if we have a limit clause, restrict the set of objects returned...
0854: this .evalLimitClause();
0855:
0856: this .evalSelectClause();
0857:
0858: try {
0859:
0860: return this .qd;
0861:
0862: } finally {
0863:
0864: // Clean up ;)
0865: this .clearResults();
0866:
0867: }
0868:
0869: }
0870:
0871: private void evalSelectClause() throws QueryExecutionException {
0872:
0873: boolean retNewObjs = false;
0874:
0875: // See if we are a single column of new objects.
0876: if (!this .retObjs) {
0877:
0878: if (this .cols.size() == 1) {
0879:
0880: SelectItemExpression sei = (SelectItemExpression) this .cols
0881: .get(0);
0882:
0883: if (sei.getExpression() instanceof NewObjectExpression) {
0884:
0885: retNewObjs = true;
0886:
0887: }
0888:
0889: }
0890:
0891: }
0892:
0893: long s = System.currentTimeMillis();
0894:
0895: // Now get the columns if necessary, we do this here to get the minimum
0896: // set of objects required.
0897: if ((!this .retObjs) && (!retNewObjs)) {
0898:
0899: Collection resC = null;
0900:
0901: if (!this .distinctResults) {
0902:
0903: resC = new ArrayList(this .qd.results.size());
0904:
0905: } else {
0906:
0907: resC = new LinkedHashSet(this .qd.results.size());
0908:
0909: }
0910:
0911: // Get the column values.
0912: this .getColumnValues(this .qd.results, resC);
0913:
0914: if (this .distinctResults) {
0915:
0916: this .qd.results = new ArrayList(resC);
0917:
0918: } else {
0919:
0920: this .qd.results = (List) resC;
0921:
0922: }
0923:
0924: this .addTiming("Collection of results took",
0925: (double) (System.currentTimeMillis() - s));
0926:
0927: } else {
0928:
0929: if (this .retObjs) {
0930:
0931: if (this .distinctResults) {
0932:
0933: s = System.currentTimeMillis();
0934:
0935: this .qd.results = ((CollectionFunctions) this
0936: .getFunctionHandler(CollectionFunctions.HANDLER_ID))
0937: .unique(this .qd.results);
0938:
0939: this .addTiming("Collecting unique results took",
0940: (double) (System.currentTimeMillis() - s));
0941:
0942: }
0943:
0944: }
0945:
0946: // If we want a single column of new objects...
0947: if (retNewObjs) {
0948:
0949: this .qd.results = this
0950: .getNewObjectSingleColumnValues(this .qd.results);
0951:
0952: }
0953:
0954: }
0955:
0956: }
0957:
0958: private void evalOrderByClause() throws QueryExecutionException {
0959:
0960: if ((this .qd.results.size() > 1) && (this .orderByComp != null)) {
0961:
0962: long s = System.currentTimeMillis();
0963:
0964: // It should be noted here that the comparator will set the
0965: // "current object" so that it can be used in the order by
0966: // clause.
0967: Collections.sort(this .qd.results, this .orderByComp);
0968:
0969: this .addTiming("Total time to order results", System
0970: .currentTimeMillis()
0971: - s);
0972:
0973: }
0974:
0975: if (this .orderByComp != null) {
0976:
0977: ListExpressionComparator lec = (ListExpressionComparator) this .orderByComp;
0978:
0979: if (lec.getException() != null) {
0980:
0981: throw new QueryExecutionException(
0982: "Unable to order results", lec.getException());
0983:
0984: }
0985:
0986: lec.clearCache();
0987:
0988: }
0989:
0990: }
0991:
0992: private void evalGroupByClause() throws QueryExecutionException {
0993:
0994: long s = System.currentTimeMillis();
0995:
0996: // Need to handle the fact that this will return a Map of Lists...
0997: try {
0998:
0999: s = System.currentTimeMillis();
1000:
1001: // Group the objects.
1002: Map mres = this .grouper.group(this .qd.results);
1003:
1004: this .qd.groupByResults = mres;
1005:
1006: List grpBys = new ArrayList(mres.keySet());
1007:
1008: // Convert the keys in the group by to a List.
1009: Map origSvs = this .qd.saveValues;
1010:
1011: Map nres = new LinkedHashMap();
1012:
1013: int gs = grpBys.size();
1014:
1015: // Now for each "group by" list, do:
1016: // 1. Execute the functions for the GROUP_BY_RESULTS type.
1017: // 2. Sort the group by results according to the ORDER BY clause.
1018: // 3. Limit the group by results according to the LIMIT clause.
1019: for (int i = 0; i < gs; i++) {
1020:
1021: List l = (List) grpBys.get(i);
1022:
1023: List lr = (List) mres.get(l);
1024:
1025: this .allObjects = lr;
1026: this .currGroupBys = l;
1027:
1028: // Now set the save values for the group bys.
1029: if (this .qd.groupBySaveValues == null) {
1030:
1031: this .qd.groupBySaveValues = new HashMap();
1032:
1033: }
1034:
1035: this .qd.saveValues = new HashMap();
1036:
1037: this .qd.groupBySaveValues.put(l, this .qd.saveValues);
1038:
1039: // Now execute all (any) group by results functions.
1040: this .doExecuteOn(lr, Query.GROUP_BY_RESULTS);
1041:
1042: // Now sort these according to the order by (if any).
1043: if ((lr.size() > 1) && (this .orderByComp != null)) {
1044:
1045: Collections.sort(lr, this .orderByComp);
1046:
1047: ListExpressionComparator lec = (ListExpressionComparator) this .orderByComp;
1048:
1049: if (lec.getException() != null) {
1050:
1051: throw new QueryExecutionException(
1052: "Unable to order group by results", lec
1053: .getException());
1054:
1055: }
1056:
1057: lec.clearCache();
1058:
1059: }
1060:
1061: if (!this .retObjs) {
1062:
1063: // Now collect the values...
1064: Collection res = null;
1065:
1066: if (!this .distinctResults) {
1067:
1068: res = new ArrayList();
1069:
1070: } else {
1071:
1072: res = new LinkedHashSet();
1073:
1074: }
1075:
1076: this .getColumnValues(lr, res);
1077:
1078: if (this .distinctResults) {
1079:
1080: lr = new ArrayList(res);
1081:
1082: } else {
1083:
1084: lr = (List) res;
1085:
1086: }
1087:
1088: } else {
1089:
1090: if (this .distinctResults) {
1091:
1092: this .qd.results = ((CollectionFunctions) this
1093: .getFunctionHandler(CollectionFunctions.HANDLER_ID))
1094: .unique(this .qd.results);
1095:
1096: }
1097:
1098: }
1099:
1100: nres.put(l, lr);
1101:
1102: }
1103:
1104: // Restore the save values.
1105: this .qd.saveValues = origSvs;
1106:
1107: // Set the group by results.
1108: this .qd.groupByResults = nres;
1109:
1110: long t = System.currentTimeMillis();
1111:
1112: this .addTiming("Group column collection and sort took",
1113: (double) (t - s));
1114:
1115: s = t;
1116:
1117: // Now order the group bys, if present.
1118: if (this .groupOrderByComp != null) {
1119:
1120: origSvs = this .qd.saveValues;
1121:
1122: Collections.sort(grpBys, this .groupOrderByComp);
1123:
1124: // "Restore" the save values.
1125: this .qd.saveValues = origSvs;
1126:
1127: GroupByExpressionComparator lec = (GroupByExpressionComparator) this .groupOrderByComp;
1128:
1129: if (lec.getException() != null) {
1130:
1131: throw new QueryExecutionException(
1132: "Unable to order group bys, remember that the current object here is a java.util.List, not the class defined in the FROM clause, you may need to use the org.josq.functions.CollectionFunctions.get(java.util.List,Number) function to get access to the relevant value from the List.",
1133: lec.getException());
1134:
1135: }
1136:
1137: lec.clearCache();
1138:
1139: }
1140:
1141: // Now limit the group bys, if required.
1142: if (this .groupByLimit != null) {
1143:
1144: s = System.currentTimeMillis();
1145:
1146: List oGrpBys = grpBys;
1147:
1148: grpBys = this .groupByLimit.getSubList(grpBys, this );
1149:
1150: // Now trim out from the group by results any list that isn't in the current grpbys.
1151: for (int i = 0; i < oGrpBys.size(); i++) {
1152:
1153: List l = (List) oGrpBys.get(i);
1154:
1155: if (!grpBys.contains(l)) {
1156:
1157: // Remove.
1158: this .qd.groupByResults.remove(l);
1159:
1160: }
1161:
1162: }
1163:
1164: this .addTiming(
1165: "Total time to limit group by results size",
1166: System.currentTimeMillis() - s);
1167:
1168: }
1169:
1170: this .addTiming("Group operation took", (double) (System
1171: .currentTimeMillis() - s));
1172:
1173: // "Restore" the save values.
1174: this .qd.saveValues = origSvs;
1175:
1176: this .qd.results = grpBys;
1177:
1178: // NOW limit the group by results to a certain size, this needs
1179: // to be done last so that the group by limit clause can make use of the size of the
1180: // results.
1181: if (this .limit != null) {
1182:
1183: for (int i = 0; i < this .qd.results.size(); i++) {
1184:
1185: List l = (List) this .qd.results.get(i);
1186:
1187: List lr = (List) this .qd.groupByResults.get(l);
1188:
1189: this .allObjects = lr;
1190: this .currGroupBys = l;
1191:
1192: this .qd.saveValues = (Map) this .qd.groupBySaveValues
1193: .get(l);
1194:
1195: this .qd.groupByResults.put(l, this .limit
1196: .getSubList(lr, this ));
1197:
1198: }
1199:
1200: }
1201:
1202: this .qd.saveValues = origSvs;
1203:
1204: } catch (Exception e) {
1205:
1206: throw new QueryExecutionException(
1207: "Unable to perform group by operation", e);
1208:
1209: }
1210:
1211: }
1212:
1213: private void evalHavingClause() throws QueryExecutionException {
1214:
1215: if (this .having != null) {
1216:
1217: int si = this .qd.results.size();
1218:
1219: this .qd.havingResults = new ArrayList(si);
1220:
1221: for (int i = 0; i < si; i++) {
1222:
1223: Object o = this .qd.results.get(i);
1224:
1225: this .currentObject = o;
1226:
1227: if (this .having.isTrue(o, this )) {
1228:
1229: this .qd.havingResults.add(o);
1230:
1231: }
1232:
1233: }
1234:
1235: this .qd.results = this .qd.havingResults;
1236:
1237: // Future proofing...
1238: this .allObjects = this .qd.results;
1239:
1240: }
1241:
1242: }
1243:
1244: private void evalLimitClause() throws QueryExecutionException {
1245:
1246: if (this .limit != null) {
1247:
1248: long s = System.currentTimeMillis();
1249:
1250: this .qd.results = this .limit.getSubList(this .qd.results,
1251: this );
1252:
1253: this .addTiming("Total time to limit results size", System
1254: .currentTimeMillis()
1255: - s);
1256:
1257: }
1258: }
1259:
1260: private void evalWhereClause() throws QueryExecutionException {
1261:
1262: long s = System.currentTimeMillis();
1263:
1264: int si = this .allObjects.size();
1265:
1266: if (this .where != null) {
1267:
1268: // Create the where results with "about" half the size of the input collection.
1269: // Further optimizations may be possible here if some statistics are collected
1270: // about how many objects match/fail the where clause and then increase the
1271: // capacity of the where results list as required, i.e. to cut down on the number
1272: // of array copy and allocation operations performed. For now though half will do ;)
1273: this .qd.whereResults = new ArrayList(si / 2);
1274:
1275: for (int i = 0; i < si; i++) {
1276:
1277: Object o = this .allObjects.get(i);
1278:
1279: this .currentObject = o;
1280:
1281: boolean res = this .where.isTrue(o, this );
1282:
1283: if (res) {
1284:
1285: this .qd.whereResults.add(o);
1286:
1287: }
1288:
1289: }
1290:
1291: } else {
1292:
1293: // No limiting where clause so what's passed in is what comes out.
1294: this .qd.whereResults = this .allObjects;
1295:
1296: }
1297:
1298: double wet = (double) System.currentTimeMillis() - (double) s;
1299:
1300: this .addTiming(
1301: "Total time to execute Where clause on all objects",
1302: wet);
1303: this .addTiming("Where took average over: " + si + " objects",
1304: wet / (double) si);
1305:
1306: this .allObjects = this .qd.whereResults;
1307:
1308: // The results here are the result of executing the where clause, if present.
1309: this .qd.results = this .qd.whereResults;
1310:
1311: }
1312:
1313: public void setCurrentGroupByObjects(List objs) {
1314:
1315: this .currGroupBys = objs;
1316:
1317: }
1318:
1319: /**
1320: * Get the current list of objects in context (value of the :_allobjs special bind variable).
1321: * Note: the value of the :_allobjs bind variable will change depending upon where the query execution
1322: * is up to.
1323: *
1324: * @return The list of objects in context.
1325: */
1326: public List getAllObjects() {
1327:
1328: return this .allObjects;
1329:
1330: }
1331:
1332: public void setAllObjects(List objs) {
1333:
1334: this .allObjects = objs;
1335:
1336: }
1337:
1338: public void setCurrentObject(Object o) {
1339:
1340: this .currentObject = o;
1341:
1342: }
1343:
1344: /**
1345: * Get the current object (value of the :_currobj special bind variable). Note: the value
1346: * of the :_currobj bind variable will change depending upon where the query execution is up to.
1347: *
1348: * @return The current object in context.
1349: */
1350: public Object getCurrentObject() {
1351:
1352: return this .currentObject;
1353:
1354: }
1355:
1356: private void getColumnValues(List res, Collection rs)
1357: throws QueryExecutionException {
1358:
1359: int s = res.size();
1360:
1361: int cs = this .cols.size();
1362:
1363: boolean addItems = false;
1364:
1365: for (int i = 0; i < s; i++) {
1366:
1367: Object o = res.get(i);
1368:
1369: this .currentObject = o;
1370:
1371: List sRes = new ArrayList(cs);
1372:
1373: for (int j = 0; j < cs; j++) {
1374:
1375: SelectItemExpression v = (SelectItemExpression) this .cols
1376: .get(j);
1377:
1378: try {
1379:
1380: if (v.isAddItemsFromCollectionOrMap()) {
1381:
1382: addItems = true;
1383:
1384: }
1385:
1386: // Get the value from the object...
1387: Object ov = v.getValue(o, this );
1388:
1389: if (addItems) {
1390:
1391: rs.addAll(v.getAddItems(ov));
1392:
1393: } else {
1394:
1395: sRes.add(ov);
1396:
1397: }
1398:
1399: // Now since the expression can set the current object, put it
1400: // back to rights after the call...
1401: this .currentObject = o;
1402:
1403: } catch (Exception e) {
1404:
1405: throw new QueryExecutionException(
1406: "Unable to get value for column: " + j
1407: + " for: " + v.toString()
1408: + " from result: " + i + " (" + o
1409: + ")", e);
1410:
1411: }
1412:
1413: }
1414:
1415: if (!addItems) {
1416:
1417: rs.add(sRes);
1418:
1419: }
1420:
1421: }
1422:
1423: }
1424:
1425: private List getNewObjectSingleColumnValues(List rows)
1426: throws QueryExecutionException {
1427:
1428: int s = rows.size();
1429:
1430: SelectItemExpression nsei = (SelectItemExpression) this .cols
1431: .get(0);
1432:
1433: List res = new ArrayList(s);
1434:
1435: for (int i = 0; i < s; i++) {
1436:
1437: Object o = rows.get(i);
1438:
1439: this .currentObject = o;
1440:
1441: try {
1442:
1443: res.add(nsei.getValue(o, this ));
1444:
1445: // Now since the expression can set the current object, put it
1446: // back to rights after the call...
1447: this .currentObject = o;
1448:
1449: } catch (Exception e) {
1450:
1451: throw new QueryExecutionException(
1452: "Unable to get value for column: " + 1
1453: + " for: " + nsei.toString()
1454: + " from result: " + i + " (" + o + ")",
1455: e);
1456:
1457: }
1458:
1459: }
1460:
1461: return res;
1462:
1463: }
1464:
1465: public void setSaveValues(Map s) {
1466:
1467: if (this .parent != null) {
1468:
1469: this .parent.qd.saveValues.putAll(s);
1470:
1471: return;
1472:
1473: }
1474:
1475: this .qd.saveValues = s;
1476:
1477: }
1478:
1479: public void setSaveValue(Object id, Object value) {
1480:
1481: if (this .parent != null) {
1482:
1483: this .parent.setSaveValue(id, value);
1484:
1485: return;
1486:
1487: }
1488:
1489: if (this .qd == null) {
1490:
1491: return;
1492:
1493: }
1494:
1495: if (id instanceof String) {
1496:
1497: id = ((String) id).toLowerCase();
1498:
1499: }
1500:
1501: Object old = this .qd.saveValues.get(id);
1502:
1503: this .qd.saveValues.put(id, value);
1504:
1505: if (old != null) {
1506:
1507: this .fireSaveValueChangedEvent(id, old, value);
1508:
1509: }
1510:
1511: }
1512:
1513: protected void fireSaveValueChangedEvent(Object id, Object from,
1514: Object to) {
1515:
1516: List l = (List) this .listeners.get("svs");
1517:
1518: if ((l == null) || (l.size() == 0)) {
1519:
1520: return;
1521:
1522: }
1523:
1524: SaveValueChangedEvent svce = new SaveValueChangedEvent(this , id
1525: .toString().toLowerCase(), from, to);
1526:
1527: for (int i = 0; i < l.size(); i++) {
1528:
1529: SaveValueChangedListener svcl = (SaveValueChangedListener) l
1530: .get(i);
1531:
1532: svcl.saveValueChanged(svce);
1533:
1534: }
1535:
1536: }
1537:
1538: protected void fireBindVariableChangedEvent(String name,
1539: Object from, Object to) {
1540:
1541: List l = (List) this .listeners.get("bvs");
1542:
1543: if ((l == null) || (l.size() == 0)) {
1544:
1545: return;
1546:
1547: }
1548:
1549: BindVariableChangedEvent bvce = new BindVariableChangedEvent(
1550: this , name, from, to);
1551:
1552: for (int i = 0; i < l.size(); i++) {
1553:
1554: BindVariableChangedListener bvcl = (BindVariableChangedListener) l
1555: .get(i);
1556:
1557: bvcl.bindVariableChanged(bvce);
1558:
1559: }
1560:
1561: }
1562:
1563: /**
1564: * Get the save value for a particular key and group by list.
1565: *
1566: * @param id The id of the save value.
1567: * @param gbs The group by list key.
1568: * @return The object the key maps to.
1569: */
1570: public Object getGroupBySaveValue(Object id, List gbs) {
1571:
1572: if (this .parent != null) {
1573:
1574: return this .getGroupBySaveValue(id, gbs);
1575:
1576: }
1577:
1578: Map m = this .getGroupBySaveValues(gbs);
1579:
1580: if (m == null) {
1581:
1582: return null;
1583:
1584: }
1585:
1586: return m.get(id);
1587:
1588: }
1589:
1590: /**
1591: * Get the save values for the specified group bys.
1592: *
1593: * @param gbs The group bys.
1594: * @return The save values (name/value pairs).
1595: */
1596: public Map getGroupBySaveValues(List gbs) {
1597:
1598: if (this .parent != null) {
1599:
1600: return this .parent.getGroupBySaveValues(gbs);
1601:
1602: }
1603:
1604: if ((this .qd == null) || (this .qd.groupBySaveValues == null)) {
1605:
1606: return null;
1607:
1608: }
1609:
1610: return (Map) this .qd.groupBySaveValues.get(gbs);
1611:
1612: }
1613:
1614: /**
1615: * Get the save values for a particular key.
1616: *
1617: * @return The object the key maps to.
1618: */
1619: public Object getSaveValue(Object id) {
1620:
1621: if (this .parent != null) {
1622:
1623: return this .parent.getSaveValue(id);
1624:
1625: }
1626:
1627: if ((this .qd == null) || (this .qd.saveValues == null)) {
1628:
1629: return null;
1630:
1631: }
1632:
1633: if (id instanceof String) {
1634:
1635: id = ((String) id).toLowerCase();
1636:
1637: }
1638:
1639: return this .qd.saveValues.get(id);
1640:
1641: }
1642:
1643: /**
1644: * Get the query string that this Query object represents.
1645: *
1646: * @return The query string.
1647: */
1648: public String getQuery() {
1649:
1650: return this .query;
1651:
1652: }
1653:
1654: /**
1655: * Will cause the order by comparator used to order the results
1656: * to be initialized. This is generally only useful if you are specifying the
1657: * the order bys yourself via: {@link #setOrderByColumns(List)}. Usage of
1658: * this method is <b>NOT</b> supported, so don't use unless you really know what
1659: * you are doing!
1660: */
1661: public void initOrderByComparator() throws QueryParseException {
1662:
1663: if (this .orderBys != null) {
1664:
1665: // No caching, this may need to change in the future.
1666: this .orderByComp = new ListExpressionComparator(this , false);
1667:
1668: ListExpressionComparator lec = (ListExpressionComparator) this .orderByComp;
1669:
1670: // Need to check the type of each order by, if we have
1671: // any "column" indexes check to see if they are an accessor...
1672: int si = this .orderBys.size();
1673:
1674: for (int i = 0; i < si; i++) {
1675:
1676: OrderBy ob = (OrderBy) this .orderBys.get(i);
1677:
1678: // Get the expression...
1679: Expression e = (Expression) ob.getExpression();
1680:
1681: if (e == null) {
1682:
1683: // Now expect an integer that refers to a column
1684: // in the select...
1685: int ci = ob.getIndex();
1686:
1687: if (ci == 0) {
1688:
1689: throw new QueryParseException(
1690: "Order by column indices should start at 1.");
1691:
1692: }
1693:
1694: if (this .retObjs) {
1695:
1696: throw new QueryParseException(
1697: "Cannot sort on a select column index when the objects are to be returned.");
1698:
1699: }
1700:
1701: if (ci > this .cols.size()) {
1702:
1703: throw new QueryParseException(
1704: "Invalid order by column index: "
1705: + ci
1706: + ", only: "
1707: + this .cols.size()
1708: + " columns are selected to be returned.");
1709:
1710: }
1711:
1712: // Get the SelectItemExpression.
1713: SelectItemExpression sei = (SelectItemExpression) this .cols
1714: .get(ci - 1);
1715:
1716: // Get the expression...
1717: e = sei.getExpression();
1718:
1719: } else {
1720:
1721: // Init the expression...
1722: e.init(this );
1723:
1724: }
1725:
1726: // Check to see if the expression returns a fixed result, if so
1727: // there's no point adding it.
1728: if (!e.hasFixedResult(this )) {
1729:
1730: lec.addSortItem(e, ob.getType());
1731:
1732: }
1733:
1734: }
1735:
1736: }
1737:
1738: }
1739:
1740: /**
1741: * Re-order the objects according to the columns supplied in the <b>dirs</b> Map.
1742: * The Map should be keyed on an Integer and map to a String value, the String value should
1743: * be either: {@link #ORDER_BY_ASC} for the column to be in ascending order or:
1744: * {@link #ORDER_BY_DESC} for the column to be in descending order. The Integer refers
1745: * to a column in the SELECT part of the statement.
1746: * <p>
1747: * For example:
1748: * <p>
1749: * <pre>
1750: * SELECT name,
1751: * directory,
1752: * file
1753: * length
1754: * FROM java.io.File
1755: * </pre>
1756: * Can be (re)ordered via the following code:
1757: * <pre>
1758: * Query q = new Query ();
1759: * q.parse (sql);
1760: *
1761: * Map reorderBys = new TreeMap ();
1762: * reorderBys.put (new Integer (2), Query.ORDER_BY_ASC);
1763: * reorderBys.put (new Integer (3), Query.ORDER_BY_DESC);
1764: * reorderBys.put (new Integer (1), Query.ORDER_BY_ASC);
1765: * reorderBys.put (new Integer (4), Query.ORDER_BY_DESC);
1766: *
1767: * // Note: this call will cause the entire statement to be executed.
1768: * q.reorder (myFiles,
1769: * reorderBys);
1770: * </pre>
1771: *
1772: * @param objs The objects you wish to reorder.
1773: * @param dirs The order bys.
1774: * @return The QueryResults.
1775: * @throws QueryParseException If the statement can be parsed, i.e. if any of the order by
1776: * columns is out of range.
1777: * @throws QueryExecutionException If the call to: {@link #execute(List)} fails.
1778: * @see #reorder(List,String)
1779: */
1780: public QueryResults reorder(List objs, SortedMap dirs)
1781: throws QueryExecutionException, QueryParseException {
1782:
1783: if (this .isWantObjects()) {
1784:
1785: throw new QueryParseException(
1786: "Only SQL statements that return columns (not the objects passed in) can be re-ordered.");
1787:
1788: }
1789:
1790: List obs = new ArrayList();
1791:
1792: Iterator iter = dirs.keySet().iterator();
1793:
1794: while (iter.hasNext()) {
1795:
1796: Integer in = (Integer) iter.next();
1797:
1798: // See if we have a column for it.
1799: if (in.intValue() > this .cols.size()) {
1800:
1801: throw new QueryParseException("Cannot reorder: "
1802: + dirs.size() + " columns, only: "
1803: + this .cols.size()
1804: + " are present in the SQL statement.");
1805:
1806: }
1807:
1808: String dir = (String) dirs.get(in);
1809:
1810: int d = OrderBy.ASC;
1811:
1812: if (dir.equals(Query.ORDER_BY_DESC)) {
1813:
1814: d = OrderBy.DESC;
1815:
1816: }
1817:
1818: OrderBy ob = new OrderBy();
1819: ob.setIndex(in.intValue());
1820: ob.setType(d);
1821:
1822: obs.add(ob);
1823:
1824: }
1825:
1826: this .orderBys = obs;
1827:
1828: this .initOrderByComparator();
1829:
1830: // Execute the query.
1831: return this .execute(objs);
1832:
1833: }
1834:
1835: /**
1836: * Allows the re-ordering of the results via a textual representation of the order bys.
1837: * This is effectively like providing a new ORDER BY clause to the sql.
1838: * <p>
1839: * For example:
1840: * <p>
1841: * <pre>
1842: * SELECT name,
1843: * directory,
1844: * file
1845: * length
1846: * FROM java.io.File
1847: * </pre>
1848: * Can be (re)ordered via the following code:
1849: * <pre>
1850: * Query q = new Query ();
1851: * q.parse (sql);
1852: *
1853: * // Note: this call will cause the entire statement to be executed.
1854: * q.reorder (myFiles,
1855: * "name DESC, 3 ASC, length, 1 DESC");
1856: * </pre>
1857: *
1858: * @param objs The objects you wish to re-order.
1859: * @param orderBys The order bys.
1860: * @return The execution results.
1861: * @throws QueryParseException If the statement can be parsed, i.e. if any of the order by
1862: * columns is out of range or the order bys cannot be parsed.
1863: * @throws QueryExecutionException If the call to: {@link #execute(List)} fails.
1864: * @see #reorder(List,SortedMap)
1865: */
1866: public QueryResults reorder(List objs, String orderBys)
1867: throws QueryParseException, QueryExecutionException {
1868:
1869: String sql = "";
1870:
1871: if (!orderBys.toLowerCase().startsWith("order by")) {
1872:
1873: sql = sql + " ORDER BY ";
1874:
1875: }
1876:
1877: sql = sql + orderBys;
1878:
1879: BufferedReader sr = new BufferedReader(new StringReader(sql));
1880:
1881: JoSQLParser parser = new JoSQLParser(sr);
1882:
1883: List ors = null;
1884:
1885: try {
1886:
1887: ors = parser.OrderBys();
1888:
1889: } catch (Exception e) {
1890:
1891: throw new QueryParseException("Unable to parse order bys: "
1892: + orderBys, e);
1893:
1894: }
1895:
1896: this .orderBys = ors;
1897:
1898: this .initOrderByComparator();
1899:
1900: // Execute the query.
1901: return this .execute(objs);
1902:
1903: }
1904:
1905: public void setClassLoader(ClassLoader cl) {
1906:
1907: this .classLoader = cl;
1908:
1909: }
1910:
1911: public ClassLoader getClassLoader() {
1912:
1913: if (this .classLoader == null) {
1914:
1915: // No custom classloader specified, use the one that loaded
1916: // this class.
1917: this .classLoader = Thread.currentThread()
1918: .getContextClassLoader();
1919:
1920: }
1921:
1922: return this .classLoader;
1923:
1924: }
1925:
1926: public Class loadClass(String name) throws Exception {
1927:
1928: return this .getClassLoader().loadClass(name);
1929:
1930: }
1931:
1932: /**
1933: * Parse the JoSQL query.
1934: *
1935: * @param q The query string.
1936: * @throws QueryParseException If the query cannot be parsed and/or {@link #init() inited}.
1937: */
1938: public void parse(String q) throws QueryParseException {
1939:
1940: this .query = q;
1941:
1942: BufferedReader sr = new BufferedReader(new StringReader(q));
1943:
1944: long s = System.currentTimeMillis();
1945:
1946: JoSQLParser parser = new JoSQLParser(sr);
1947:
1948: this .addTiming("Time to init josql parser object", System
1949: .currentTimeMillis()
1950: - s);
1951:
1952: s = System.currentTimeMillis();
1953:
1954: try {
1955:
1956: parser.parseQuery(this );
1957:
1958: } catch (Exception e) {
1959:
1960: throw new QueryParseException(
1961: "Unable to parse query: " + q, e);
1962:
1963: }
1964:
1965: this .isParsed = true;
1966:
1967: this .addTiming("Time to parse query into object form", System
1968: .currentTimeMillis()
1969: - s);
1970:
1971: // Init the query.
1972: this .init();
1973:
1974: }
1975:
1976: private void initFromObjectClass() throws QueryParseException {
1977:
1978: if (this .parent == null) {
1979:
1980: if (!(this .from instanceof ConstantExpression)) {
1981:
1982: throw new QueryParseException(
1983: "The FROM clause of the outer-most Query must be a string that denotes a fully-qualified class name, expression: "
1984: + this .from + " is not valid.");
1985:
1986: }
1987:
1988: // See if the class name is the special "null".
1989: String cn = null;
1990:
1991: try {
1992:
1993: // Should be safe to use a null value here (especially since we know
1994: // how ConstantExpression works ;)
1995: cn = (String) this .from.getValue(null, this );
1996:
1997: } catch (Exception e) {
1998:
1999: throw new QueryParseException(
2000: "Unable to determine FROM clause of the outer-most Query from expression: "
2001: + this .from
2002: + ", note: this exception shouldn't be able to happen, so something has gone SERIOUSLY wrong!",
2003: e);
2004:
2005: }
2006:
2007: if (!cn.equalsIgnoreCase("null")) {
2008:
2009: // Load the class that we are dealing with...
2010: try {
2011:
2012: this .objClass = this .loadClass(cn);
2013:
2014: } catch (Exception e) {
2015:
2016: throw new QueryParseException(
2017: "Unable to load FROM class: " + cn, e);
2018:
2019: }
2020:
2021: }
2022:
2023: }
2024:
2025: }
2026:
2027: public void init() throws QueryParseException {
2028:
2029: long s = System.currentTimeMillis();
2030:
2031: // If we don't have a parent, then there must be an explicit class name.
2032: this .initFromObjectClass();
2033:
2034: // Now if we have any columns, init those as well...
2035: this .initSelect();
2036:
2037: // Now init the where clause (where possible)...
2038: if (this .where != null) {
2039:
2040: this .where.init(this );
2041:
2042: }
2043:
2044: // Now init the having clause (where possible)...
2045: if (this .having != null) {
2046:
2047: this .having.init(this );
2048:
2049: }
2050:
2051: // See if we have order by columns, if so init the comparator.
2052: this .initOrderByComparator();
2053:
2054: // See if we have order by columns, if so init the comparator.
2055: if (this .groupBys != null) {
2056:
2057: this .initGroupBys();
2058:
2059: }
2060:
2061: this .initGroupOrderBys();
2062:
2063: if (this .groupByLimit != null) {
2064:
2065: this .groupByLimit.init(this );
2066:
2067: }
2068:
2069: if (this .limit != null) {
2070:
2071: this .limit.init(this );
2072:
2073: }
2074:
2075: this .initExecuteOn();
2076:
2077: this .addTiming("Time to init Query objects", System
2078: .currentTimeMillis()
2079: - s);
2080:
2081: }
2082:
2083: private void initSelect() throws QueryParseException {
2084:
2085: if (this .retObjs) {
2086:
2087: // Nothing to do.
2088: return;
2089:
2090: }
2091:
2092: int aic = 0;
2093:
2094: int si = this .cols.size();
2095:
2096: this .aliases = new HashMap();
2097:
2098: for (int i = 0; i < si; i++) {
2099:
2100: SelectItemExpression exp = (SelectItemExpression) this .cols
2101: .get(i);
2102:
2103: exp.init(this );
2104:
2105: if (exp.isAddItemsFromCollectionOrMap()) {
2106:
2107: aic++;
2108:
2109: }
2110:
2111: String alias = exp.getAlias();
2112:
2113: if (alias != null) {
2114:
2115: this .aliases.put(alias, Integer.valueOf(i + 1));
2116:
2117: }
2118:
2119: this .aliases.put((i + 1) + "", Integer.valueOf(i + 1));
2120:
2121: }
2122:
2123: if ((aic > 0) && (aic != si)) {
2124:
2125: throw new QueryParseException(
2126: "If one or more SELECT clause columns is set to add the items returned from a: "
2127: + Map.class.getName()
2128: + " or: "
2129: + java.util.Collection.class.getName()
2130: + " then ALL columns must be marked to return the items as well.");
2131:
2132: }
2133:
2134: }
2135:
2136: private void initGroupBys() throws QueryParseException {
2137:
2138: this .grouper = new Grouper(this );
2139:
2140: int si = this .groupBys.size();
2141:
2142: for (int i = 0; i < si; i++) {
2143:
2144: OrderBy ob = (OrderBy) this .groupBys.get(i);
2145:
2146: // Get the expression...
2147: Expression e = (Expression) ob.getExpression();
2148:
2149: if (e == null) {
2150:
2151: // Now expect an integer that refers to a column
2152: // in the select...
2153: int ci = ob.getIndex();
2154:
2155: if (ci == 0) {
2156:
2157: throw new QueryParseException(
2158: "Order by column indices should start at 1.");
2159:
2160: }
2161:
2162: if (this .retObjs) {
2163:
2164: throw new QueryParseException(
2165: "Cannot sort on a select column index when the objects are to be returned.");
2166:
2167: }
2168:
2169: if (ci > this .cols.size()) {
2170:
2171: throw new QueryParseException(
2172: "Invalid order by column index: "
2173: + ci
2174: + ", only: "
2175: + this .cols.size()
2176: + " columns are selected to be returned.");
2177:
2178: }
2179:
2180: // Get the SelectItemExpression.
2181: SelectItemExpression sei = (SelectItemExpression) this .cols
2182: .get(ci - 1);
2183:
2184: // Get the expression...
2185: e = sei.getExpression();
2186:
2187: } else {
2188:
2189: // Init the expression...
2190: e.init(this );
2191:
2192: }
2193:
2194: this .grouper.addExpression(e);
2195:
2196: }
2197:
2198: }
2199:
2200: private void initExecuteOn() throws QueryParseException {
2201:
2202: if (this .executeOn == null) {
2203:
2204: return;
2205:
2206: }
2207:
2208: // Get the supported types.
2209: List allF = (List) this .executeOn.get(Query.ALL);
2210:
2211: if (allF != null) {
2212:
2213: // We have some, so init them...
2214: int si = allF.size();
2215:
2216: for (int i = 0; i < si; i++) {
2217:
2218: AliasedExpression f = (AliasedExpression) allF.get(i);
2219:
2220: f.init(this );
2221:
2222: }
2223:
2224: }
2225:
2226: List resultsF = (List) this .executeOn.get(Query.RESULTS);
2227:
2228: if (resultsF != null) {
2229:
2230: // We have some, so init them...
2231: int si = resultsF.size();
2232:
2233: for (int i = 0; i < si; i++) {
2234:
2235: AliasedExpression f = (AliasedExpression) resultsF
2236: .get(i);
2237:
2238: f.init(this );
2239:
2240: }
2241:
2242: }
2243:
2244: resultsF = (List) this .executeOn.get(Query.GROUP_BY_RESULTS);
2245:
2246: if (resultsF != null) {
2247:
2248: // We have some, so init them...
2249: int si = resultsF.size();
2250:
2251: for (int i = 0; i < si; i++) {
2252:
2253: AliasedExpression f = (AliasedExpression) resultsF
2254: .get(i);
2255:
2256: f.init(this );
2257:
2258: }
2259:
2260: }
2261:
2262: }
2263:
2264: private void initGroupOrderBys() throws QueryParseException {
2265:
2266: if (this .groupOrderBys == null) {
2267:
2268: // Nothing to do.
2269: return;
2270:
2271: }
2272:
2273: if (this .grouper == null) {
2274:
2275: throw new QueryParseException(
2276: "Group Order Bys are only valid if 1 or more Group By columns have been specified.");
2277:
2278: }
2279:
2280: // Here we "override" the from class because when dealing with the order bys the
2281: // current object will be a List, NOT the class defined in the FROM clause.
2282: Class c = this .objClass;
2283:
2284: this .objClass = List.class;
2285:
2286: // No caching, this may need to change in the future.
2287: this .groupOrderByComp = new GroupByExpressionComparator(this ,
2288: false);
2289:
2290: GroupByExpressionComparator lec = (GroupByExpressionComparator) this .groupOrderByComp;
2291:
2292: List grouperExps = this .grouper.getExpressions();
2293:
2294: // Need to check the type of each order by, if we have
2295: // any "column" indexes check to see if they are an accessor...
2296: int si = this .groupOrderBys.size();
2297:
2298: for (int i = 0; i < si; i++) {
2299:
2300: OrderBy ob = (OrderBy) this .groupOrderBys.get(i);
2301:
2302: if (ob.getIndex() > -1) {
2303:
2304: int ci = ob.getIndex();
2305:
2306: if (ci == 0) {
2307:
2308: throw new QueryParseException(
2309: "Group Order by column indices should start at 1.");
2310:
2311: }
2312:
2313: if (ci > grouperExps.size()) {
2314:
2315: throw new QueryParseException(
2316: "Invalid Group Order By column index: "
2317: + ci
2318: + ", only: "
2319: + grouperExps.size()
2320: + " Group By columns are selected to be returned.");
2321:
2322: }
2323:
2324: lec.addSortItem(null,
2325: // Remember the -1! Column indices start at 1 but
2326: // List indices start at 0 ;)
2327: ci - 1, ob.getType());
2328:
2329: continue;
2330:
2331: }
2332:
2333: // Get the expression...
2334: Expression e = (Expression) ob.getExpression();
2335:
2336: // See if the expression is a "direct" match for any of the
2337: // group by columns.
2338: boolean cont = true;
2339:
2340: for (int j = 0; j < grouperExps.size(); j++) {
2341:
2342: Expression exp = (Expression) grouperExps.get(j);
2343:
2344: if (e.equals(exp)) {
2345:
2346: // This is a match, add to the comparator.
2347: lec.addSortItem(null, j, ob.getType());
2348:
2349: cont = false;
2350:
2351: }
2352:
2353: }
2354:
2355: if (!cont) {
2356:
2357: continue;
2358:
2359: }
2360:
2361: if ((e instanceof Function) || (e instanceof BindVariable)
2362: || (e instanceof SaveValue)) {
2363:
2364: e.init(this );
2365:
2366: lec.addSortItem(e, -1, ob.getType());
2367:
2368: continue;
2369:
2370: }
2371:
2372: // If we are here then we haven't been able to deal with the
2373: // order by... so barf.
2374: throw new QueryParseException(
2375: "If the Group Order By: "
2376: + ob
2377: + " is not a function, a bind variable or a save value then it must be present in the Group By list.");
2378:
2379: }
2380:
2381: // Restore the FROM object class.
2382: this .objClass = c;
2383:
2384: }
2385:
2386: /**
2387: * Set the "FROM" object class. It is advised that you NEVER call this method, do so
2388: * at your own risk, dragons will swoop from the sky and crisp your innards if you do so!!!
2389: * Seriously though ;), this method should ONLY be called by those who know what they
2390: * are doing, whatever you think you know about how this method operates is irrelevant
2391: * which is why the dangers of calling this method are not documented...
2392: * <p>
2393: * YOU HAVE BEEN WARNED!!! NO BUGS WILL BE ACCEPTED THAT ARISE FROM THE CALLING OF
2394: * THIS METHOD!!!
2395: *
2396: * @param c The FROM class.
2397: */
2398: public void setFromObjectClass(Class c) {
2399:
2400: this .objClass = c;
2401:
2402: }
2403:
2404: public Class getFromObjectClass() {
2405:
2406: return this .objClass;
2407:
2408: }
2409:
2410: public void removeBindVariableChangedListener(
2411: BindVariableChangedListener bvl) {
2412:
2413: List l = (List) this .listeners.get("bvs");
2414:
2415: if (l == null) {
2416:
2417: return;
2418:
2419: }
2420:
2421: l.remove(bvl);
2422:
2423: }
2424:
2425: public void addBindVariableChangedListener(
2426: BindVariableChangedListener bvl) {
2427:
2428: List l = (List) this .listeners.get("bvs");
2429:
2430: if (l == null) {
2431:
2432: l = new ArrayList();
2433:
2434: this .listeners.put("bvs", l);
2435:
2436: }
2437:
2438: if (!l.contains(bvl)) {
2439:
2440: l.add(bvl);
2441:
2442: }
2443:
2444: }
2445:
2446: public void removeSaveValueChangedListener(
2447: SaveValueChangedListener svl) {
2448:
2449: List l = (List) this .listeners.get("svs");
2450:
2451: if (l == null) {
2452:
2453: return;
2454:
2455: }
2456:
2457: l.remove(svl);
2458:
2459: }
2460:
2461: public void addSaveValueChangedListener(SaveValueChangedListener svl) {
2462:
2463: List l = (List) this .listeners.get("svs");
2464:
2465: if (l == null) {
2466:
2467: l = new ArrayList();
2468:
2469: this .listeners.put("svs", l);
2470:
2471: }
2472:
2473: if (!l.contains(svl)) {
2474:
2475: l.add(svl);
2476:
2477: }
2478:
2479: }
2480:
2481: public Map getAliases() {
2482:
2483: return this .aliases;
2484:
2485: }
2486:
2487: /**
2488: * Return whether the query should return objects.
2489: *
2490: * @return <code>true</code> if the query should return objects.
2491: */
2492: public boolean isWantObjects() {
2493:
2494: return this .retObjs;
2495:
2496: }
2497:
2498: /**
2499: * Set whether the query should return objects (use <code>true</code>).
2500: * Caution: Do NOT use unless you are sure about what you are doing!
2501: *
2502: * @param v Set to <code>true</code> to indicate that the query should return objects.
2503: */
2504: public void setWantObjects(boolean v) {
2505:
2506: this .retObjs = v;
2507:
2508: }
2509:
2510: /**
2511: * Get the character that represents a wildcard in LIKE searches.
2512: *
2513: * @return The char.
2514: */
2515: public char getWildcardCharacter() {
2516:
2517: return this .wildcardChar;
2518:
2519: }
2520:
2521: /**
2522: * Set the character that represents a wildcard in LIKE searches.
2523: *
2524: * @param c The char.
2525: */
2526: public void setWildcardCharacter(char c) {
2527:
2528: this .wildcardChar = c;
2529:
2530: }
2531:
2532: /**
2533: * Set the object that represents the <a href="http://josql.sourceforge.net/limit-clause.html" target="_blank">limit clause</a>.
2534: * Caution: Do NOT use unless you are sure about what you are doing!
2535: *
2536: * @param l The object.
2537: */
2538: public void setLimit(Limit l) {
2539:
2540: this .limit = l;
2541:
2542: }
2543:
2544: /**
2545: * Get the object that represents the <a href="http://josql.sourceforge.net/limit-clause.html" target="_blank">limit clause</a>.
2546: *
2547: * @return The object.
2548: */
2549: public Limit getLimit() {
2550:
2551: return this .limit;
2552:
2553: }
2554:
2555: /**
2556: * Return whether this Query object has had a statement applied to it
2557: * and has been parsed.
2558: *
2559: * @return Whether the query is associated with a statement.
2560: */
2561: public boolean parsed() {
2562:
2563: return this .isParsed;
2564:
2565: }
2566:
2567: /**
2568: * Indicate whether "distinct" results are required.
2569: *
2570: * @param v Set to <code>true</code> to make the results distinct.
2571: */
2572: public void setWantDistinctResults(boolean v) {
2573:
2574: this .distinctResults = v;
2575:
2576: }
2577:
2578: /**
2579: * Get the results of {@link #execute(java.util.List) executing} this query.
2580: *
2581: * @return The query results.
2582: */
2583: public QueryResults getQueryResults() {
2584:
2585: return this .qd;
2586:
2587: }
2588:
2589: /**
2590: * Get the "order bys". This will return a List of {@link OrderBy} objects.
2591: * This is generally only useful when you want to {@link #reorder(List,String)}
2592: * the search and wish to get access to the textual representation of the order bys.
2593: * <p>
2594: * It is therefore possible to modify the orderbys in place, perhaps by using a different
2595: * expression or changing the direction (since the objects are not cloned before being
2596: * returned). However do so at <b>YOUR OWN RISK</b>. If you do so, then ensure you
2597: * call: {@link #setOrderByColumns(List)}, then: {@link #initOrderByComparator()}
2598: * before re-executing the statement, otherwise nothing will happen!
2599: *
2600: * @return The order bys.
2601: */
2602: public List getOrderByColumns() {
2603:
2604: return new ArrayList(this .orderBys);
2605:
2606: }
2607:
2608: /**
2609: * Set the parent query.
2610: * Caution: Do NOT use unless you are sure about what you are doing!
2611: *
2612: * @param q The parent query.
2613: */
2614: public void setParent(Query q) {
2615:
2616: this .parent = q;
2617:
2618: }
2619:
2620: /**
2621: * Get the parent query.
2622: *
2623: * @return The query, will be <code>null</code> if there is no parent.
2624: */
2625: public Query getParent() {
2626:
2627: return this .parent;
2628:
2629: }
2630:
2631: /**
2632: * Get the top level query if "this" is a sub-query, the query chain is traversed until
2633: * the top level query is found, i.e. when {@link #getParent()} returns null.
2634: *
2635: * @return The top level query, will be <code>null</code> if there is no parent.
2636: */
2637: public Query getTopLevelQuery() {
2638:
2639: Query q = this ;
2640: Query par = null;
2641:
2642: while (true) {
2643:
2644: par = q.getParent();
2645:
2646: if (par == null) {
2647:
2648: break;
2649:
2650: }
2651:
2652: q = par;
2653:
2654: }
2655:
2656: return q;
2657:
2658: }
2659:
2660: /**
2661: * Get a string version of this query suitable for debugging. This will reconstruct the query
2662: * based on the objects it holds that represent the various clauses.
2663: *
2664: * @return The reconstructed query.
2665: */
2666: public String toString() {
2667:
2668: StringBuffer buf = new StringBuffer("SELECT ");
2669:
2670: if (this .distinctResults) {
2671:
2672: buf.append("DISTINCT ");
2673:
2674: }
2675:
2676: if (this .retObjs) {
2677:
2678: buf.append("*");
2679:
2680: } else {
2681:
2682: for (int i = 0; i < this .cols.size(); i++) {
2683:
2684: buf.append(" ");
2685: buf.append(this .cols.get(i));
2686:
2687: if (i < (this .cols.size() - 1)) {
2688:
2689: buf.append(",");
2690:
2691: }
2692:
2693: }
2694:
2695: }
2696:
2697: buf.append(" FROM ");
2698: buf.append(this .from);
2699:
2700: if (this .where != null) {
2701:
2702: buf.append(" WHERE ");
2703:
2704: buf.append(this.where);
2705:
2706: }
2707:
2708: return buf.toString();
2709:
2710: }
2711:
2712: }
|