/*******************************************************************************
* Copyright (c) 2006-2013, Cloudsmith Inc.
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the copyright holder
* listed above, as the Initial Contributor under such license. The text or
* such license is available at www.eclipse.org.
******************************************************************************/
package org.eclipse.buckminster.osgi.filter.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.buckminster.osgi.filter.Filter;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.InvalidSyntaxException;
/**
* Parser class for OSGi filter strings. This class parses the complete filter
* string and builds a tree of Filter objects rooted at the parent.
*/
public class Parser {
private static class Compacter {
private FilterImpl base;
private List<FilterImpl> parts;
private int op;
Compacter(FilterImpl base, int op) {
this.base = base;
this.op = op;
}
FilterImpl getResultingFilter() {
if (parts == null)
return base;
int partsOp = op == FilterImpl.AND ? FilterImpl.OR : FilterImpl.AND;
return base.addFilter(normalize(parts, partsOp), op);
}
boolean merge(FilterImpl b) {
FilterImpl[] aArr;
FilterImpl[] bArr;
if (base.getOp() == op)
aArr = base.getFilterImpls();
else
aArr = new FilterImpl[] { base };
if (b.getOp() == op)
bArr = b.getFilterImpls();
else
bArr = new FilterImpl[] { b };
List<FilterImpl> common = null;
List<FilterImpl> onlyA = null;
int atop = aArr.length;
int btop = bArr.length;
int aidx;
int bidx;
for (aidx = 0; aidx < atop; ++aidx) {
FilterImpl af = aArr[aidx];
for (bidx = 0; bidx < btop; ++bidx) {
FilterImpl bf = bArr[bidx];
if (af.equals(bf)) {
if (common == null)
common = new ArrayList<FilterImpl>();
common.add(af);
break;
}
}
if (bidx == btop) {
if (onlyA == null)
onlyA = new ArrayList<FilterImpl>();
onlyA.add(af);
}
}
if (common == null)
// Nothing in common
return false;
if (onlyA == null && parts == null)
return true;
List<FilterImpl> onlyB = null;
for (bidx = 0; bidx < btop; ++bidx) {
FilterImpl bf = bArr[bidx];
for (aidx = 0; aidx < atop; ++aidx)
if (bf.equals(aArr[aidx]))
break;
if (aidx == atop) {
if (onlyB == null)
onlyB = new ArrayList<FilterImpl>();
onlyB.add(bf);
}
}
if (onlyB == null && parts == null) {
// All of B is already covered by base
base = b;
return true;
}
if (parts == null)
parts = new ArrayList<FilterImpl>();
if (onlyA != null) {
base = normalize(common, op);
FilterImpl af = normalize(onlyA, op);
if (!parts.contains(af))
parts.add(af);
}
FilterImpl bf = normalize(onlyB, op);
if (!parts.contains(bf))
parts.add(bf);
return true;
}
}
public static Filter parse(String filterString) throws InvalidSyntaxException {
return new Parser(filterString).internalParse();
}
static FilterImpl normalize(List<FilterImpl> operands, int op) {
int top = operands.size();
if (top == 1)
return operands.get(0);
// a | (b | c) becomes a | b | c
// a & (b & c) becomes a & b & c
//
for (int idx = 0; idx < top; ++idx) {
FilterImpl f = operands.get(idx);
if (f.getOp() != op)
continue;
FilterImpl[] sfs = f.getFilterImpls();
operands.remove(idx);
--top;
for (int ndx = 0; ndx < sfs.length; ++ndx) {
FilterImpl nf = sfs[ndx];
if (!operands.contains(nf))
operands.add(nf);
}
}
if (top == 1)
return operands.get(0);
Collections.sort(operands);
List<Compacter> splits = new ArrayList<Compacter>();
int reverseOp = op == FilterImpl.AND ? FilterImpl.OR : FilterImpl.AND;
for (int idx = 0; idx < top; ++idx)
merge(splits, operands.get(idx), reverseOp);
operands.clear();
top = splits.size();
for (int idx = 0; idx < top; ++idx) {
FilterImpl filter = splits.get(idx).getResultingFilter();
if (!operands.contains(filter))
operands.add(filter);
}
top = operands.size();
if (top == 1)
return operands.get(0);
Collections.sort(operands);
return new AndOrFilterImpl(op, operands.toArray(new FilterImpl[top]));
}
private static void merge(List<Compacter> splits, FilterImpl filterImpl, int op) {
int top = splits.size();
for (int idx = 0; idx < top; ++idx) {
Compacter split = splits.get(idx);
if (split.merge(filterImpl))
return;
}
splits.add(new Compacter(filterImpl, op));
}
private final String filterString;
private final StringBuilder sb = new StringBuilder();
private int pos;
private Parser(String filter) {
this.filterString = filter;
this.pos = 0;
}
private Filter internalParse() throws InvalidSyntaxException {
try {
FilterImpl filter = parse_filter();
if (pos != filterString.length())
throw syntaxException(Messages.FILTER_TRAILING_CHARACTERS);
return filter;
} catch (StringIndexOutOfBoundsException e) {
throw new InvalidSyntaxException(NLS.bind(Messages.FILTER_PREMATURE_END, filterString), filterString);
}
}
private FilterImpl parse_and() throws InvalidSyntaxException {
skipWhiteSpace();
char c = filterString.charAt(pos);
if (c != '(')
throw syntaxException(Messages.FILTER_MISSING_LEFTPAREN);
ArrayList<FilterImpl> operands = new ArrayList<FilterImpl>();
while (c == '(') {
FilterImpl child = parse_filter();
if (!operands.contains(child))
operands.add(child);
c = filterString.charAt(pos);
}
return normalize(operands, FilterImpl.AND);
}
private String parse_attr() throws InvalidSyntaxException {
skipWhiteSpace();
int begin = pos;
int end = pos;
char c = filterString.charAt(begin);
while (!(c == '~' || c == '<' || c == '>' || c == '=' || c == '(' || c == ')')) {
pos++;
if (!Character.isWhitespace(c))
end = pos;
c = filterString.charAt(pos);
}
if (end == begin)
throw syntaxException(Messages.FILTER_MISSING_ATTR);
return filterString.substring(begin, end);
}
private FilterImpl parse_filter() throws InvalidSyntaxException {
FilterImpl filter;
skipWhiteSpace();
if (filterString.charAt(pos) != '(')
throw syntaxException(Messages.FILTER_MISSING_LEFTPAREN);
pos++;
filter = parse_filtercomp();
skipWhiteSpace();
if (filterString.charAt(pos) != ')')
throw syntaxException(Messages.FILTER_MISSING_RIGHTPAREN);
pos++;
skipWhiteSpace();
return filter;
}
private FilterImpl parse_filtercomp() throws InvalidSyntaxException {
skipWhiteSpace();
char c = filterString.charAt(pos);
switch (c) {
case '&': {
pos++;
return parse_and();
}
case '|': {
pos++;
return parse_or();
}
case '!': {
pos++;
return parse_not();
}
}
return parse_item();
}
private FilterImpl parse_item() throws InvalidSyntaxException {
String attr = parse_attr();
skipWhiteSpace();
char c2 = filterString.charAt(pos + 1);
switch (filterString.charAt(pos)) {
case '~':
if (c2 == '=') {
pos += 2;
return new StringFilterImpl(FilterImpl.APPROX, attr, parse_value());
}
break;
case '>':
if (c2 == '=') {
pos += 2;
return new StringFilterImpl(FilterImpl.GREATER, attr, parse_value());
}
break;
case '<':
if (c2 == '=') {
pos += 2;
return new StringFilterImpl(FilterImpl.LESS, attr, parse_value());
}
break;
case '=':
if (c2 == '*') {
int oldpos = pos;
pos += 2;
skipWhiteSpace();
if (filterString.charAt(pos) == ')')
return new PresentFilterImpl(attr);
pos = oldpos;
}
pos++;
Object string = parse_substring();
return (string instanceof String) ? new StringFilterImpl(FilterImpl.EQUAL, attr, (String) string) : new SubstringFilterImpl(attr,
(String[]) string);
}
throw syntaxException(Messages.FILTER_INVALID_OPERATOR);
}
private FilterImpl parse_not() throws InvalidSyntaxException {
skipWhiteSpace();
if (filterString.charAt(pos) != '(')
throw syntaxException(Messages.FILTER_MISSING_LEFTPAREN);
FilterImpl child = parse_filter();
return child.getOp() == FilterImpl.NOT ? ((NotFilterImpl) child).getFilter() : new NotFilterImpl(child);
}
private FilterImpl parse_or() throws InvalidSyntaxException {
skipWhiteSpace();
char c = filterString.charAt(pos);
if (c != '(')
throw syntaxException(Messages.FILTER_MISSING_LEFTPAREN);
ArrayList<FilterImpl> operands = new ArrayList<FilterImpl>();
while (c == '(') {
FilterImpl child = parse_filter();
operands.add(child);
c = filterString.charAt(pos);
}
return normalize(operands, FilterImpl.OR);
}
private Object parse_substring() throws InvalidSyntaxException {
List<String> operands = null;
sb.setLength(0);
parseloop: while (true) {
char c = filterString.charAt(pos);
switch (c) {
case ')':
if (sb.length() > 0) {
String val = sb.toString();
if (operands == null)
return val;
operands.add(val);
}
break parseloop;
case '(':
throw syntaxException(Messages.FILTER_INVALID_VALUE);
case '*':
if (operands == null)
operands = new ArrayList<String>();
if (sb.length() > 0) {
operands.add(sb.toString());
sb.setLength(0);
}
operands.add(null);
pos++;
break;
case '\\':
c = filterString.charAt(++pos);
/* fall through into default */
default:
sb.append(c);
pos++;
break;
}
}
if (operands == null)
throw syntaxException(Messages.FILTER_MISSING_VALUE);
int size = operands.size();
if (size == 1) {
String single = operands.get(0);
if (single != null)
return single;
}
return operands.toArray(new String[size]);
}
private String parse_value() throws InvalidSyntaxException {
sb.setLength(0);
parseloop: while (true) {
char c = filterString.charAt(pos);
switch (c) {
case ')':
break parseloop;
case '(':
throw syntaxException(Messages.FILTER_INVALID_VALUE);
case '\\':
c = filterString.charAt(++pos);
/* fall through into default */
default:
sb.append(c);
pos++;
break;
}
}
int len = sb.length();
while (len > 0 && sb.charAt(len - 1) <= ' ')
--len;
if (len == 0)
throw syntaxException(Messages.FILTER_MISSING_VALUE);
sb.setLength(len);
return sb.toString();
}
private void skipWhiteSpace() {
for (int top = filterString.length(); pos < top && Character.isWhitespace(filterString.charAt(pos)); ++pos)
;
}
private InvalidSyntaxException syntaxException(String msg) {
return new InvalidSyntaxException(NLS.bind(msg, filterString, Integer.valueOf(pos)), filterString);
}
}