/**
* 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.metamodel.query.parser;
/**
* Parser of query parts. This parser is aware of parenthesis symbols '(' and
* ')' and only yields tokens that have balanced parentheses. Delimitors are
* configurable.
*/
public final class QueryPartParser {
private final QueryPartProcessor _processor;
private final String _clause;
private final String[] _ItemDelims;
public QueryPartParser(QueryPartProcessor processor, String clause, String... itemDelims) {
if (clause == null) {
throw new IllegalArgumentException("Clause cannot be null");
}
if (itemDelims == null || itemDelims.length == 0) {
throw new IllegalArgumentException("Item delimitors cannot be null or empty");
}
_processor = processor;
_clause = clause;
_ItemDelims = itemDelims;
}
public void parse() {
if (_clause.isEmpty()) {
return;
}
int parenthesisCount = 0;
int offset = 0;
boolean singleOuterParenthesis = _clause.charAt(0) == '(' && _clause.charAt(_clause.length() - 1) == ')';
String previousDelim = null;
DelimOccurrence nextDelimOccurrence = getNextDelim(0);
if (nextDelimOccurrence != null) {
for (int i = 0; i < _clause.length(); i++) {
char c = _clause.charAt(i);
if (c == '(') {
parenthesisCount++;
} else if (c == ')') {
parenthesisCount--;
if (singleOuterParenthesis && parenthesisCount == 0 && i != _clause.length() - 1) {
singleOuterParenthesis = false;
}
}
if (i == nextDelimOccurrence.index) {
if (parenthesisCount == 0) {
// token bounds has been identified
String itemToken = _clause.substring(offset, i);
parseItem(previousDelim, itemToken);
offset = i + nextDelimOccurrence.delim.length();
previousDelim = nextDelimOccurrence.delim;
}
nextDelimOccurrence = getNextDelim(nextDelimOccurrence.index + 1);
if (nextDelimOccurrence == null) {
break;
}
}
}
}
if (singleOuterParenthesis) {
String newClause = _clause.substring(1, _clause.length() - 1);
// re-run based on new clause
QueryPartParser newParser = new QueryPartParser(_processor, newClause, _ItemDelims);
newParser.parse();
return;
}
// last token will occur outside loop
if (offset != _clause.length()) {
final String token = _clause.substring(offset);
parseItem(previousDelim, token);
}
}
private static class DelimOccurrence {
public int index;
public String delim;
}
private DelimOccurrence getNextDelim(int offset) {
DelimOccurrence result = null;
for (int i = 0; i < _ItemDelims.length; i++) {
String delim = _ItemDelims[i];
int index = _clause.toUpperCase().indexOf(delim, offset);
if (index != -1) {
if (result == null || index == Math.min(result.index, index)) {
result = new DelimOccurrence();
result.index = index;
result.delim = delim;
}
}
}
return result;
}
private void parseItem(String delim, String token) {
if (token != null) {
token = token.trim();
if (!token.isEmpty()) {
_processor.parse(delim, token);
}
}
}
}