// This file is part of OpenTSDB. // Copyright (C) 2015 The OpenTSDB Authors. // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 2.1 of the License, or (at your // option) any later version. This program 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 Lesser // General Public License for more details. You should have received a copy // of the GNU Lesser General Public License along with this program. If not, // see <http://www.gnu.org/licenses/>. package net.opentsdb.query.expression; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import net.opentsdb.core.TSQuery; import net.opentsdb.query.expression.parser.ParseException; import net.opentsdb.query.expression.parser.SyntaxChecker; /** * Static class with helpers to parse and deal with expressions * @since 2.3 */ public class Expressions { /** No instantiation for you! */ private Expressions() { } /** * Parses an expression into a tree * @param expression The expression to parse (as a string) * @param metric_queries A list to store the parsed metrics in * @param data_query The time series query * @return The parsed tree ready for evaluation * @throws IllegalArgumentException if the expression was null, empty or * invalid. * @throws UnsupportedOperationException if the requested function couldn't * be found. */ public static ExpressionTree parse(final String expression, final List<String> metric_queries, final TSQuery data_query) { if (expression == null || expression.isEmpty()) { throw new IllegalArgumentException("Expression may not be null or empty"); } if (expression.indexOf('(') == -1 || expression.indexOf(')') == -1) { throw new IllegalArgumentException("Invalid Expression: " + expression); } final ExpressionReader reader = new ExpressionReader(expression.toCharArray()); // consume any whitespace ahead of the expression reader.skipWhitespaces(); final String function_name = reader.readFuncName(); final Expression root_expression = ExpressionFactory.getByName(function_name); final ExpressionTree root = new ExpressionTree(root_expression, data_query); reader.skipWhitespaces(); if (reader.peek() == '(') { reader.next(); parse(reader, metric_queries, root, data_query); } return root; } /** * Parses a list of string expressions into the proper trees, adding the * metrics to the {@link metric_queries} list. * @param expressions A list of zero or more expressions (if empty, you get an * empty tree list back) * @param ts_query The original query with timestamps * @param metric_queries The list to fill with metrics to fetch */ public static List<ExpressionTree> parseExpressions( final List<String> expressions, final TSQuery ts_query, final List<String> metric_queries) { final List<ExpressionTree> trees = new ArrayList<ExpressionTree>(expressions.size()); for (final String expr: expressions) { final SyntaxChecker checker = new SyntaxChecker(new StringReader(expr)); checker.setMetricQueries(metric_queries); checker.setTSQuery(ts_query); try { trees.add(checker.EXPRESSION()); } catch (ParseException e) { throw new IllegalArgumentException("Failed to parse " + expr, e); } } return trees; } /** * Helper to parse out the function(s) and parameters * @param reader The reader used for iterating over the expression * @param metric_queries A list to store the parsed metrics in * @param root The root tree * @param data_query The time series query */ private static void parse(final ExpressionReader reader, final List<String> metric_queries, final ExpressionTree root, final TSQuery data_query) { int parameter_index = 0; reader.skipWhitespaces(); if (reader.peek() != ')') { final String param = reader.readNextParameter(); parseParam(param, metric_queries, root, data_query, parameter_index++); } while (!reader.isEOF()) { reader.skipWhitespaces(); if (reader.peek() == ')') { return; } else if (reader.isNextSeq(",,")) { reader.skip(2); //swallow the ",," delimiter reader.skipWhitespaces(); final String param = reader.readNextParameter(); parseParam(param, metric_queries, root, data_query, parameter_index++); } else { throw new IllegalArgumentException("Invalid delimiter in parameter " + "list at pos=" + reader.getMark() + ", expr=" + reader.toString()); } } } /** * Helper that parses out the parameter from the expression * @param param The parameter to parse * @param metric_queries A list to store the parsed metrics in * @param root The root tree * @param data_query The time series query * @param index Index of the parameter */ private static void parseParam(final String param, final List<String> metric_queries, final ExpressionTree root, final TSQuery data_query, final int index) { if (param == null || param.length() == 0) { throw new IllegalArgumentException("Parameter cannot be null or empty"); } if (param.indexOf('(') > 0 && param.indexOf(')') > 0) { // sub expression final ExpressionTree sub_tree = parse(param, metric_queries, data_query); root.addSubExpression(sub_tree, index); } else if (param.indexOf(':') >= 0) { // metric query metric_queries.add(param); root.addSubMetricQuery(param, metric_queries.size() - 1, index); } else { // expression parameter root.addFunctionParameter(param); } } }