Mercurial > hg > release > thermostat-1.2
view storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/BasicDescriptorParser.java @ 1660:c6ae78b6f3ac
[Thermostat 1.2] Update copyright year to 2015
Reviewed-by: omajid
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-March/013127.html
PR2273
author | Severin Gehwolf <sgehwolf@redhat.com> |
---|---|
date | Wed, 11 Mar 2015 15:07:27 +0100 |
parents | 73fdadf117c5 |
children |
line wrap: on
line source
/* * Copyright 2012-2015 Red Hat, Inc. * * This file is part of Thermostat. * * Thermostat is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * Thermostat is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Thermostat; see the file COPYING. If not see * <http://www.gnu.org/licenses/>. * * Linking this code with other modules is making a combined work * based on this code. Thus, the terms and conditions of the GNU * General Public License cover the whole combination. * * As a special exception, the copyright holders of this code give * you permission to link this code with independent modules to * produce an executable, regardless of the license terms of these * independent modules, and to copy and distribute the resulting * executable under terms of your choice, provided that you also * meet, for each linked independent module, the terms and conditions * of the license of that module. An independent module is a module * which is not derived from or based on this code. If you modify * this code, you may extend this exception to your version of the * library, but you are not obligated to do so. If you do not wish * to do so, delete this exception statement from your version. */ package com.redhat.thermostat.storage.internal.statement; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.redhat.thermostat.common.utils.LoggingUtils; import com.redhat.thermostat.storage.core.Add; import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction; import com.redhat.thermostat.storage.core.BackingStorage; import com.redhat.thermostat.storage.core.Category; import com.redhat.thermostat.storage.core.DescriptorParsingException; import com.redhat.thermostat.storage.core.Key; import com.redhat.thermostat.storage.core.ParsedStatement; import com.redhat.thermostat.storage.core.Query; import com.redhat.thermostat.storage.core.Query.SortDirection; import com.redhat.thermostat.storage.core.Remove; import com.redhat.thermostat.storage.core.Replace; import com.redhat.thermostat.storage.core.StatementDescriptor; import com.redhat.thermostat.storage.core.Update; import com.redhat.thermostat.storage.core.experimental.AggregateQuery2; import com.redhat.thermostat.storage.model.Pojo; import com.redhat.thermostat.storage.query.BinaryComparisonOperator; import com.redhat.thermostat.storage.query.BinaryLogicalOperator; /** * A parser for the string representation of {@link StatementDescriptor}s. * Tokens have to be separated by whitespace. * * This parser implements the following simple grammar for statement descriptors. * It supports the following statement types: * <ul> * <li>QUERY (read)</li> * <li>QUERY-COUNT (read)</li> * <li>ADD (write)</li> * <li>UPDATE (write)</li> * <li>REPLACE (write)</li> * <li>REMOVE (write)</li> * </ul> * * <p><strong>Grammar:</strong></p> * <pre> * statementDesc := statementType category setList suffix * statementType := 'QUERY' | 'QUERY-COUNT' | * 'ADD' | 'REPLACE' | 'UPDATE' | * 'REMOVE' * category := string * setList := 'SET' setValues | \empty * setValues := valuePair valueList * valuePair := term '=' term * valueList := ',' setValues | \empty * suffix := 'WHERE' where | * 'SORT' sortCond | * 'LIMIT' term | \empty * where := whereExp sort limit * whereExp := andCond orCond * orCond := 'OR' whereExp | \empty * sort := 'SORT' sortCond | \empty * sortCond := sortPair sortList * sortPair := term sortModifier * sortModifier := 'ASC' | 'DSC' * sortList := ',' sortCond | \empty * limit := 'LIMIT' term | \empty * andCond := condition andBody * andBody := 'AND' whereExp | \empty * condition := 'NOT' condition | compExp * compExp := term compExpRHS * term := freeParam | literal * freeParam := '?s' | '?i' | '?l' | '?s[' | '?b' * literal := sQuote string sQuote | int | long | boolean * sQuote := \' * boolean := <true> | <false> * int := <literal-int> * long := <literal-long>longPostFix * longPostFix := 'l' | 'L' * string := <literal-string-value> * compExpRHS := '!=' term | '=' term | '<=' term | '>=' term | * '<' term | '>' term * </pre> * * This implements the following logic precedence rules (in this order of * precedence): * * <ol> * <li>NOT</li> * <li>AND</li> * <li>OR</li> * </ol> * * NOTE: Comparison expressions have equal precedence. */ class BasicDescriptorParser<T extends Pojo> implements StatementDescriptorParser<T> { private static final Logger logger = LoggingUtils.getLogger(BasicDescriptorParser.class); private static final String TOKEN_DELIMS = " \t\r\n\f"; private static final short IDX_QUERY = 0; private static final short IDX_ADD = 1; private static final short IDX_REPLACE = 2; private static final short IDX_UPDATE = 3; private static final short IDX_REMOVE = 4; private static final String[] KNOWN_STATEMENT_TYPES = new String[] { "QUERY", "ADD", "REPLACE", "UPDATE", "REMOVE" }; // package-private for testing static final String AGGREGATE_PARAM_REGEXP = "(?:\\(([a-zA-Z_]+)\\))?$"; private static final String QUERY_COUNT_REGEXP = "QUERY-COUNT" + AGGREGATE_PARAM_REGEXP; private static final String QUERY_DISTINCT_REGEXP = "QUERY-DISTINCT" + AGGREGATE_PARAM_REGEXP; private static final Pattern QUERY_COUNT_PATTERN = Pattern.compile(QUERY_COUNT_REGEXP); private static final Pattern QUERY_DISTINCT_PATTERN = Pattern.compile(QUERY_DISTINCT_REGEXP); private static final String SORTLIST_SEP = ","; private static final String SETLIST_SEP = SORTLIST_SEP; private static final String KEYWORD_SET = "SET"; private static final String KEYWORD_WHERE = "WHERE"; private static final String KEYWORD_SORT = "SORT"; private static final String KEYWORD_LIMIT = "LIMIT"; private static final String KEYWORD_ASC = "ASC"; private static final String KEYWORD_DSC = "DSC"; private static final String POJO_FREE_PARAMETER_TYPE = "?p"; private static final char PARAM_PLACEHOLDER = '?'; private final String[] tokens; protected final StatementDescriptor<T> desc; private final BackingStorage storage; private int currTokenIndex; private int placeHolderCount; // the parsed statement private ParsedStatementImpl<T> parsedStatement; protected SuffixExpression tree; protected SetList setList; BasicDescriptorParser(BackingStorage storage, StatementDescriptor<T> desc) { this.tokens = getTokens(desc.getDescriptor()); this.currTokenIndex = 0; this.placeHolderCount = 0; this.desc = desc; this.storage = storage; } private String[] getTokens(String str) { StringTokenizer tokenizer = new StringTokenizer(str, TOKEN_DELIMS); List<String> toks = new ArrayList<>(tokenizer.countTokens()); while (tokenizer.hasMoreTokens()) { toks.add(tokenizer.nextToken()); } return toks.toArray(new String[0]); } public ParsedStatement<T> parse() throws DescriptorParsingException { matchStatementType(); matchCategory(); // matched so far, create the raw statement createStatement(); this.setList = new SetList(); matchSetList(setList); this.tree = new SuffixExpression(); matchSuffix(); if (currTokenIndex != tokens.length) { throw new DescriptorParsingException("Incomplete parse of '" + desc.toString() + "'"); } parsedStatement.setNumFreeParams(placeHolderCount); parsedStatement.setSetList(setList); parsedStatement.setSuffixExpression(tree); return parsedStatement; } /* * Match set list for DML statements. */ private void matchSetList(final SetList setList) throws DescriptorParsingException { if (tokens.length == currTokenIndex) { // no set list return; } if (tokens[currTokenIndex].equals(KEYWORD_SET)) { currTokenIndex++; // SET matchSetValues(setList); } // empty, proceed with suffix } /* * Match list of values in a SET expression */ private void matchSetValues(SetList setList) throws DescriptorParsingException { matchValuePair(setList); matchValueList(setList); } /* * Match more value pairs in a SET list */ private void matchValueList(SetList setList) throws DescriptorParsingException { if (currTokenIndex == tokens.length) { // empty return; } if (tokens[currTokenIndex].equals(SETLIST_SEP)) { currTokenIndex++; // , matchSetValues(setList); } } /* * Match one pair of values in a */ private void matchValuePair(SetList setList) throws DescriptorParsingException { SetListValue value = new SetListValue(); TerminalNode lval = new TerminalNode(null); matchTerm(lval, true, true); value.setKey(lval); if (tokens[currTokenIndex].equals("=")) { currTokenIndex++; // = } else { String msg = "Expected '=' after SET value LHS. Token was ->" + tokens[currTokenIndex] + "<-"; throw new DescriptorParsingException(msg); } TerminalNode rval = new TerminalNode(null); matchTerm(rval, false, true); value.setValue(rval); setList.addValue(value); } /* * Match optional suffixes. */ private void matchSuffix() throws DescriptorParsingException { if (tokens.length == currTokenIndex) { // no suffix return; } if (tokens[currTokenIndex].equals(KEYWORD_WHERE)) { currTokenIndex++; WhereExpression expn = new WhereExpression(); tree.setWhereExpn(expn); matchWhereExp(expn.getRoot()); matchSort(tree); matchLimit(tree); } else if (tokens[currTokenIndex].equals(KEYWORD_SORT)) { // SORT token eaten up by matchSort() matchSort(tree); matchLimit(tree); } else if (tokens[currTokenIndex].equals(KEYWORD_LIMIT)) { // LIMIT token eaten up by matchLimit() matchLimit(tree); } else { throw new DescriptorParsingException("Unexpected token: '" + tokens[currTokenIndex] + "'. Expected one of " + KEYWORD_WHERE + ", " + KEYWORD_SORT + ", " + KEYWORD_LIMIT); } } private void matchLimit(SuffixExpression tree) throws DescriptorParsingException { if (currTokenIndex == tokens.length) { // empty return; } else if (currTokenIndex < tokens.length) { if (tokens[currTokenIndex].equals(KEYWORD_LIMIT)) { LimitExpression node = new LimitExpression(); tree.setLimitExpn(node); currTokenIndex++; matchTerm(node); } } else { throw new DescriptorParsingException("Illegal statement descriptor: Reason LIMIT"); } } private void matchSort(SuffixExpression tree) throws DescriptorParsingException { if (currTokenIndex < tokens.length && tokens[currTokenIndex].equals(KEYWORD_SORT)) { SortExpression sortExpn = new SortExpression(); tree.setSortExpn(sortExpn); currTokenIndex++; matchSortList(sortExpn); } if (currTokenIndex > tokens.length) { throw new DescriptorParsingException("Illegal statement descriptor."); } // empty } private void matchSortList(SortExpression sortExpn) throws DescriptorParsingException { matchSortPair(sortExpn); matchSortListPreamble(sortExpn); } private void matchSortListPreamble(SortExpression sortExpn) throws DescriptorParsingException { if (currTokenIndex < tokens.length && tokens[currTokenIndex].equals(SORTLIST_SEP)) { currTokenIndex++; // ',' token matchSortList(sortExpn); } } private void matchSortPair(SortExpression expn) throws DescriptorParsingException { SortMember member = new SortMember(); matchTerm(member); matchSortModifier(member); // Add the member node to the list of the sort node expn.addMember(member); } private void matchSortModifier(SortMember member) throws DescriptorParsingException { String msg = "Illegal statement decriptor: Reason SORT. Expected ASC or DSC"; if (currTokenIndex >= tokens.length) { throw new DescriptorParsingException(msg); } if (tokens[currTokenIndex].equals(KEYWORD_ASC)) { member.setDirection(SortDirection.ASCENDING); currTokenIndex++; } else if (tokens[currTokenIndex].equals(KEYWORD_DSC)) { member.setDirection(SortDirection.DESCENDING); currTokenIndex++; } else { throw new DescriptorParsingException(msg); } } private void matchWhereExp(Node node) throws DescriptorParsingException { if (currTokenIndex >= tokens.length) { throw new DescriptorParsingException("Illegal where clause"); } assert(node != null); matchAndCondition(node); matchOrCondition(node); } private void matchAndCondition(Node currNode) throws DescriptorParsingException { matchCondition(currNode); matchAndExpression(currNode); } private void matchCondition(Node currNode) throws DescriptorParsingException { if (currTokenIndex >= tokens.length) { throw new DescriptorParsingException("Illegal statement descriptor: Reason sort clause"); } if (tokens[currTokenIndex].equals(Operator.NOT.getName())) { NotBooleanExpressionNode notNode = new NotBooleanExpressionNode(currNode); if (currNode instanceof BinaryExpressionNode) { BinaryExpressionNode currNodeExpr = (BinaryExpressionNode)currNode; Node available = currNodeExpr.getLeftChild(); if (available != null) { currNodeExpr.setRightChild(notNode); } else { currNodeExpr.setLeftChild(notNode); } } else { assert(currNode instanceof NotBooleanExpressionNode || currNode instanceof Node); currNode.setValue(notNode); } currTokenIndex++; // NOT keyword matchCondition(notNode); } else { matchComparisionExpression(currNode); } } private void matchComparisionExpression(Node currNode) throws DescriptorParsingException { if (currTokenIndex >= tokens.length) { throw new DescriptorParsingException("Illegal statement descriptor: Comparison expression"); } BinaryExpressionNode expr = new BinaryExpressionNode(currNode); TerminalNode left = new TerminalNode(expr); TerminalNode right = new TerminalNode(expr); expr.setLeftChild(left); expr.setRightChild(right); if (currNode instanceof BinaryExpressionNode) { BinaryExpressionNode currNodeExpr = (BinaryExpressionNode)currNode; Node available = currNodeExpr.getLeftChild(); if (available == null) { currNodeExpr.setLeftChild(expr); } else { assert(currNodeExpr.getRightChild() == null); currNodeExpr.setRightChild(expr); } } else { assert(currNode instanceof NotBooleanExpressionNode || currNode instanceof Node); currNode.setValue(expr); } matchTerm(left, true); matchComparisonRHS(expr); } private void matchComparisonRHS(BinaryExpressionNode currNode) throws DescriptorParsingException { if (currTokenIndex >= tokens.length) { // boolean literals are not allowed throw new DescriptorParsingException("Illegal statement descriptor: Boolean literals are not allowed!"); } if (tokens[currTokenIndex].equals(Operator.EQUALS.getName())) { currTokenIndex++; currNode.setOperator(BinaryComparisonOperator.EQUALS); matchTerm((TerminalNode)currNode.getRightChild(), false); } else if (tokens[currTokenIndex].equals(Operator.LESS_THAN_OR_EQUAL_TO.getName())) { currTokenIndex++; currNode.setOperator(BinaryComparisonOperator.LESS_THAN_OR_EQUAL_TO); matchTerm((TerminalNode)currNode.getRightChild(), false); } else if (tokens[currTokenIndex].equals(Operator.GREATER_THAN_OR_EQUAL_TO.getName())) { currTokenIndex++; currNode.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO); matchTerm((TerminalNode)currNode.getRightChild(), false); } else if (tokens[currTokenIndex].equals(Operator.GREATER_THAN.getName())) { currTokenIndex++; currNode.setOperator(BinaryComparisonOperator.GREATER_THAN); matchTerm((TerminalNode)currNode.getRightChild(), false); } else if (tokens[currTokenIndex].equals(Operator.LESS_THAN.getName())) { currTokenIndex++; currNode.setOperator(BinaryComparisonOperator.LESS_THAN); matchTerm((TerminalNode)currNode.getRightChild(), false); } else if (tokens[currTokenIndex].equals(Operator.NOT_EQUAL_TO.getName())) { currTokenIndex++; currNode.setOperator(BinaryComparisonOperator.NOT_EQUAL_TO); matchTerm((TerminalNode)currNode.getRightChild(), false); } else { throw new DescriptorParsingException("Illegal statement descriptor: Reason comparison expression!"); } } private void matchTerm(SortMember member) throws DescriptorParsingException { String term = getTerm(); if (term.charAt(0) == PARAM_PLACEHOLDER) { assert(placeHolderCount > 0); ensureValidType(term, "SORT"); if (term.charAt(1) != 's') { String msg = "Sort parameters only accept string types. Placeholder was: " + term; throw new DescriptorParsingException(msg); } UnfinishedSortKey unfinishedKey = new UnfinishedSortKey(); unfinishedKey.setParameterIndex(placeHolderCount - 1); member.setSortKey(unfinishedKey); return; } String stringTerm = getStringTerm(term); member.setSortKey(stringTerm); } /* * Check if the free parameter type is valid in a given context. Currently, * list and pojo free type parameters are invalid for LIMIT, SORT and WHERE. * * Currently, no list types and no Pojo types are allowed. */ private void ensureValidType(String term, String contextName) throws DescriptorParsingException { if (term.length() > 2) { // Don't allow list types for invalid contexts // Only list type free variables have 3 characters String format = "List free variable type not allowed in %s context"; String msg = String.format(format, contextName); throw new DescriptorParsingException(msg); } if (term.equals(POJO_FREE_PARAMETER_TYPE)) { String format = "Pojo free variable type not allowed in %s context"; String msg = String.format(format, contextName); throw new DescriptorParsingException(msg); } } private void matchTerm(LimitExpression expn) throws DescriptorParsingException { String term = getTerm(); if (term.charAt(0) == PARAM_PLACEHOLDER) { assert(placeHolderCount > 0); ensureValidType(term, "LIMIT"); if (term.charAt(1) != 'i') { String msg = "Limit parameters only accept integer types. Placeholder was: " + term; throw new DescriptorParsingException(msg); } UnfinishedLimitValue limitValue = new UnfinishedLimitValue(); limitValue.setParameterIndex(placeHolderCount - 1); expn.setValue(limitValue); return; } int limitVal; try { limitVal = Integer.parseInt(term); } catch (NumberFormatException e) { throw new DescriptorParsingException("Invalid limit expression. '" + term + "' not an integer"); } expn.setValue(limitVal); } /** * Calls {@link #matchTerm(TerminalNode, boolean, boolean)} with a * {@code false isSetListContext} parameter. */ private void matchTerm(TerminalNode node, boolean isLHS) throws DescriptorParsingException { // default to false matchTerm(node, isLHS, false); } /** * Match a terminal in a TerminalNode context. Only where expression, and * set list context take this code path. * * @param node * The terminal node which is parsed at this point. * @param isLHS * {@code true} if and only if the node is the left hand side of * a binary expression. * @param isSetListContext * {@code true} if and only if the node is in a set list context * or conversely NOT in a where expression context. * @throws DescriptorParsingException * If and error was encountered parsing the terminal string * token. */ private void matchTerm(TerminalNode node, boolean isLHS, boolean isSetListContext) throws DescriptorParsingException { String term = getTerm(); if (term.charAt(0) == PARAM_PLACEHOLDER) { assert(placeHolderCount > 0); if (!isSetListContext) { ensureValidType(term, "WHERE"); } UnfinishedValueNode patchNode = new UnfinishedValueNode(); patchNode.setParameterIndex(placeHolderCount - 1); patchNode.setLHS(isLHS); // figure out the expected type Class<?> expectedType = getType(term.substring(1)); if (expectedType == null) { throw new DescriptorParsingException("Unknown type of free parameter: '" + term + "'"); } patchNode.setType(expectedType); node.setValue(patchNode); return; } // regular terminal. i.e. literal value if (isLHS) { // FIXME: In thermostat LHS of comparisons must be Key objects. I'm // not sure if this restriction is very meaningful in a prepared // statement context as the purpose of this was to ensure "type" // compatibility between Key <=> value comparisons. String stringTerm = getStringTerm(term); Key<?> key = new Key<>(stringTerm); node.setValue(key); } else { Object typedValue = getTypedValue(term); node.setValue(typedValue); } } private Object getTypedValue(String term) throws DescriptorParsingException { try { String stringTerm = getStringTerm(term); return stringTerm; } catch (DescriptorParsingException e) { // Must be integer (long/int) or boolean. First check for boolean, // then for long and regular ints. if (term.equals(Boolean.toString(false)) || term.equals(Boolean.toString(true))) { boolean boolVal = Boolean.parseBoolean(term); return boolVal; } // Next, parse long or int. try { int lastCharInTokenIndex = term.length() - 1; // preceding l/L indicate long integer types. if (term.charAt(lastCharInTokenIndex) == 'L' || term.charAt(lastCharInTokenIndex) == 'l') { long longVal = Long.parseLong(term.substring(0, lastCharInTokenIndex)); return longVal; } // must be integer or some invalid type int intVal = Integer.parseInt(term); return intVal; } catch (NumberFormatException nfe) { String msg = "Illegal terminal type. Token was ->" + term + "<-"; throw new DescriptorParsingException(msg); } } } private String getStringTerm(String term) throws DescriptorParsingException { String errorMsg = "Expected string value. Got term ->" + term + "<-" + " Was the string properly quoted? Example: 'string'."; if (term.charAt(0) != '\'' || term.charAt(term.length() - 1) != '\'') { throw new DescriptorParsingException(errorMsg); } return term.substring(1, term.length() - 1); } private Class<?> getType(String term) { if (term.equals("")) { // illegal type return null; } // free variable types can have 1 or 2 characters. assert(term.length() > 0 && term.length() < 3); char switchChar = term.charAt(0); Class<?> typeToken = null; switch (switchChar) { case 'i': { if (term.length() == 1) { typeToken = int.class; } else if (term.length() == 2 && term.charAt(1) == '[') { typeToken = int[].class; } break; } case 'l': { if (term.length() == 1) { typeToken = long.class; } else if (term.length() == 2 && term.charAt(1) == '[') { typeToken = long[].class; } break; } case 's': { if (term.length() == 1) { typeToken = String.class; } else if (term.length() == 2 && term.charAt(1) == '[') { typeToken = String[].class; } break; } case 'b': { if (term.length() == 1) { typeToken = boolean.class; } else if (term.length() == 2 && term.charAt(1) == '[') { typeToken = boolean[].class; } break; } case 'd': { if (term.length() == 1) { typeToken = double.class; } else if (term.length() == 2 && term.charAt(1) == '[') { typeToken = double[].class; } break; } case 'p': { if (term.length() == 1) { typeToken = Pojo.class; } else if (term.length() == 2 && term.charAt(1) == '[') { typeToken = Pojo[].class; } break; } default: assert(typeToken == null); break; } return typeToken; } private String getTerm() throws DescriptorParsingException { if (currTokenIndex >= tokens.length) { throw new DescriptorParsingException("Invalid where clause. Reason: term expected but not given!"); } if (tokens[currTokenIndex].charAt(0) == PARAM_PLACEHOLDER) { placeHolderCount++; } String term = tokens[currTokenIndex]; currTokenIndex++; return term; } private void matchAndExpression(Node currNode) throws DescriptorParsingException { if (currTokenIndex < tokens.length && tokens[currTokenIndex].equals(Operator.AND.getName())) { currTokenIndex++; // AND keyword Node parent = currNode; if (currNode instanceof BinaryExpressionNode || currNode instanceof NotBooleanExpressionNode) { parent = currNode.getParent(); assert(parent != null); } BinaryExpressionNode and = new BinaryExpressionNode(parent); and.setOperator(BinaryLogicalOperator.AND); if (currNode instanceof BinaryExpressionNode || currNode instanceof NotBooleanExpressionNode) { currNode.setParent(and); and.setLeftChild(currNode); parent.setValue(and); } else { // Root node case and.setLeftChild((Node)parent.getValue()); parent.setValue(and); } // Note the current AND expression node at this point of parsing // must be at the root of the entire expression. assert(and.getParent().getParent() == null); matchWhereExp(and); } // empty } private void matchOrCondition(Node currNode) throws DescriptorParsingException { if (currTokenIndex < tokens.length && tokens[currTokenIndex].equals(Operator.OR.getName())) { currTokenIndex++; // OR keyword Node parent = currNode; if (currNode instanceof BinaryExpressionNode || currNode instanceof NotBooleanExpressionNode) { parent = currNode.getParent(); assert(parent != null); } BinaryExpressionNode or = new BinaryExpressionNode(parent); or.setOperator(BinaryLogicalOperator.OR); if (currNode instanceof BinaryExpressionNode || currNode instanceof NotBooleanExpressionNode) { currNode.setParent(or); or.setLeftChild(currNode); parent.setValue(or); } else { // Root node case or.setLeftChild((Node)parent.getValue()); parent.setValue(or); } // Note the current OR expression node at this point of parsing // must be at the root of the entire expression. assert(or.getParent().getParent() == null); matchWhereExp(or); } // empty } private void createStatement() { // matchStatementType and matchCategory advanced currTokenIndex, // lets use idx of 0 here. final String statementType = tokens[0]; Matcher queryCountMatcher = QUERY_COUNT_PATTERN.matcher(statementType); Matcher queryDistinctMatcher = QUERY_DISTINCT_PATTERN.matcher(statementType); if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_QUERY])) { // regular query case Query<T> query = storage.createQuery(desc.getCategory()); this.parsedStatement = new ParsedStatementImpl<>(query); } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_ADD])) { // create add Add<T> add = storage.createAdd(desc.getCategory()); this.parsedStatement = new ParsedStatementImpl<>(add); } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REPLACE])) { // create replace Replace<T> replace = storage.createReplace(desc.getCategory()); this.parsedStatement = new ParsedStatementImpl<>(replace); } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_UPDATE])) { // create replace Update<T> update = storage.createUpdate(desc.getCategory()); this.parsedStatement = new ParsedStatementImpl<>(update); } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REMOVE])) { // create remove Remove<T> remove = storage.createRemove(desc.getCategory()); this.parsedStatement = new ParsedStatementImpl<>(remove); } else if (queryCountMatcher.matches()) { this.parsedStatement = createAggregatePreparedStatement(AggregateFunction.COUNT, queryCountMatcher); } else if (queryDistinctMatcher.matches()) { this.parsedStatement = createAggregatePreparedStatement(AggregateFunction.DISTINCT, queryDistinctMatcher); } else { throw new IllegalStateException("Don't know how to create statement type '" + statementType + "'"); } } private ParsedStatementImpl<T> createAggregatePreparedStatement(final AggregateFunction function, final Matcher matcher) { // create aggregate query Query<T> query = storage.createAggregateQuery(function, desc.getCategory()); if (!(query instanceof AggregateQuery2)) { // FIXME: Thermostat 2.0 BackingStorage.createAggregateQuery() should // return a merged version of AggregateQuery (i.e. AggregateQuery2). // Thus, this check can go away then. // For 1.2 we have this in order to be API backwards compatible. logger.log(Level.WARNING, "Expected AggregateQuery2. This will no longer work for Thermostat 2.0. Stmt was: " + desc); return new ParsedStatementImpl<>(query); } AggregateQuery2<T> aggregateQuery = (AggregateQuery2<T>)query; // We'll always have a match for at least one group. That group // will be the keyName to use (if any). For old query descriptors // the keyName may be null String keyName = matcher.group(1); // groups start at 1 if (keyName != null) { Key<?> aggKey = new Key<>(keyName); aggregateQuery.setAggregateKey(aggKey); } return new ParsedStatementImpl<>(aggregateQuery); } private void matchCategory() throws DescriptorParsingException { if (currTokenIndex >= tokens.length) { throw new DescriptorParsingException("Missing category name in descriptor: '" + desc.getDescriptor() + "'"); } Category<?> category = desc.getCategory(); if (!tokens[currTokenIndex].equals(category.getName())) { throw new DescriptorParsingException( "Category mismatch in descriptor. Category from descriptor string: '" + tokens[currTokenIndex] + "'. Category name from category: '" + category.getName() + "'."); } currTokenIndex++; } private void matchStatementType() throws DescriptorParsingException { final String statementType = tokens[currTokenIndex]; Matcher queryCountMatcher = QUERY_COUNT_PATTERN.matcher(statementType); Matcher queryDistinctMatcher = QUERY_DISTINCT_PATTERN.matcher(statementType); if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_QUERY])) { // QUERY currTokenIndex++; } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_ADD])) { // ADD currTokenIndex++; } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REPLACE])) { // REPLACE currTokenIndex++; } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_UPDATE])) { // UPDATE currTokenIndex++; } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REMOVE])) { // REMOVE currTokenIndex++; } else if (queryCountMatcher.matches()) { // QUERY-COUNT currTokenIndex++; } else if (queryDistinctMatcher.matches()) { // QUERY-DISTINCT currTokenIndex++; } else { throw new DescriptorParsingException("Unknown statement type: '" + statementType + "'"); } } }