// 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.util.NoSuchElementException; /** * Parses a Graphite style expression. * Please use {@link #isEOF()} before any method call. Otherwise the methods * will throw a NoSuchElementException. * @since 2.3 */ public class ExpressionReader { /** The character array to parse */ protected final char[] chars; /** The current index in the character array */ private int mark = 0; /** * Default ctor * @param chars The characters to parse */ public ExpressionReader(final char[] chars) { if (chars == null) { throw new IllegalArgumentException("Character set cannot be null"); } this.chars = chars; } /** @return the current index */ public int getMark() { return mark; } /** @return the current character without advancing the index */ public char peek() { if (isEOF()) { throw new NoSuchElementException("Index " + mark + " is out of bounds " + chars.length); } return chars[mark]; } /** @return the current character and advances the index */ public char next() { if (isEOF()) { throw new NoSuchElementException("Index " + mark + " is out of bounds " + chars.length); } return chars[mark++]; } /** @param the number of characters to skip */ public void skip(final int num) { if (num < 0) { throw new UnsupportedOperationException("Skipping backwards is not allowed"); } mark += num; } /** * Checks to see if the next character matches the parameter * @param c The character to check for * @return True if they match, false if not */ public boolean isNextChar(final char c) { return peek() == c; } /** @return true if the given sequence appears next in the array. */ public boolean isNextSeq(final CharSequence seq) { if (seq == null) { throw new IllegalArgumentException("Comparative sequence cannot be null"); } for (int i = 0; i < seq.length(); i++) { if (mark + i >= chars.length) { return false; } if (chars[mark + i] != seq.charAt(i)) { return false; } } return true; } /** @return the name of the function */ public String readFuncName() { // in case we get something like " function(foo)" consume a bit skipWhitespaces(); StringBuilder builder = new StringBuilder(); while (peek() != '(' && !Character.isWhitespace(peek())) { builder.append(next()); } skipWhitespaces(); // increment over whitespace after return builder.toString(); } /** @return Whether or not the index is at the end of the character array */ public boolean isEOF() { return mark >= chars.length; } /** Increments the mark over white spaces */ public void skipWhitespaces() { for (int i = mark; i < chars.length; i++) { if (Character.isWhitespace(chars[i])) { mark++; } else { break; } } } /** @return the next parameter from the expression * TODO - may need some work */ public String readNextParameter() { final StringBuilder builder = new StringBuilder(); int num_nested = 0; while (!isEOF() && !Character.isWhitespace(peek())) { final char ch = peek(); if (ch == '(') { num_nested++; } else if (ch == ')') { num_nested--; } if (num_nested < 0) { break; } if (num_nested <= 0 && isNextSeq(",,")) { break; } builder.append(next()); } return builder.toString(); } @Override public String toString() { // make a copy return new String(chars); } }