001: /*
002: * $Header: /export/home/cvsroot/MyPersonalizerRepository/MyPersonalizer/Subsystems/Kernel/Sources/es/udc/mypersonalizer/kernel/model/query/parser/QueryParser.java,v 1.1.1.1 2004/03/25 12:08:37 fbellas Exp $
003: * $Revision: 1.1.1.1 $
004: * $Date: 2004/03/25 12:08:37 $
005: *
006: * =============================================================================
007: *
008: * Copyright (c) 2003, The MyPersonalizer Development Group
009: * (http://www.tic.udc.es/~fbellas/mypersonalizer/index.html) at
010: * University Of A Coruņa
011: * All rights reserved.
012: *
013: * Redistribution and use in source and binary forms, with or without
014: * modification, are permitted provided that the following conditions are met:
015: *
016: * - Redistributions of source code must retain the above copyright notice,
017: * this list of conditions and the following disclaimer.
018: *
019: * - Redistributions in binary form must reproduce the above copyright notice,
020: * this list of conditions and the following disclaimer in the documentation
021: * and/or other materials provided with the distribution.
022: *
023: * - Neither the name of the University Of A Coruņa nor the names of its
024: * contributors may be used to endorse or promote products derived from
025: * this software without specific prior written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
028: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
029: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
030: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
031: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
032: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
033: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
034: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
035: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
036: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
037: * POSSIBILITY OF SUCH DAMAGE.
038: *
039: */
040: package es.udc.mypersonalizer.kernel.model.query.parser;
041:
042: import java.util.List;
043:
044: import org.jaxen.JaxenHandler;
045: import org.jaxen.expr.Expr;
046: import org.jaxen.expr.LocationPath;
047: import org.jaxen.expr.XPathExpr;
048: import org.jaxen.saxpath.SAXPathException;
049: import org.jaxen.saxpath.base.XPathReader;
050: import es.udc.mypersonalizer.kernel.model.query.ast.Query;
051:
052: /**
053: * Parser class for coverting an XQuery-like string into a <code>Query</code>
054: * object.
055: * <p>
056: * The syntax to be used is a subset of the XQuery FLWOR expressions
057: * (for, let, where, order by, return).
058: *
059: * @author Abel Muinho
060: * @since 1.0
061: */
062: public class QueryParser {
063: private String orderByString;
064: private String returnString;
065:
066: private static final String ORDER_BY = "order by ";
067: private static final int ORDER_BY_LENGTH = ORDER_BY.length();
068: private static final String RETURN = "return ";
069: private static final int RETURN_LENGTH = RETURN.length();
070:
071: /**
072: * Method to parse a string representing a query.
073: * @param query the string representing a query.
074: * @return the <code>Query</code> represented by the string.
075: * @throws ParseException if the string does not represent a valid
076: * <code>Query</code>.
077: */
078: public Query parse(String query) throws ParseException {
079: query = normalize(query);
080: tokenize(query);
081: Query result = new Query();
082:
083: result.setReturnClause(createStepList(returnString));
084: if (orderByString != null) {
085: result.setOrderByClause(createStepList(orderByString));
086: }
087: return result;
088: }
089:
090: /**
091: * Replaces all whitespace (newlines, tabs, spaces) by simple spaces. It
092: * also removes all preceding and trailing whitespace.
093: * @return a string with only space chars as whitespace.
094: */
095: private String normalize(String query) {
096: return query.trim().replaceAll("\\s+", " ");
097: }
098:
099: /**
100: * Tokenizes the query in its five possible clauses (for, let, where,
101: * order by and return).
102: * <p>
103: * Right now, only order by and return are supported.
104: *
105: * @param query the query to parse.
106: * @throws ParseException if there are errors in the string.
107: */
108: private void tokenize(String query) throws ParseException {
109: /* Look for optional order by. */
110: if (query.startsWith("order by")) {
111: int returnPos = query.indexOf(" " + RETURN);
112: if (returnPos == -1) {
113: /* return not found. */
114: throw new ParseException(
115: "Return clause is missing from query");
116: }
117: orderByString = query.substring(ORDER_BY_LENGTH, returnPos)
118: .trim();
119: returnString = query.substring(returnPos + RETURN_LENGTH
120: + 1);
121: } else if (query.startsWith(RETURN)) {
122: returnString = query.substring(RETURN_LENGTH);
123: } else {
124: throw new ParseException(
125: "Return clause is missing from query");
126: }
127: returnString = returnString.trim();
128:
129: if (orderByString != null && orderByString.length() == 0) {
130: throw new ParseException("Empty order by clause");
131: }
132: if (returnString.length() == 0) {
133: throw new ParseException("Empty result clause");
134: }
135: }
136:
137: private List createStepList(String xpathString)
138: throws ParseException {
139: XPathReader reader = new XPathReader();
140: JaxenHandler handler = new JaxenHandler();
141: reader.setXPathHandler(handler);
142: try {
143: reader.parse(xpathString);
144: } catch (SAXPathException e) {
145: throw new ParseException(e);
146: }
147: XPathExpr expression = handler.getXPathExpr();
148: Expr rootExpression = expression.getRootExpr();
149: if (rootExpression instanceof LocationPath) {
150: XPathTranslatorVisitor visitor = new XPathTranslatorVisitor();
151: try {
152: rootExpression.accept(visitor);
153: } catch (XPathVisitorException e) {
154: if (e.getCause() != null) {
155: throw new ParseException(e.getCause());
156: } else {
157: throw new ParseException(e);
158: }
159: }
160: return visitor.getResultSteps();
161: } else {
162: throw new ParseException(
163: "Root XPath element must be a LocationPath, found "
164: + expression.getClass().getName());
165: }
166: }
167: }
|