/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2010-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) 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 3 of the License, * or (at your option) any later version. * * OpenNMS(R) 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 OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.core.soa.filter; import java.util.LinkedList; import java.util.List; import org.opennms.core.soa.Filter; /** * FilterUtils * * @author brozow */ public class FilterParser { private class Lexer { private String m_input; private int m_ptr; private String m_peekedToken; Lexer(String input) { m_input = input; m_ptr = 0; m_peekedToken = null; } Character nextChar() { if (m_ptr >= m_input.length()) { return null; } return m_input.charAt(m_ptr++); } Character peekChar() { if (m_ptr >= m_input.length()) { return null; } return m_input.charAt(m_ptr); } /* * TOKENS: * '(' * ')' * '&' * '|' * '!' * '=' * '*' * '>=' * '<=' * text == '[^()&|!=<>*]|\[()&|!=<>*\]' * */ boolean isTokenStart(Character ch) { if (ch == null) { return true; } switch(ch) { case '(': case ')': case '&': case '|': case '!': case '=': case '*': case '>': case '<': return true; default: return false; } } String readText() { StringBuilder bldr = new StringBuilder(); Character ch = peekChar(); while(!isTokenStart(ch)) { if (ch == '\\') { // skip backslash nextChar(); // read next char and append it ch = nextChar(); if (ch == null) { parseError("End of input reached after '\\'"); } } bldr.append(nextChar()); ch = peekChar(); } return bldr.toString(); } String peekToken() { if (m_peekedToken == null) { m_peekedToken = nextToken(); } return m_peekedToken; } String nextToken() { // return a peeked token first if (m_peekedToken != null) { String token = m_peekedToken; m_peekedToken = null; return token; } Character ch = nextChar(); if (ch == null) { return null; } switch(ch) { case '(': case ')': case '&': case '|': case '!': case '=': case '*': return ch.toString(); case '>': case '<': Character eq = nextChar(); if ( eq == null || '=' != eq ) { parseError("Expected '=' following '" + ch + "'. Note strict '>' and '<' not supported"); return null; } return String.valueOf(new char[] { ch, eq }); default: StringBuilder bldr = new StringBuilder(); bldr.append(ch); bldr.append(readText()); return bldr.toString(); } } String charsTil(char token) { if (m_peekedToken != null) { throw new IllegalStateException("Cannot compute charTil while a peeked token exists."); } StringBuilder buf = new StringBuilder(); boolean escaped = false; Character ch = peekChar(); while (ch != null && (ch != token || escaped) ) { buf.append(nextChar()); // use next char to move ptr forward escaped = ch == '\\' ? !escaped : false; ch = peekChar(); } return buf.toString(); } } private Lexer m_lexer; public FilterParser() { } public Filter parse(String filterString) { m_lexer = new Lexer(filterString); return filter(); } private Filter filter() { skipWhitespace(); match("("); Filter filter = filterComp(); skipWhitespace(); match(")"); return filter; } private Filter filterComp() { skipWhitespace(); String token = m_lexer.peekToken(); if ("&".equals(token)) { return and(); } else if ("|".equals(token)) { return or(); } else if ("!".equals(token)) { return not(); } else { return operation(); } } private Filter and() { match("&"); List<Filter> filters = filterList(); return new AndFilter(filters); } private Filter or() { match("|"); List<Filter> filters = filterList(); return new OrFilter(filters); } private Filter not() { match("!"); Filter filter = filter(); return new NotFilter(filter); } private LinkedList<Filter> filterList() { LinkedList<Filter> filters; Filter filter = filter(); skipWhitespace(); String token = m_lexer.peekToken(); if ("(".equals(token)) { filters = filterList(); } else { filters = new LinkedList<Filter>(); } filters.addFirst(filter); return filters; } private Filter operation() { String attribute = matchAttribute(); skipWhitespace(); String operation = m_lexer.peekToken(); if (">=".equals(operation)) { return greaterThan(attribute); } else if ("<=".equals(operation)) { return lessThan(attribute); } else if ("=".equals(operation)) { return eq(attribute); } else { parseError("Unsupported operation " + operation); return null; } } private Filter eq(String attribute) { match("="); String value = m_lexer.charsTil(')'); // a presence filter if ("*".equals(value.trim())) { return new PresenceFilter(attribute); } // a simple equals filter if (!value.replace("\\*", "").contains("*")) { return new EqFilter(attribute, value.replaceAll("\\\\(.)", "$1")); } return new PatternMatchingFilter(attribute, value); } private void assertNotEnd(String token, String msg) { if (token == null) { parseError("Unexpected end of input. " + msg); } } private Filter lessThan(String attribute) { match("<="); String value = m_lexer.nextToken(); assertNotEnd(value, "Expected a value following <="); return new LessThanFilter(attribute, value); } private Filter greaterThan(String attribute) { match(">="); String value = m_lexer.nextToken(); assertNotEnd(value, "Expected a value following >="); return new GreaterThanFilter(attribute, value); } private String matchAttribute() { String token = m_lexer.nextToken(); assertNotEnd(token, "Expected an attribute name."); String attr = token.trim(); ensureAttrDoesNotContain(attr, "()&|!*=<>~"); return attr; } private void ensureAttrDoesNotContain(String attr, String invalidChars) { for(int i = 0; i < invalidChars.length(); i++) { char ch = invalidChars.charAt(i); if (attr.contains(String.valueOf(ch))) { parseError("Attributes may not contain the '" + ch + "' characters"); } } } private String match(String expected) { String actual = m_lexer.nextToken(); assertNotEnd(actual, "Expected " + expected); if (!expected.equals(actual)) { parseError("Unexpected token " + actual + ". Expected " + expected); return null; } return actual; } private void skipWhitespace() { String token = m_lexer.peekToken(); if (token != null && "".equals(token.trim())) { m_lexer.nextToken(); // skip whitespace token token = m_lexer.peekToken(); } } void parseError(String msg) { throw new IllegalArgumentException(msg); } }