blob: 74517133c6d63802a8e7ab78be61f082aaf67117 [file] [log] [blame]
/**
* 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.hadoop.yarn.server.timelineservice.reader;
import java.util.Deque;
import java.util.LinkedList;
import org.apache.hadoop.classification.InterfaceAudience.Private;
import org.apache.hadoop.classification.InterfaceStability.Unstable;
import org.apache.hadoop.yarn.server.timelineservice.reader.filter.TimelineCompareOp;
import org.apache.hadoop.yarn.server.timelineservice.reader.filter.TimelineFilter;
import org.apache.hadoop.yarn.server.timelineservice.reader.filter.TimelineFilterList;
import org.apache.hadoop.yarn.server.timelineservice.reader.filter.TimelineFilterList.Operator;
/**
* Abstract class for parsing equality expressions. This means the values in
* expression would either be equal or not equal.
* Equality expressions are of the form :
* (<value>,<value>,<value>) <op> !(<value>,
* <value>)
*
* Here, "!" means all the values should not exist/should not be equal.
* If not specified, they should exist/be equal.
*
* op is a logical operator and can be either AND or OR.
*
* The way values will be interpreted would also depend on implementation.
*
* For instance for event filters this expression may look like,
* (event1,event2) AND !(event3,event4)
* This means for an entity to match, event1 and event2 should exist. But event3
* and event4 should not exist.
*/
@Private
@Unstable
abstract class TimelineParserForEqualityExpr implements TimelineParser {
private enum ParseState {
PARSING_VALUE,
PARSING_OP,
PARSING_COMPAREOP
}
private final String expr;
// Expression in lower case.
private final String exprInLowerCase;
// Expression name.
private final String exprName;
// Expression offset.
private int offset = 0;
// Offset used to parse values in the expression.
private int startOffset = 0;
private final int exprLength;
private ParseState currentParseState = ParseState.PARSING_COMPAREOP;
private TimelineCompareOp currentCompareOp = null;
// Used to store filter lists which can then be combined as brackets are
// closed.
private Deque<TimelineFilterList> filterListStack = new LinkedList<>();
private TimelineFilter currentFilter = null;
private TimelineFilterList filterList = null;
// Delimiter used to separate values.
private final char delimiter;
public TimelineParserForEqualityExpr(String expression, String name,
char delim) {
if (expression != null) {
expr = expression.trim();
exprLength = expr.length();
exprInLowerCase = expr.toLowerCase();
} else {
exprLength = 0;
expr = null;
exprInLowerCase = null;
}
exprName = name;
delimiter = delim;
}
protected TimelineFilter getCurrentFilter() {
return currentFilter;
}
protected TimelineFilter getFilterList() {
return filterList;
}
/**
* Creates filter as per implementation.
*
* @return a {@link TimelineFilter} implementation.
*/
protected abstract TimelineFilter createFilter();
/**
* Sets compare op to the current filter as per filter implementation.
*
* @param compareOp compare op to be set.
* @throws Exception if any problem occurs.
*/
protected abstract void setCompareOpToCurrentFilter(
TimelineCompareOp compareOp) throws TimelineParseException;
/**
* Sets value to the current filter as per filter implementation.
*
* @param value value to be set.
* @throws Exception if any problem occurs.
*/
protected abstract void setValueToCurrentFilter(String value)
throws TimelineParseException;
private void createAndSetFilter(boolean checkIfNull)
throws TimelineParseException {
if (!checkIfNull || currentFilter == null) {
currentFilter = createFilter();
setCompareOpToCurrentFilter(currentCompareOp);
}
setValueToCurrentFilter(expr.substring(startOffset, offset).trim());
}
private void handleSpaceChar() throws TimelineParseException {
if (currentParseState == ParseState.PARSING_VALUE) {
if (startOffset == offset) {
startOffset++;
} else {
createAndSetFilter(true);
currentParseState = ParseState.PARSING_OP;
}
}
offset++;
}
private void handleDelimiter() throws TimelineParseException {
if (currentParseState == ParseState.PARSING_OP ||
currentParseState == ParseState.PARSING_VALUE) {
if (currentParseState == ParseState.PARSING_VALUE) {
createAndSetFilter(false);
}
if (filterList == null) {
filterList = new TimelineFilterList();
}
// Add parsed filter into filterlist and make it null to move on to next
// filter.
filterList.addFilter(currentFilter);
currentFilter = null;
offset++;
startOffset = offset;
currentParseState = ParseState.PARSING_VALUE;
} else {
throw new TimelineParseException("Invalid " + exprName + "expression.");
}
}
private void handleOpeningBracketChar(boolean encounteredNot)
throws TimelineParseException {
if (currentParseState == ParseState.PARSING_COMPAREOP ||
currentParseState == ParseState.PARSING_VALUE) {
offset++;
startOffset = offset;
filterListStack.push(filterList);
filterList = null;
if (currentFilter == null) {
currentFilter = createFilter();
}
currentCompareOp = encounteredNot ?
TimelineCompareOp.NOT_EQUAL : TimelineCompareOp.EQUAL;
setCompareOpToCurrentFilter(currentCompareOp);
currentParseState = ParseState.PARSING_VALUE;
} else {
throw new TimelineParseException("Encountered unexpected opening " +
"bracket while parsing " + exprName + ".");
}
}
private void handleNotChar() throws TimelineParseException {
if (currentParseState == ParseState.PARSING_COMPAREOP ||
currentParseState == ParseState.PARSING_VALUE) {
offset++;
while (offset < exprLength &&
expr.charAt(offset) == TimelineParseConstants.SPACE_CHAR) {
offset++;
}
if (offset == exprLength) {
throw new TimelineParseException("Invalid " + exprName + "expression");
}
if (expr.charAt(offset) == TimelineParseConstants.OPENING_BRACKET_CHAR) {
handleOpeningBracketChar(true);
} else {
throw new TimelineParseException("Invalid " + exprName + "expression");
}
} else {
throw new TimelineParseException("Encountered unexpected not(!) char " +
"while parsing " + exprName + ".");
}
}
private void handleClosingBracketChar() throws TimelineParseException {
if (currentParseState != ParseState.PARSING_VALUE &&
currentParseState != ParseState.PARSING_OP) {
throw new TimelineParseException("Encountered unexpected closing " +
"bracket while parsing " + exprName + ".");
}
if (!filterListStack.isEmpty()) {
if (currentParseState == ParseState.PARSING_VALUE) {
if (startOffset != offset) {
createAndSetFilter(true);
currentParseState = ParseState.PARSING_OP;
}
}
if (filterList == null) {
filterList = new TimelineFilterList();
}
if (currentFilter != null) {
filterList.addFilter(currentFilter);
}
// As bracket is closing, pop the filter list from top of the stack and
// combine it with current filter list.
TimelineFilterList fList = filterListStack.pop();
if (fList != null) {
fList.addFilter(filterList);
filterList = fList;
}
currentFilter = null;
offset++;
startOffset = offset;
} else {
throw new TimelineParseException("Encountered unexpected closing " +
"bracket while parsing " + exprName + ".");
}
}
private void parseOp(boolean closingBracket) throws TimelineParseException {
Operator operator = null;
if (exprInLowerCase.startsWith("or ", offset)) {
operator = Operator.OR;
offset = offset + 3;
} else if (exprInLowerCase.startsWith("and ", offset)) {
operator = Operator.AND;
offset = offset + 4;
}
if (operator == null) {
throw new TimelineParseException("Operator cannot be parsed for " +
exprName + ".");
}
if (filterList == null) {
filterList = new TimelineFilterList(operator);
}
if (currentFilter != null) {
filterList.addFilter(currentFilter);
}
if (closingBracket || filterList.getOperator() != operator) {
filterList = new TimelineFilterList(operator, filterList);
}
currentFilter = null;
startOffset = offset;
currentParseState = ParseState.PARSING_COMPAREOP;
}
private void parseCompareOp() throws TimelineParseException {
if (currentFilter == null) {
currentFilter = createFilter();
}
currentCompareOp = TimelineCompareOp.EQUAL;
setCompareOpToCurrentFilter(currentCompareOp);
currentParseState = ParseState.PARSING_VALUE;
}
@Override
public TimelineFilterList parse() throws TimelineParseException {
if (expr == null || exprLength == 0) {
return null;
}
boolean closingBracket = false;
while (offset < exprLength) {
char offsetChar = expr.charAt(offset);
switch(offsetChar) {
case TimelineParseConstants.NOT_CHAR:
handleNotChar();
break;
case TimelineParseConstants.SPACE_CHAR:
handleSpaceChar();
break;
case TimelineParseConstants.OPENING_BRACKET_CHAR:
handleOpeningBracketChar(false);
break;
case TimelineParseConstants.CLOSING_BRACKET_CHAR:
handleClosingBracketChar();
closingBracket = true;
break;
default: // other characters.
if (offsetChar == delimiter) {
handleDelimiter();
} else if (currentParseState == ParseState.PARSING_COMPAREOP) {
parseCompareOp();
} else if (currentParseState == ParseState.PARSING_OP) {
parseOp(closingBracket);
closingBracket = false;
} else {
offset++;
}
break;
}
}
if (!filterListStack.isEmpty()) {
filterListStack.clear();
throw new TimelineParseException("Encountered improper brackets while " +
"parsing " + exprName + ".");
}
if (currentParseState == ParseState.PARSING_VALUE) {
if (startOffset != offset) {
createAndSetFilter(true);
}
}
if (filterList == null || filterList.getFilterList().isEmpty()) {
filterList = new TimelineFilterList(currentFilter);
} else if (currentFilter != null) {
filterList.addFilter(currentFilter);
}
return filterList;
}
@Override
public void close() {
if (filterListStack != null) {
filterListStack.clear();
}
currentFilter = null;
filterList = null;
}
}