Return-Path: X-Original-To: apmail-jackrabbit-commits-archive@www.apache.org Delivered-To: apmail-jackrabbit-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 5EAE391EF for ; Thu, 23 Feb 2012 15:35:08 +0000 (UTC) Received: (qmail 18728 invoked by uid 500); 23 Feb 2012 15:35:07 -0000 Delivered-To: apmail-jackrabbit-commits-archive@jackrabbit.apache.org Received: (qmail 18676 invoked by uid 500); 23 Feb 2012 15:35:07 -0000 Mailing-List: contact commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jackrabbit.apache.org Delivered-To: mailing list commits@jackrabbit.apache.org Received: (qmail 18634 invoked by uid 99); 23 Feb 2012 15:35:07 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 23 Feb 2012 15:35:07 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 23 Feb 2012 15:34:54 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 234B423888E7; Thu, 23 Feb 2012 15:34:32 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1292828 [1/3] - in /jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query: ./ index/ qom/ qom/tree/ Date: Thu, 23 Feb 2012 15:34:29 -0000 To: commits@jackrabbit.apache.org From: thomasm@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120223153432.234B423888E7@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: thomasm Date: Thu Feb 23 15:34:27 2012 New Revision: 1292828 URL: http://svn.apache.org/viewvc?rev=1292828&view=rev Log: Query implementation (WIP) Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/NamespaceRegistryImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/ParserSQL2.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/ParserXPath.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryManagerImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryResultImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RangeIteratorImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RowImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RowIteratorImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/index/ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/index/Index.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/QueryObjectModelFactoryImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/QueryObjectModelImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/AndImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/BindVariableValueImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/ChildNodeImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/ChildNodeJoinConditionImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/ColumnImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/ComparisonImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/ConstraintImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/DescendantNodeImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/DescendantNodeJoinConditionImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/DynamicOperandImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/EquiJoinConditionImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/FullTextSearchImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/FullTextSearchScoreImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/JoinConditionImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/JoinImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/JoinType.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/LengthImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/LiteralImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/LowerCaseImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/NodeLocalNameImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/NodeNameImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/NotImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/Operator.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/OrImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/Order.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/OrderingImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/PropertyExistenceImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/PropertyValueImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/QOMNode.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/QOMTreeVisitor.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/QOMVisitor.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/SameNodeImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/SameNodeJoinConditionImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/SelectorImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/SourceImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/StaticOperandImpl.java jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/qom/tree/UpperCaseImpl.java Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/NamespaceRegistryImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/NamespaceRegistryImpl.java?rev=1292828&view=auto ============================================================================== --- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/NamespaceRegistryImpl.java (added) +++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/NamespaceRegistryImpl.java Thu Feb 23 15:34:27 2012 @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.query; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import javax.jcr.AccessDeniedException; +import javax.jcr.NamespaceException; +import javax.jcr.NamespaceRegistry; +import javax.jcr.RepositoryException; +import javax.jcr.UnsupportedRepositoryOperationException; +import org.apache.jackrabbit.query.val.ExceptionFactory; +import org.apache.jackrabbit.query.val.Val; + +/** + * The implementation of the corresponding JCR interface. + */ +public class NamespaceRegistryImpl implements NamespaceRegistry { + + public static final NamespaceRegistryImpl BUILT_IN = new NamespaceRegistryImpl(null); + + static { + NamespaceRegistryImpl registry = BUILT_IN; + registry.register(PREFIX_JCR, NAMESPACE_JCR); + registry.register(PREFIX_NT, NAMESPACE_NT); + registry.register(PREFIX_MIX, NAMESPACE_MIX); + registry.register(PREFIX_XML, NAMESPACE_XML); + registry.register(PREFIX_EMPTY, NAMESPACE_EMPTY); + registry.register("rep", "internal"); + } + + private final NamespaceRegistryImpl parent; + private LinkedHashMap prefixToUri = new LinkedHashMap(); + private HashMap uriToPrefix = new HashMap(); + private boolean shareMapsWithParent; + + private NamespaceRegistryImpl(NamespaceRegistryImpl parent) { + this.parent = parent; + if (parent != null) { + prefixToUri = parent.prefixToUri; + uriToPrefix = parent.uriToPrefix; + shareMapsWithParent = true; + } + } + + public static NamespaceRegistryImpl createLocalInstance() { + return new NamespaceRegistryImpl(BUILT_IN); + } + + public String nameToString(Val name) { + if (name.getType() == Val.TYPE_MULTI_VALUE) { + Val[] namePair = name.getArray(); + String uri = getPrefix(namePair[0].getString()); + String localName = namePair[1].getString(); + StringBuilder buff = new StringBuilder(uri.length() + 1 + localName.length()); + buff.append(uri).append(':').append(localName); + return buff.toString(); + } + return name.getString(); + } + + public Val parseName(String name) { + if (name.charAt(0) == '{') { + int index = name.indexOf('}'); + Val namespace = Val.get(name.substring(1, index)); + Val localName = Val.get(name.substring(index + 1)); + return Val.get(namespace, localName); + } + int index = name.indexOf(':'); + if (index < 0) { + return Val.get(name); + } + String prefix = name.substring(0, index); + String ns = getURI(prefix); + if (ns == null) { + throw ExceptionFactory.illegalArgument("Not URI found for prefix: {0}", prefix); + } + Val namespace = Val.get(ns); + Val localName = Val.get(name.substring(index + 1)); + return Val.get(namespace, localName); + } + + public String getPrefix(String uri) { + return uriToPrefix.get(uri); + } + + public synchronized String[] getPrefixes() throws RepositoryException { + String[] prefixes = new String[prefixToUri.size()]; + prefixToUri.keySet().toArray(prefixes); + return prefixes; + } + + public String getURI(String prefix) { + return prefixToUri.get(prefix); + } + + public synchronized String[] getURIs() throws RepositoryException { + String[] uris = new String[uriToPrefix.size()]; + uriToPrefix.keySet().toArray(uris); + return uris; + } + + public void registerNamespace(String prefix, String uri) throws NamespaceException, + UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException { + if (parent.prefixToUri.containsKey(prefix) || parent.uriToPrefix.containsKey(uri)) { + if (!parent.prefixToUri.get(prefix).equals(uri)) { + throw ExceptionFactory.namespace("Can not redefine built-in prefix: {0}", prefix); + } + } else if (prefix.equalsIgnoreCase("xml")) { + throw ExceptionFactory.namespace("Can not (re-)define an xml variant: {0}", prefix); + } + register(prefix, uri); + } + + private synchronized void register(String prefix, String uri) { + if (shareMapsWithParent) { + prefixToUri = new LinkedHashMap(prefixToUri); + uriToPrefix = new HashMap(uriToPrefix); + shareMapsWithParent = false; + } + prefixToUri.put(prefix, uri); + uriToPrefix.put(uri, prefix); + } + + public synchronized void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, + AccessDeniedException, RepositoryException { + if (parent.prefixToUri.containsKey(prefix)) { + throw ExceptionFactory.namespace("Can not unregister a built-in prefix: {0}", prefix); + } + if (!prefixToUri.containsKey(prefix)) { + throw ExceptionFactory.namespace("Not registered: {0}", prefix); + } + String uri = prefixToUri.remove(prefix); + uriToPrefix.remove(uri); + } + + public String toString() { + StringBuilder buff = new StringBuilder(); + for (Entry e : prefixToUri.entrySet()) { + buff.append('<').append(e.getKey()).append('=').append(e.getValue()).append('>'); + } + return buff.toString(); + } + +} Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/ParserSQL2.java URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/ParserSQL2.java?rev=1292828&view=auto ============================================================================== --- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/ParserSQL2.java (added) +++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/ParserSQL2.java Thu Feb 23 15:34:27 2012 @@ -0,0 +1,1002 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.query; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; + +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.ValueFactory; +import javax.jcr.query.InvalidQueryException; +import javax.jcr.query.qom.BindVariableValue; +import javax.jcr.query.qom.Column; +import javax.jcr.query.qom.Constraint; +import javax.jcr.query.qom.DynamicOperand; +import javax.jcr.query.qom.JoinCondition; +import javax.jcr.query.qom.Literal; +import javax.jcr.query.qom.Ordering; +import javax.jcr.query.qom.PropertyExistence; +import javax.jcr.query.qom.PropertyValue; +import javax.jcr.query.qom.QueryObjectModel; +import javax.jcr.query.qom.QueryObjectModelConstants; +import javax.jcr.query.qom.QueryObjectModelFactory; +import javax.jcr.query.qom.Selector; +import javax.jcr.query.qom.Source; +import javax.jcr.query.qom.StaticOperand; +import org.apache.jackrabbit.query.val.ExceptionFactory; + +/** + * The SQL2 parser can convert a JCR-SQL2 query to a QueryObjectModel. + */ +public class ParserSQL2 { + + // Character types, used during the tokenizer phase + private static final int CHAR_END = -1, CHAR_VALUE = 2, CHAR_QUOTED = 3; + private static final int CHAR_NAME = 4, CHAR_SPECIAL_1 = 5, CHAR_SPECIAL_2 = 6; + private static final int CHAR_STRING = 7, CHAR_DECIMAL = 8; + + // Token types + private static final int KEYWORD = 1, IDENTIFIER = 2, PARAMETER = 3, END = 4, VALUE = 5; + private static final int MINUS = 12, PLUS = 13, OPEN = 14, CLOSE = 15; + + // The query as an array of characters and character types + private String statement; + private char[] statementChars; + private int[] characterTypes; + + // The current state of the parser + private int parseIndex; + private int currentTokenType; + private String currentToken; + private boolean currentTokenQuoted; + private Value currentValue; + private ArrayList expected; + + // The bind variables + private HashMap bindVariables; + + // The list of selectors of this query + private ArrayList selectors; + + // SQL injection protection: if disabled, literals are not allowed + private boolean allowTextLiterals = true, allowNumberLiterals = true; + + private QueryObjectModelFactory factory; + private ValueFactory valueFactory; + + /** + * Create a new parser. A parser can be re-used, but it is not thread safe. + * + * @param factory the query object model factory + * @param valueFactory the value factory + */ + public ParserSQL2(QueryObjectModelFactory factory, ValueFactory valueFactory) { + this.factory = factory; + this.valueFactory = valueFactory; + } + + /** + * Parse a JCR-SQL2 query and return the query object model + * + * @param query the query string + * @return the query object model + * @throws RepositoryException if parsing failed + */ + public QueryObjectModel createQueryObjectModel(String query) throws RepositoryException { + initialize(query); + selectors = new ArrayList(); + expected = new ArrayList(); + bindVariables = new HashMap(); + read(); + read("SELECT"); + ArrayList list = parseColumns(); + read("FROM"); + Source source = parseSource(); + Column[] columnArray = resolveColumns(list); + Constraint constraint = null; + if (readIf("WHERE")) { + constraint = parseConstraint(); + } + Ordering[] orderings = null; + if (readIf("ORDER")) { + read("BY"); + orderings = parseOrder(); + } + if (currentToken.length() > 0) { + throw getSyntaxError(""); + } + return factory.createQuery(source, constraint, orderings, columnArray); + } + + private Selector parseSelector() throws RepositoryException { + String nodeTypeName = readName(); + if (readIf("AS")) { + String selectorName = readName(); + return factory.selector(nodeTypeName, selectorName); + } else { + return factory.selector(nodeTypeName, nodeTypeName); + } + } + + private String readName() throws RepositoryException { + if (readIf("[")) { + if (currentTokenType == VALUE) { + Value value = readString(); + read("]"); + return value.getString(); + } else { + int level = 1; + StringBuilder buff = new StringBuilder(); + while (true) { + if (isToken("]")) { + if (--level <= 0) { + read(); + break; + } + } else if (isToken("[")) { + level++; + } + buff.append(readAny()); + } + return buff.toString(); + } + } else { + return readAny(); + } + } + + private Source parseSource() throws RepositoryException { + Selector selector = parseSelector(); + selectors.add(selector); + Source source = selector; + while (true) { + String joinType; + if (readIf("RIGHT")) { + read("OUTER"); + joinType = QueryObjectModelConstants.JCR_JOIN_TYPE_RIGHT_OUTER; + } else if (readIf("LEFT")) { + read("OUTER"); + joinType = QueryObjectModelConstants.JCR_JOIN_TYPE_LEFT_OUTER; + } else if (readIf("INNER")) { + joinType = QueryObjectModelConstants.JCR_JOIN_TYPE_INNER; + } else { + break; + } + read("JOIN"); + selector = parseSelector(); + selectors.add(selector); + read("ON"); + JoinCondition on = parseJoinCondition(); + source = factory.join(source, selector, joinType, on); + } + return source; + } + + private JoinCondition parseJoinCondition() throws RepositoryException { + boolean identifier = currentTokenType == IDENTIFIER; + String name = readName(); + JoinCondition c; + if (identifier && readIf("(")) { + if ("ISSAMENODE".equalsIgnoreCase(name)) { + String selector1 = readName(); + read(","); + String selector2 = readName(); + if (readIf(",")) { + c = factory.sameNodeJoinCondition(selector1, selector2, readPath()); + } else { + // TODO verify "." is correct + c = factory.sameNodeJoinCondition(selector1, selector2, "."); + } + } else if ("ISCHILDNODE".equalsIgnoreCase(name)) { + String childSelector = readName(); + read(","); + c = factory.childNodeJoinCondition(childSelector, readName()); + } else if ("ISDESCENDANTNODE".equalsIgnoreCase(name)) { + String descendantSelector = readName(); + read(","); + c = factory.descendantNodeJoinCondition(descendantSelector, readName()); + } else { + throw getSyntaxError("ISSAMENODE, ISCHILDNODE, or ISDESCENDANTNODE"); + } + read(")"); + return c; + } else { + String selector1 = name; + read("."); + String property1 = readName(); + read("="); + String selector2 = readName(); + read("."); + return factory.equiJoinCondition(selector1, property1, selector2, readName()); + } + } + + private Constraint parseConstraint() throws RepositoryException { + Constraint a = parseAnd(); + while (readIf("OR")) { + a = factory.or(a, parseAnd()); + } + return a; + } + + private Constraint parseAnd() throws RepositoryException { + Constraint a = parseCondition(); + while (readIf("AND")) { + a = factory.and(a, parseCondition()); + } + return a; + } + + private Constraint parseCondition() throws RepositoryException { + Constraint a; + if (readIf("NOT")) { + a = factory.not(parseConstraint()); + } else if (readIf("(")) { + a = parseConstraint(); + read(")"); + } else if (currentTokenType == IDENTIFIER) { + String identifier = readName(); + if (readIf("(")) { + a = parseConditionFuntionIf(identifier); + if (a == null) { + DynamicOperand op = parseExpressionFunction(identifier); + a = parseCondition(op); + } + } else if (readIf(".")) { + a = parseCondition(factory.propertyValue(identifier, readName())); + } else { + a = parseCondition(factory.propertyValue(getOnlySelectorName(), identifier)); + } + } else if ("[".equals(currentToken)) { + String name = readName(); + if (readIf(".")) { + a = parseCondition(factory.propertyValue(name, readName())); + } else { + a = parseCondition(factory.propertyValue(getOnlySelectorName(), name)); + } + } else { + throw getSyntaxError(); + } + return a; + } + + private Constraint parseCondition(DynamicOperand left) throws RepositoryException { + Constraint c; + + if (readIf("=")) { + String operator = QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO; + c = factory.comparison(left, operator, parseStaticOperand()); + } else if (readIf("<>")) { + String operator = QueryObjectModelConstants.JCR_OPERATOR_NOT_EQUAL_TO; + c = factory.comparison(left, operator, parseStaticOperand()); + } else if (readIf("<")) { + String operator = QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN; + c = factory.comparison(left, operator, parseStaticOperand()); + } else if (readIf(">")) { + String operator = QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN; + c = factory.comparison(left, operator, parseStaticOperand()); + } else if (readIf("<=")) { + String operator = QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO; + c = factory.comparison(left, operator, parseStaticOperand()); + } else if (readIf(">=")) { + String operator = QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO; + c = factory.comparison(left, operator, parseStaticOperand()); + } else if (readIf("LIKE")) { + String operator = QueryObjectModelConstants.JCR_OPERATOR_LIKE; + c = factory.comparison(left, operator, parseStaticOperand()); + } else if (readIf("IS")) { + boolean not = readIf("NOT"); + read("NULL"); + if (!(left instanceof PropertyValue)) { + throw getSyntaxError("propertyName (NOT NULL is only supported for properties)"); + } + PropertyValue p = (PropertyValue) left; + c = getPropertyExistence(p); + if (!not) { + c = factory.not(c); + } + } else if (readIf("NOT")) { + if (readIf("IS")) { + read("NULL"); + if (!(left instanceof PropertyValue)) { + throw ExceptionFactory.repository( + "Only property values can be tested for NOT IS NULL; got: " + + left.getClass().getName()); + } + PropertyValue pv = (PropertyValue) left; + c = getPropertyExistence(pv); + } else { + read("LIKE"); + String operator = QueryObjectModelConstants.JCR_OPERATOR_LIKE; + c = factory.comparison(left, operator, parseStaticOperand()); + c = factory.not(c); + } + } else { + throw getSyntaxError(); + } + return c; + } + + private PropertyExistence getPropertyExistence(PropertyValue p) throws InvalidQueryException, RepositoryException { + return factory.propertyExistence(p.getSelectorName(), p.getPropertyName()); + } + + private Constraint parseConditionFuntionIf(String functionName) throws RepositoryException { + Constraint c; + if ("CONTAINS".equalsIgnoreCase(functionName)) { + String name = readName(); + if (readIf(".")) { + if (readIf("*")) { + read(","); + c = factory.fullTextSearch( + name, null, parseStaticOperand()); + } else { + String selector = name; + name = readName(); + read(","); + c = factory.fullTextSearch( + selector, name, parseStaticOperand()); + } + } else { + read(","); + c = factory.fullTextSearch( + getOnlySelectorName(), name, + parseStaticOperand()); + } + } else if ("ISSAMENODE".equalsIgnoreCase(functionName)) { + String name = readName(); + if (readIf(",")) { + c = factory.sameNode(name, readPath()); + } else { + c = factory.sameNode(getOnlySelectorName(), name); + } + } else if ("ISCHILDNODE".equalsIgnoreCase(functionName)) { + String name = readName(); + if (readIf(",")) { + c = factory.childNode(name, readPath()); + } else { + c = factory.childNode(getOnlySelectorName(), name); + } + } else if ("ISDESCENDANTNODE".equalsIgnoreCase(functionName)) { + String name = readName(); + if (readIf(",")) { + c = factory.descendantNode(name, readPath()); + } else { + c = factory.descendantNode(getOnlySelectorName(), name); + } + } else { + return null; + } + read(")"); + return c; + } + + private String readPath() throws RepositoryException { + return readName(); + } + + private DynamicOperand parseDynamicOperand() throws RepositoryException { + boolean identifier = currentTokenType == IDENTIFIER; + String name = readName(); + if (identifier && readIf("(")) { + return parseExpressionFunction(name); + } else { + return parsePropertyValue(name); + } + } + + private DynamicOperand parseExpressionFunction(String functionName) throws RepositoryException { + DynamicOperand op; + if ("LENGTH".equalsIgnoreCase(functionName)) { + op = factory.length(parsePropertyValue(readName())); + } else if ("NAME".equalsIgnoreCase(functionName)) { + if (isToken(")")) { + op = factory.nodeName(getOnlySelectorName()); + } else { + op = factory.nodeName(readName()); + } + } else if ("LOCALNAME".equalsIgnoreCase(functionName)) { + if (isToken(")")) { + op = factory.nodeLocalName(getOnlySelectorName()); + } else { + op = factory.nodeLocalName(readName()); + } + } else if ("SCORE".equalsIgnoreCase(functionName)) { + if (isToken(")")) { + op = factory.fullTextSearchScore(getOnlySelectorName()); + } else { + op = factory.fullTextSearchScore(readName()); + } + } else if ("LOWER".equalsIgnoreCase(functionName)) { + op = factory.lowerCase(parseDynamicOperand()); + } else if ("UPPER".equalsIgnoreCase(functionName)) { + op = factory.upperCase(parseDynamicOperand()); + } else { + throw getSyntaxError("LENGTH, NAME, LOCALNAME, SCORE, LOWER, UPPER, or CAST"); + } + read(")"); + return op; + } + + private PropertyValue parsePropertyValue(String name) throws RepositoryException { + if (readIf(".")) { + return factory.propertyValue(name, readName()); + } else { + return factory.propertyValue(getOnlySelectorName(), name); + } + } + + private StaticOperand parseStaticOperand() throws RepositoryException { + if (currentTokenType == PLUS) { + read(); + } else if (currentTokenType == MINUS) { + read(); + if (currentTokenType != VALUE) { + throw getSyntaxError("number"); + } + int valueType = currentValue.getType(); + switch (valueType) { + case PropertyType.LONG: + currentValue = valueFactory.createValue(-currentValue.getLong()); + break; + case PropertyType.DOUBLE: + currentValue = valueFactory.createValue(-currentValue.getDouble()); + break; + case PropertyType.BOOLEAN: + currentValue = valueFactory.createValue(!currentValue.getBoolean()); + break; + case PropertyType.DECIMAL: + currentValue = valueFactory.createValue(currentValue.getDecimal().negate()); + break; + default: + throw getSyntaxError("Illegal operation: -" + currentValue); + } + } + if (currentTokenType == VALUE) { + Literal literal = getUncastLiteral(currentValue); + read(); + return literal; + } else if (currentTokenType == PARAMETER) { + read(); + String name = readName(); + if (readIf(":")) { + name = name + ":" + readName(); + } + BindVariableValue var = bindVariables.get(name); + if (var == null) { + var = factory.bindVariable(name); + bindVariables.put(name, var); + } + return var; + } else if (readIf("TRUE")) { + Literal literal = getUncastLiteral(valueFactory.createValue(true)); + return literal; + } else if (readIf("FALSE")) { + Literal literal = getUncastLiteral(valueFactory.createValue(false)); + return literal; + } else if (readIf("CAST")) { + read("("); + StaticOperand op = parseStaticOperand(); + if (!(op instanceof Literal)) { + throw getSyntaxError("literal"); + } + Literal literal = (Literal) op; + Value value = literal.getLiteralValue(); + read("AS"); + value = parseCastAs(value); + read(")"); + // CastLiteral + literal = factory.literal(value); + return literal; + } else { + throw getSyntaxError("static operand"); + } + } + + /** + * Create a literal from a parsed value. + * + * @param value the original value + * @return the literal + */ + private Literal getUncastLiteral(Value value) throws RepositoryException { + return factory.literal(value); + } + + private Value parseCastAs(Value value) throws RepositoryException { + if (readIf("STRING")) { + return valueFactory.createValue(value.getString()); + } else if (readIf("BINARY")) { + return valueFactory.createValue(value.getBinary()); + } else if (readIf("DATE")) { + return valueFactory.createValue(value.getDate()); + } else if (readIf("LONG")) { + return valueFactory.createValue(value.getLong()); + } else if (readIf("DOUBLE")) { + return valueFactory.createValue(value.getDouble()); + } else if (readIf("DECIMAL")) { + return valueFactory.createValue(value.getDecimal()); + } else if (readIf("BOOLEAN")) { + return valueFactory.createValue(value.getBoolean()); + } else if (readIf("NAME")) { + return valueFactory.createValue(value.getString(), PropertyType.NAME); + } else if (readIf("PATH")) { + return valueFactory.createValue(value.getString(), PropertyType.PATH); + } else if (readIf("REFERENCE")) { + return valueFactory.createValue(value.getString(), PropertyType.REFERENCE); + } else if (readIf("WEAKREFERENCE")) { + return valueFactory.createValue(value.getString(), PropertyType.WEAKREFERENCE); + } else if (readIf("URI")) { + return valueFactory.createValue(value.getString(), PropertyType.URI); + } else { + throw getSyntaxError("data type (STRING|BINARY|...)"); + } + } + + private Ordering[] parseOrder() throws RepositoryException { + ArrayList orderList = new ArrayList(); + do { + Ordering ordering; + DynamicOperand op = parseDynamicOperand(); + if (readIf("DESC")) { + ordering = factory.descending(op); + } else { + readIf("ASC"); + ordering = factory.ascending(op); + } + orderList.add(ordering); + } while (readIf(",")); + Ordering[] orderings = new Ordering[orderList.size()]; + orderList.toArray(orderings); + return orderings; + } + + private ArrayList parseColumns() throws RepositoryException { + ArrayList list = new ArrayList(); + if (readIf("*")) { + list.add(new ColumnOrWildcard()); + } else { + do { + ColumnOrWildcard column = new ColumnOrWildcard(); + column.propertyName = readName(); + if (readIf(".")) { + column.selectorName = column.propertyName; + if (readIf("*")) { + column.propertyName = null; + } else { + column.propertyName = readName(); + if (readIf("AS")) { + column.columnName = readName(); + } + } + } else { + if (readIf("AS")) { + column.columnName = readName(); + } + } + list.add(column); + } while (readIf(",")); + } + return list; + } + + private Column[] resolveColumns(ArrayList list) throws RepositoryException { + ArrayList columns = new ArrayList(); + for (ColumnOrWildcard c : list) { + if (c.propertyName == null) { + for (Selector selector : selectors) { + if (c.selectorName == null + || c.selectorName + .equals(selector.getSelectorName())) { + Column column = factory.column(selector + .getSelectorName(), null, null); + columns.add(column); + } + } + } else { + Column column; + if (c.selectorName != null) { + column = factory.column(c.selectorName, c.propertyName, c.columnName); + } else if (c.columnName != null) { + column = factory.column(getOnlySelectorName(), c.propertyName, c.columnName); + } else { + column = factory.column(getOnlySelectorName(), c.propertyName, c.propertyName); + } + columns.add(column); + } + } + Column[] array = new Column[columns.size()]; + columns.toArray(array); + return array; + } + + private boolean readIf(String token) throws RepositoryException { + if (isToken(token)) { + read(); + return true; + } + return false; + } + + private boolean isToken(String token) { + boolean result = token.equalsIgnoreCase(currentToken) && !currentTokenQuoted; + if (result) { + return true; + } + addExpected(token); + return false; + } + + private void read(String expected) throws RepositoryException { + if (!expected.equalsIgnoreCase(currentToken) || currentTokenQuoted) { + throw getSyntaxError(expected); + } + read(); + } + + private String readAny() throws RepositoryException { + if (currentTokenType == END) { + throw getSyntaxError("a token"); + } + String s; + if (currentTokenType == VALUE) { + s = currentValue.getString(); + } else { + s = currentToken; + } + read(); + return s; + } + + private Value readString() throws RepositoryException { + if (currentTokenType != VALUE) { + throw getSyntaxError("string value"); + } + Value value = currentValue; + read(); + return value; + } + + private void addExpected(String token) { + if (expected != null) { + expected.add(token); + } + } + + private void initialize(String query) throws InvalidQueryException { + if (query == null) { + query = ""; + } + statement = query; + int len = query.length() + 1; + char[] command = new char[len]; + int[] types = new int[len]; + len--; + query.getChars(0, len, command, 0); + command[len] = ' '; + int startLoop = 0; + for (int i = 0; i < len; i++) { + char c = command[i]; + int type = 0; + switch (c) { + case '/': + case '-': + case '(': + case ')': + case '{': + case '}': + case '*': + case ',': + case ';': + case '+': + case '%': + case '?': + case '$': + case '[': + case ']': + type = CHAR_SPECIAL_1; + break; + case '!': + case '<': + case '>': + case '|': + case '=': + case ':': + type = CHAR_SPECIAL_2; + break; + case '.': + type = CHAR_DECIMAL; + break; + case '\'': + type = CHAR_STRING; + types[i] = CHAR_STRING; + startLoop = i; + while (command[++i] != '\'') { + checkRunOver(i, len, startLoop); + } + break; + case '\"': + type = CHAR_QUOTED; + types[i] = CHAR_QUOTED; + startLoop = i; + while (command[++i] != '\"') { + checkRunOver(i, len, startLoop); + } + break; + case '_': + type = CHAR_NAME; + break; + default: + if (c >= 'a' && c <= 'z') { + type = CHAR_NAME; + } else if (c >= 'A' && c <= 'Z') { + type = CHAR_NAME; + } else if (c >= '0' && c <= '9') { + type = CHAR_VALUE; + } else { + if (Character.isJavaIdentifierPart(c)) { + type = CHAR_NAME; + } + } + } + types[i] = (byte) type; + } + statementChars = command; + types[len] = CHAR_END; + characterTypes = types; + parseIndex = 0; + } + + private void checkRunOver(int i, int len, int startLoop) throws InvalidQueryException { + if (i >= len) { + parseIndex = startLoop; + throw getSyntaxError(); + } + } + + private void read() throws RepositoryException { + currentTokenQuoted = false; + if (expected != null) { + expected.clear(); + } + int[] types = characterTypes; + int i = parseIndex; + int type = types[i]; + while (type == 0) { + type = types[++i]; + } + int start = i; + char[] chars = statementChars; + char c = chars[i++]; + currentToken = ""; + switch (type) { + case CHAR_NAME: + while (true) { + type = types[i]; + if (type != CHAR_NAME && type != CHAR_VALUE) { + c = chars[i]; + break; + } + i++; + } + currentToken = statement.substring(start, i); + if (currentToken.length() == 0) { + throw getSyntaxError(); + } + currentTokenType = IDENTIFIER; + parseIndex = i; + return; + case CHAR_SPECIAL_2: + if (types[i] == CHAR_SPECIAL_2) { + i++; + } + // fall through + case CHAR_SPECIAL_1: + currentToken = statement.substring(start, i); + switch (c) { + case '$': + currentTokenType = PARAMETER; + break; + case '+': + currentTokenType = PLUS; + break; + case '-': + currentTokenType = MINUS; + break; + case '(': + currentTokenType = OPEN; + break; + case ')': + currentTokenType = CLOSE; + break; + default: + currentTokenType = KEYWORD; + } + parseIndex = i; + return; + case CHAR_VALUE: + long number = c - '0'; + while (true) { + c = chars[i]; + if (c < '0' || c > '9') { + if (c == '.') { + readDecimal(start, i); + break; + } + if (c == 'E' || c == 'e') { + readDecimal(start, i); + break; + } + checkLiterals(false); + currentValue = valueFactory.createValue(number); + currentTokenType = VALUE; + currentToken = "0"; + parseIndex = i; + break; + } + number = number * 10 + (c - '0'); + if (number > Integer.MAX_VALUE) { + readDecimal(start, i); + break; + } + i++; + } + return; + case CHAR_DECIMAL: + if (types[i] != CHAR_VALUE) { + currentTokenType = KEYWORD; + currentToken = "."; + parseIndex = i; + return; + } + readDecimal(i - 1, i); + return; + case CHAR_STRING: + readString(i, '\''); + return; + case CHAR_QUOTED: + readString(i, '\"'); + return; + case CHAR_END: + currentToken = ""; + currentTokenType = END; + parseIndex = i; + return; + default: + throw getSyntaxError(); + } + } + + private void readString(int i, char end) throws RepositoryException { + char[] chars = statementChars; + String result = null; + while (true) { + for (int begin = i;; i++) { + if (chars[i] == end) { + if (result == null) { + result = statement.substring(begin, i); + } else { + result += statement.substring(begin - 1, i); + } + break; + } + } + if (chars[++i] != end) { + break; + } + i++; + } + currentToken = "'"; + checkLiterals(false); + currentValue = valueFactory.createValue(result); + parseIndex = i; + currentTokenType = VALUE; + } + + private void checkLiterals(boolean text) throws InvalidQueryException { + if (text && !allowTextLiterals || (!text && !allowNumberLiterals)) { + throw getSyntaxError("bind variable (literals of this type not allowed)"); + } + } + + private void readDecimal(int start, int i) throws RepositoryException { + char[] chars = statementChars; + int[] types = characterTypes; + while (true) { + int t = types[i]; + if (t != CHAR_DECIMAL && t != CHAR_VALUE) { + break; + } + i++; + } + if (chars[i] == 'E' || chars[i] == 'e') { + i++; + if (chars[i] == '+' || chars[i] == '-') { + i++; + } + if (types[i] != CHAR_VALUE) { + throw getSyntaxError(); + } + while (types[++i] == CHAR_VALUE) { + // go until the first non-number + } + } + parseIndex = i; + String sub = statement.substring(start, i); + BigDecimal bd; + try { + bd = new BigDecimal(sub); + } catch (NumberFormatException e) { + throw ExceptionFactory.invalidQuery("Data conversion error converting " + sub + " to BigDecimal: " + e); + } + checkLiterals(false); + + currentValue = valueFactory.createValue(bd); + currentTokenType = VALUE; + } + + private InvalidQueryException getSyntaxError() { + if (expected == null || expected.size() == 0) { + return getSyntaxError(null); + } else { + StringBuilder buff = new StringBuilder(); + for (String exp : expected) { + if (buff.length() > 0) { + buff.append(", "); + } + buff.append(exp); + } + return getSyntaxError(buff.toString()); + } + } + + private InvalidQueryException getSyntaxError(String expected) { + int index = Math.min(parseIndex, statement.length() - 1); + String query = statement.substring(0, index) + "(*)" + statement.substring(index).trim(); + if (expected != null) { + query += "; expected: " + expected; + } + return new InvalidQueryException("Query:\n" + query); + } + + /** + * Represents a column or a wildcard in a SQL expression. + * This class is temporarily used during parsing. + */ + static class ColumnOrWildcard { + String selectorName; + String propertyName; + String columnName; + } + + /** + * Get the selector name if only one selector exists in the query. + * If more than one selector exists, an exception is thrown. + * + * @return the selector name + */ + private String getOnlySelectorName() throws RepositoryException { + if (selectors.size() > 1) { + throw getSyntaxError("Need to specify the selector name because the query contains more than one selector."); + } + return selectors.get(0).getSelectorName(); + } + +} Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/ParserXPath.java URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/ParserXPath.java?rev=1292828&view=auto ============================================================================== --- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/ParserXPath.java (added) +++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/ParserXPath.java Thu Feb 23 15:34:27 2012 @@ -0,0 +1,458 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.query; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.ValueFactory; +import javax.jcr.query.InvalidQueryException; +import javax.jcr.query.qom.BindVariableValue; +import javax.jcr.query.qom.Column; +import javax.jcr.query.qom.Constraint; +import javax.jcr.query.qom.Ordering; +import javax.jcr.query.qom.QueryObjectModel; +import javax.jcr.query.qom.QueryObjectModelFactory; +import javax.jcr.query.qom.Selector; +import javax.jcr.query.qom.Source; +import org.apache.jackrabbit.query.ParserSQL2.ColumnOrWildcard; +import org.apache.jackrabbit.query.qom.QueryObjectModelFactoryImpl; +import org.apache.jackrabbit.query.val.ExceptionFactory; +import org.apache.jackrabbit.query.val.ValueFactoryImpl; + + +/** + * The XPath parser can convert a XPATH query to a QueryObjectModel. + */ +public class ParserXPath { + + // Character types, used during the tokenizer phase + private static final int CHAR_END = -1, CHAR_VALUE = 2, CHAR_QUOTED = 3; + private static final int CHAR_NAME = 4, CHAR_SPECIAL_1 = 5, CHAR_SPECIAL_2 = 6; + private static final int CHAR_STRING = 7, CHAR_DECIMAL = 8; + + // Token types + private static final int KEYWORD = 1, IDENTIFIER = 2, PARAMETER = 3, END = 4, VALUE = 5; + private static final int MINUS = 12, PLUS = 13, OPEN = 14, CLOSE = 15; + + // The query as an array of characters and character types + private String statement; + private char[] statementChars; + private int[] characterTypes; + + // The current state of the parser + private int parseIndex; + private int currentTokenType; + private String currentToken; + private boolean currentTokenQuoted; + private Value currentValue; + private ArrayList expected; + + // Code injection protection: if disabled, literals are not allowed + private boolean allowTextLiterals = true, allowNumberLiterals = true; + + private QueryObjectModelFactory factory; + private ValueFactory valueFactory; + + /** + * Create a new parser. A parser can be re-used, but it is not thread safe. + * + * @param factory the query object model factory + * @param valueFactory the value factory + */ + public ParserXPath(QueryObjectModelFactory factory, ValueFactory valueFactory) { + this.factory = factory; + this.valueFactory = valueFactory; + } + + /** + * Parse a JCR-SQL2 query and return the query object model + * + * @param query the query string + * @return the query object model + * @throws RepositoryException if parsing failed + */ + public QueryObjectModel createQueryObjectModel(String query) throws RepositoryException { + initialize(query); + expected = new ArrayList(); + read(); + if (readIf("//")) { + System.out.println("any child node"); + } else if (readIf("/")) { + System.out.println("any node: " + readString()); + } + if (currentToken.length() > 0) { + throw getSyntaxError(""); + } + return null; + // return factory.createQuery(source, constraint, orderings, columnArray); + } + + private boolean readIf(String token) throws RepositoryException { + if (isToken(token)) { + read(); + return true; + } + return false; + } + + private boolean isToken(String token) { + boolean result = token.equalsIgnoreCase(currentToken) && !currentTokenQuoted; + if (result) { + return true; + } + addExpected(token); + return false; + } + + private void read(String expected) throws RepositoryException { + if (!expected.equalsIgnoreCase(currentToken) || currentTokenQuoted) { + throw getSyntaxError(expected); + } + read(); + } + + private String readAny() throws RepositoryException { + if (currentTokenType == END) { + throw getSyntaxError("a token"); + } + String s; + if (currentTokenType == VALUE) { + s = currentValue.getString(); + } else { + s = currentToken; + } + read(); + return s; + } + + private Value readString() throws RepositoryException { + if (currentTokenType != VALUE) { + throw getSyntaxError("string value"); + } + Value value = currentValue; + read(); + return value; + } + + private void addExpected(String token) { + if (expected != null) { + expected.add(token); + } + } + + private void initialize(String query) throws InvalidQueryException { + if (query == null) { + query = ""; + } + statement = query; + int len = query.length() + 1; + char[] command = new char[len]; + int[] types = new int[len]; + len--; + query.getChars(0, len, command, 0); + command[len] = ' '; + int startLoop = 0; + for (int i = 0; i < len; i++) { + char c = command[i]; + int type = 0; + switch (c) { + case '/': + case '-': + case '(': + case ')': + case '{': + case '}': + case '*': + case ',': + case ';': + case '+': + case '%': + case '?': + case '$': + case '[': + case ']': + type = CHAR_SPECIAL_1; + break; + case '!': + case '<': + case '>': + case '|': + case '=': + case ':': + type = CHAR_SPECIAL_2; + break; + case '.': + type = CHAR_DECIMAL; + break; + case '\'': + type = CHAR_STRING; + types[i] = CHAR_STRING; + startLoop = i; + while (command[++i] != '\'') { + checkRunOver(i, len, startLoop); + } + break; + case '\"': + type = CHAR_QUOTED; + types[i] = CHAR_QUOTED; + startLoop = i; + while (command[++i] != '\"') { + checkRunOver(i, len, startLoop); + } + break; + case '_': + type = CHAR_NAME; + break; + default: + if (c >= 'a' && c <= 'z') { + type = CHAR_NAME; + } else if (c >= 'A' && c <= 'Z') { + type = CHAR_NAME; + } else if (c >= '0' && c <= '9') { + type = CHAR_VALUE; + } else { + if (Character.isJavaIdentifierPart(c)) { + type = CHAR_NAME; + } + } + } + types[i] = (byte) type; + } + statementChars = command; + types[len] = CHAR_END; + characterTypes = types; + parseIndex = 0; + } + + private void checkRunOver(int i, int len, int startLoop) throws InvalidQueryException { + if (i >= len) { + parseIndex = startLoop; + throw getSyntaxError(); + } + } + + private void read() throws RepositoryException { + currentTokenQuoted = false; + if (expected != null) { + expected.clear(); + } + int[] types = characterTypes; + int i = parseIndex; + int type = types[i]; + while (type == 0) { + type = types[++i]; + } + int start = i; + char[] chars = statementChars; + char c = chars[i++]; + currentToken = ""; + switch (type) { + case CHAR_NAME: + while (true) { + type = types[i]; + if (type != CHAR_NAME && type != CHAR_VALUE) { + c = chars[i]; + break; + } + i++; + } + currentToken = statement.substring(start, i); + if (currentToken.length() == 0) { + throw getSyntaxError(); + } + currentTokenType = IDENTIFIER; + parseIndex = i; + return; + case CHAR_SPECIAL_2: + if (types[i] == CHAR_SPECIAL_2) { + i++; + } + // fall through + case CHAR_SPECIAL_1: + currentToken = statement.substring(start, i); + switch (c) { + case '$': + currentTokenType = PARAMETER; + break; + case '+': + currentTokenType = PLUS; + break; + case '-': + currentTokenType = MINUS; + break; + case '(': + currentTokenType = OPEN; + break; + case ')': + currentTokenType = CLOSE; + break; + default: + currentTokenType = KEYWORD; + } + parseIndex = i; + return; + case CHAR_VALUE: + long number = c - '0'; + while (true) { + c = chars[i]; + if (c < '0' || c > '9') { + if (c == '.') { + readDecimal(start, i); + break; + } + if (c == 'E' || c == 'e') { + readDecimal(start, i); + break; + } + checkLiterals(false); + currentValue = valueFactory.createValue(number); + currentTokenType = VALUE; + currentToken = "0"; + parseIndex = i; + break; + } + number = number * 10 + (c - '0'); + if (number > Integer.MAX_VALUE) { + readDecimal(start, i); + break; + } + i++; + } + return; + case CHAR_DECIMAL: + if (types[i] != CHAR_VALUE) { + currentTokenType = KEYWORD; + currentToken = "."; + parseIndex = i; + return; + } + readDecimal(i - 1, i); + return; + case CHAR_STRING: + readString(i, '\''); + return; + case CHAR_QUOTED: + readString(i, '\"'); + return; + case CHAR_END: + currentToken = ""; + currentTokenType = END; + parseIndex = i; + return; + default: + throw getSyntaxError(); + } + } + + private void readString(int i, char end) throws RepositoryException { + char[] chars = statementChars; + String result = null; + while (true) { + for (int begin = i;; i++) { + if (chars[i] == end) { + if (result == null) { + result = statement.substring(begin, i); + } else { + result += statement.substring(begin - 1, i); + } + break; + } + } + if (chars[++i] != end) { + break; + } + i++; + } + currentToken = "'"; + checkLiterals(false); + currentValue = valueFactory.createValue(result); + parseIndex = i; + currentTokenType = VALUE; + } + + private void checkLiterals(boolean text) throws InvalidQueryException { + if (text && !allowTextLiterals || (!text && !allowNumberLiterals)) { + throw getSyntaxError("bind variable (literals of this type not allowed)"); + } + } + + private void readDecimal(int start, int i) throws RepositoryException { + char[] chars = statementChars; + int[] types = characterTypes; + while (true) { + int t = types[i]; + if (t != CHAR_DECIMAL && t != CHAR_VALUE) { + break; + } + i++; + } + if (chars[i] == 'E' || chars[i] == 'e') { + i++; + if (chars[i] == '+' || chars[i] == '-') { + i++; + } + if (types[i] != CHAR_VALUE) { + throw getSyntaxError(); + } + while (types[++i] == CHAR_VALUE) { + // go until the first non-number + } + } + parseIndex = i; + String sub = statement.substring(start, i); + BigDecimal bd; + try { + bd = new BigDecimal(sub); + } catch (NumberFormatException e) { + throw ExceptionFactory.invalidQuery("Data conversion error converting " + sub + " to BigDecimal: " + e); + } + checkLiterals(false); + + currentValue = valueFactory.createValue(bd); + currentTokenType = VALUE; + } + + private InvalidQueryException getSyntaxError() { + if (expected == null || expected.size() == 0) { + return getSyntaxError(null); + } else { + StringBuilder buff = new StringBuilder(); + for (String exp : expected) { + if (buff.length() > 0) { + buff.append(", "); + } + buff.append(exp); + } + return getSyntaxError(buff.toString()); + } + } + + private InvalidQueryException getSyntaxError(String expected) { + int index = Math.min(parseIndex, statement.length() - 1); + String query = statement.substring(0, index) + "(*)" + statement.substring(index).trim(); + if (expected != null) { + query += "; expected: " + expected; + } + return new InvalidQueryException("Query:\n" + query); + } + + +} + Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryImpl.java?rev=1292828&view=auto ============================================================================== --- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryImpl.java (added) +++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryImpl.java Thu Feb 23 15:34:27 2012 @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.query; + +import javax.jcr.ItemExistsException; +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.PathNotFoundException; +import javax.jcr.RepositoryException; +import javax.jcr.UnsupportedRepositoryOperationException; +import javax.jcr.Value; +import javax.jcr.lock.LockException; +import javax.jcr.nodetype.ConstraintViolationException; +import javax.jcr.query.InvalidQueryException; +import javax.jcr.query.Query; +import javax.jcr.query.QueryResult; +import javax.jcr.query.qom.QueryObjectModel; +import javax.jcr.version.VersionException; + +/** + * The implementation of the corresponding JCR interface. + */ +public class QueryImpl implements Query { + + private final QueryObjectModel qom; + + public QueryImpl(QueryObjectModel qom) { + this.qom = qom; + } + + public void bindValue(String varName, Value value) throws IllegalArgumentException, RepositoryException { + qom.bindValue(varName, value); + } + + public QueryResult execute() throws InvalidQueryException, RepositoryException { + return qom.execute(); + } + + public String[] getBindVariableNames() throws RepositoryException { + return qom.getBindVariableNames(); + } + + public String getLanguage() { + return qom.getLanguage(); + } + + public String getStatement() { + return qom.getStatement(); + } + + public String getStoredQueryPath() throws ItemNotFoundException, RepositoryException { + return qom.getStoredQueryPath(); + } + + public void setLimit(long limit) { + qom.setLimit(limit); + } + + public void setOffset(long offset) { + qom.setOffset(offset); + } + + public Node storeAsNode(String absPath) throws ItemExistsException, PathNotFoundException, VersionException, + ConstraintViolationException, LockException, UnsupportedRepositoryOperationException, RepositoryException { + return qom.storeAsNode(absPath); + } + +} Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryManagerImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryManagerImpl.java?rev=1292828&view=auto ============================================================================== --- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryManagerImpl.java (added) +++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryManagerImpl.java Thu Feb 23 15:34:27 2012 @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.query; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.query.InvalidQueryException; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.query.qom.QueryObjectModel; +import org.apache.jackrabbit.query.qom.QueryObjectModelFactoryImpl; +import org.apache.jackrabbit.query.val.ValueFactoryImpl; +/** + * The implementation of the corresponding JCR interface. + */ +public class QueryManagerImpl implements QueryManager { + + private final QueryObjectModelFactoryImpl qomFactory; + private final ParserSQL2 parserSQL2; + private final ParserXPath parserXPath; + + @SuppressWarnings("deprecation") + private static final String[] SUPPOERTED_QUERY_LANGUAGES = { + Query.JCR_JQOM, + Query.JCR_SQL2, + Query.SQL, + Query.XPATH + }; + + public QueryManagerImpl(QueryObjectModelFactoryImpl qomFactory, ValueFactoryImpl valueFactory) { + this.qomFactory = qomFactory; + parserSQL2 = new ParserSQL2(qomFactory, valueFactory); + parserXPath = new ParserXPath(qomFactory, valueFactory); + } + + public Query createQuery(String statement, String language) throws InvalidQueryException, RepositoryException { + if (Query.JCR_SQL2.equals(language)) { + QueryObjectModel qom = parserSQL2.createQueryObjectModel(statement); + return new QueryImpl(qom); + } else if (Query.XPATH.equals(language)) { + QueryObjectModel qom = parserXPath.createQueryObjectModel(statement); + return new QueryImpl(qom); + } + // TODO Auto-generated method stub + return null; + } + + public QueryObjectModelFactoryImpl getQOMFactory() { + return qomFactory; + } + + public Query getQuery(Node node) throws InvalidQueryException, RepositoryException { + // TODO Auto-generated method stub + return null; + } + + public String[] getSupportedQueryLanguages() throws RepositoryException { + // TODO the list is mutable + return SUPPOERTED_QUERY_LANGUAGES; + } + +} Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryResultImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryResultImpl.java?rev=1292828&view=auto ============================================================================== --- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryResultImpl.java (added) +++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/QueryResultImpl.java Thu Feb 23 15:34:27 2012 @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.query; + +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.query.QueryResult; +import javax.jcr.query.RowIterator; + +/** + * The implementation of the corresponding JCR interface. + */ +public class QueryResultImpl implements QueryResult { + + public String[] getColumnNames() throws RepositoryException { + // TODO Auto-generated method stub + return null; + } + + public NodeIterator getNodes() throws RepositoryException { + // TODO Auto-generated method stub + return null; + } + + public RowIterator getRows() throws RepositoryException { + // TODO Auto-generated method stub + return null; + } + + public String[] getSelectorNames() throws RepositoryException { + // TODO Auto-generated method stub + return null; + } + +} Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RangeIteratorImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RangeIteratorImpl.java?rev=1292828&view=auto ============================================================================== --- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RangeIteratorImpl.java (added) +++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RangeIteratorImpl.java Thu Feb 23 15:34:27 2012 @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.query; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import javax.jcr.RangeIterator; +import javax.jcr.RepositoryException; + +/** + * The implementation of the corresponding JCR interface. + * + * @param the type + */ +public abstract class RangeIteratorImpl implements RangeIterator { + + private final Iterator iterator; + private T next; + private long size; + private long pos; + + protected RangeIteratorImpl(Iterator iterator, long size) { + this.iterator = iterator; + this.size = size; + fetchNext(); + } + + public boolean skip(T x) throws RepositoryException { + return false; + } + + void fetchNext() { + try { + while (true) { + if (!iterator.hasNext()) { + next = null; + break; + } + next = iterator.next(); + if (skip(next)) { + continue; + } + break; + } + } catch (RepositoryException e) { + throw new RuntimeException(e); + } + } + + public Object next() { + return getNext(); + } + + public T getNext() { + pos++; + T result = next; + if (result == null) { + throw new NoSuchElementException(); + } + fetchNext(); + return result; + } + + public long getPosition() { + return pos; + } + + public long getSize() { + return size; + } + + public void skip(long skipNum) { + for (int i = 0; i < skipNum; i++) { + getNext(); + } + } + + public boolean hasNext() { + return next != null; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + +} Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RowImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RowImpl.java?rev=1292828&view=auto ============================================================================== --- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RowImpl.java (added) +++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RowImpl.java Thu Feb 23 15:34:27 2012 @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.query; + +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.query.Row; + +/** + * The implementation of the corresponding JCR interface. + */ +public class RowImpl implements Row { + + public Node getNode() throws RepositoryException { + // TODO + return null; + } + + public Node getNode(String selectorName) throws RepositoryException { + // TODO + return null; + } + + public String getPath() throws RepositoryException { + // TODO + return null; + } + + public String getPath(String selectorName) throws RepositoryException { + // TODO + return null; + } + + public double getScore() throws RepositoryException { + // TODO + return 0; + } + + public double getScore(String selectorName) throws RepositoryException { + // TODO + return 0; + } + + public Value getValue(String columnName) throws ItemNotFoundException, RepositoryException { + // TODO + return null; + } + + public Value[] getValues() throws RepositoryException { + // TODO + return null; + } + +} Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RowIteratorImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RowIteratorImpl.java?rev=1292828&view=auto ============================================================================== --- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RowIteratorImpl.java (added) +++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/RowIteratorImpl.java Thu Feb 23 15:34:27 2012 @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.query; + +import java.util.Collection; +import javax.jcr.query.Row; +import javax.jcr.query.RowIterator; + +/** + * The implementation of the corresponding JCR interface. + */ +public class RowIteratorImpl extends RangeIteratorImpl implements RowIterator { + + protected RowIteratorImpl(Collection coll, long size) { + super(coll.iterator(), size); + } + + public Row nextRow() { + return getNext(); + } + +} Added: jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/index/Index.java URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/index/Index.java?rev=1292828&view=auto ============================================================================== --- jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/index/Index.java (added) +++ jackrabbit/sandbox/jackrabbit-microkernel/src/main/java/org/apache/jackrabbit/query/index/Index.java Thu Feb 23 15:34:27 2012 @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.jackrabbit.query.index; + +public class Index { + + /** + * The path of what the index contains, or null if not set. + */ + private String pathPrefix; + + /** + * The value prefix, or null if not set. + */ + private String valuePrefix; + + /** + * The property name, or null if not set. + */ + private String propertyName; + + /** + * The node type, or null if not set. + */ + // TODO: use a hash set? + private String nodeType; + + public String getPathPrefix() { + return pathPrefix; + } + + public void setPathPrefix(String pathPrefix) { + this.pathPrefix = pathPrefix; + } + + public String getValuePrefix() { + return valuePrefix; + } + + public void setValuePrefix(String valuePrefix) { + this.valuePrefix = valuePrefix; + } + + public String getPropertyName() { + return propertyName; + } + + public void setPropertyName(String propertyName) { + this.propertyName = propertyName; + } + + public String getNodeType() { + return nodeType; + } + + public void setNodeType(String nodeType) { + this.nodeType = nodeType; + } + + public String toString() { + StringBuilder buff = new StringBuilder("Index on "); + if (pathPrefix != null) { + buff.append("pathPrefix=").append(pathPrefix); + } + if (valuePrefix != null) { + buff.append("valuePrefix=").append(valuePrefix); + } + if (propertyName != null) { + buff.append("propertyName=").append(propertyName); + } + if (nodeType != null) { + buff.append("nodeType=").append(nodeType); + } + return buff.toString(); + } + +}