package aQute.bnd.osgi.resource;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import aQute.bnd.version.Version;
import aQute.lib.exceptions.Exceptions;
import aQute.lib.strings.Strings;
public class FilterParser {
final Map<String,Expression> cache = new HashMap<String,FilterParser.Expression>();
public enum Op {
GREATER(">"), GREATER_OR_EQUAL(">="), LESS("<"), LESS_OR_EQUAL("<="), EQUAL("="), NOT_EQUAL("!="), RANGE("..");
private String symbol;
Op(String s) {
this.symbol = s;
}
public Op not() {
switch (this) {
case GREATER :
return LESS_OR_EQUAL;
case GREATER_OR_EQUAL :
return LESS;
case LESS :
return GREATER_OR_EQUAL;
case LESS_OR_EQUAL :
return GREATER;
case EQUAL :
return NOT_EQUAL;
case NOT_EQUAL :
return EQUAL;
default :
return null;
}
}
public String toString() {
return symbol;
}
}
public static abstract class Expression {
static Expression TRUE = new Expression() {
@Override
public boolean eval(Map<String, ? > map) {
return true;
}
@Override
Expression not() {
return FALSE;
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visitTrue();
}
@Override
void toString(StringBuilder sb) {
sb.append("true");
}
};
static Expression FALSE = new Expression() {
@Override
public boolean eval(Map<String, ? > map) {
return false;
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visitFalse();
}
@Override
Expression not() {
return TRUE;
}
void toString(StringBuilder sb) {
sb.append("false");
}
};
public abstract boolean eval(Map<String, ? > map);
public abstract <T> T visit(ExpressionVisitor<T> visitor);
Expression not() {
return null;
}
abstract void toString(StringBuilder sb);
public String toString() {
StringBuilder sb = new StringBuilder();
toString(sb);
return sb.toString();
}
public String query() {
return null;
}
}
public static class RangeExpression extends SimpleExpression {
final SimpleExpression low;
final SimpleExpression high;
public RangeExpression(String key, SimpleExpression low, SimpleExpression high) {
super(key, Op.RANGE, null);
this.low = low;
this.high = high;
}
@Override
protected boolean eval(Object scalar) {
return (low == null || low.eval(scalar)) && (high == null || high.eval(scalar));
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visit(this);
}
static Expression make(String key, SimpleExpression low, SimpleExpression high) {
if (key.indexOf("version") >= 0) {
try {
Version a = Version.parseVersion(low.value);
Version b = Version.parseVersion(high.value);
if (a.compareTo(b) > 0)
return FALSE;
if (a.equals(Version.LOWEST) && b.equals(Version.HIGHEST))
return TRUE;
if (b.equals(Version.HIGHEST))
return low;
if (a.equals(Version.LOWEST))
return high;
} catch (Exception e) {
// ignore, might not be a version
}
}
return new RangeExpression(key, low, high);
}
public String getRangeString() {
StringBuilder sb = new StringBuilder();
if (low != null) {
if (high == null)
sb.append(low.value);
else {
if (low.op == Op.GREATER)
sb.append("(");
else
sb.append("[");
sb.append(low.value);
}
}
if (high != null) {
sb.append(",");
if (low == null) {
sb.append("[0.0.0,");
}
sb.append(high.value);
if (high.op == Op.LESS)
sb.append(")");
else
sb.append("]");
}
return sb.toString();
}
public void toString(StringBuilder sb) {
sb.append(key).append("=").append(getRangeString());
}
public SimpleExpression getLow() {
return low;
}
public SimpleExpression getHigh() {
return high;
}
}
public static class SimpleExpression extends Expression {
final Op op;
final String key;
final String value;
transient Object cached;
public SimpleExpression(String key, Op op, String value) {
this.key = key;
this.op = op;
this.value = value;
}
@Override
public boolean eval(Map<String, ? > map) {
Object target = map.get(key);
if (target instanceof Iterable) {
for (Object scalar : (Iterable< ? >) target) {
if (eval(scalar))
return true;
}
return false;
} else if (target.getClass().isArray()) {
int l = Array.getLength(target);
for (int i = 0; i < l; i++) {
if (eval(Array.get(target, i)))
return true;
}
return false;
} else {
return eval(target);
}
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visit(this);
}
protected boolean eval(Object scalar) {
if (cached == null || cached.getClass() != scalar.getClass()) {
Class< ? > scalarClass = scalar.getClass();
if (scalarClass == String.class)
cached = value;
else if (scalarClass == Byte.class)
cached = Byte.parseByte(value);
else if (scalarClass == Short.class)
cached = Short.parseShort(value);
else if (scalarClass == Integer.class)
cached = Integer.parseInt(value);
else if (scalarClass == Long.class)
cached = Long.parseLong(value);
else if (scalarClass == Float.class)
cached = Float.parseFloat(value);
else if (scalarClass == Double.class)
cached = Double.parseDouble(value);
else if (scalarClass == Character.class)
cached = value;
else {
try {
Method factory = scalarClass.getMethod("valueOf", String.class);
cached = factory.invoke(null, value);
} catch (Exception e) {
Constructor< ? > constructor;
try {
constructor = scalarClass.getConstructor(String.class);
cached = constructor.newInstance(value);
} catch (Exception e1) {
cached = value;
}
}
}
}
if (op == Op.EQUAL)
return cached == scalar || cached.equals(scalar);
if (op == Op.NOT_EQUAL)
return !cached.equals(scalar);
if (cached instanceof Comparable< ? >) {
@SuppressWarnings("unchecked")
int result = ((Comparable<Object>) scalar).compareTo(cached);
switch (op) {
case LESS :
return result < 0;
case LESS_OR_EQUAL :
return result <= 0;
case GREATER :
return result > 0;
case GREATER_OR_EQUAL :
return result >= 0;
default :
break;
}
}
return false;
}
static Expression make(String key, Op op, String value) {
if (op == Op.EQUAL) {
if ("osgi.wiring.bundle".equals(key))
return new BundleExpression(value);
else if ("osgi.wiring.host".equals(key))
return new HostExpression(value);
else if ("osgi.wiring.package".equals(key))
return new PackageExpression(value);
else if ("osgi.identity".equals(key))
return new IdentityExpression(value);
}
return new SimpleExpression(key, op, value);
}
Expression not() {
Op alt = op.not();
if (alt == null)
return null;
return new SimpleExpression(key, alt, value);
}
public void toString(StringBuilder sb) {
sb.append(key).append(op.toString()).append(value);
}
@Override
public String query() {
return value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public Op getOp() {
return op;
}
}
public abstract static class WithRangeExpression extends Expression {
RangeExpression range;
public boolean eval(Map<String, ? > map) {
return range == null || range.eval(map);
}
@Override
void toString(StringBuilder sb) {
if (range == null)
return;
sb.append("; ");
range.toString(sb);
}
public RangeExpression getRangeExpression() {
return range;
}
public abstract String printExcludingRange();
}
public static class PackageExpression extends WithRangeExpression {
final String packageName;
public PackageExpression(String value) {
this.packageName = value;
}
@Override
public boolean eval(Map<String, ? > map) {
String p = (String) map.get("osgi.wiring.package");
if (p == null)
return false;
return packageName.equals(p) && super.eval(map);
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
void toString(StringBuilder sb) {
sb.append(packageName);
super.toString(sb);
}
public String getPackageName() {
return packageName;
}
public String query() {
return "p:" + packageName;
}
@Override
public String printExcludingRange() {
return packageName;
}
}
public static class HostExpression extends WithRangeExpression {
final String hostName;
public HostExpression(String value) {
this.hostName = value;
}
@Override
public boolean eval(Map<String, ? > map) {
String p = (String) map.get("osgi.wiring.host");
if (p == null)
return false;
return hostName.equals(p) && super.eval(map);
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
void toString(StringBuilder sb) {
sb.append(hostName);
super.toString(sb);
}
public String getHostName() {
return hostName;
}
public String query() {
return "bsn:" + hostName;
}
@Override
public String printExcludingRange() {
return hostName;
}
}
public static class BundleExpression extends WithRangeExpression {
final String bundleName;
public BundleExpression(String value) {
this.bundleName = value;
}
@Override
public boolean eval(Map<String, ? > map) {
String p = (String) map.get("osgi.wiring.bundle");
if (p == null)
return false;
return bundleName.equals(p) && super.eval(map);
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
void toString(StringBuilder sb) {
sb.append(bundleName);
super.toString(sb);
}
public String query() {
return "bsn:" + bundleName;
}
@Override
public String printExcludingRange() {
return bundleName;
}
}
public static class IdentityExpression extends WithRangeExpression {
final String identity;
public IdentityExpression(String value) {
this.identity = value;
}
@Override
public boolean eval(Map<String, ? > map) {
String p = (String) map.get("osgi.identity");
if (p == null)
return false;
return identity.equals(p);
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
void toString(StringBuilder sb) {
sb.append(identity);
super.toString(sb);
}
public String getSymbolicName() {
return identity;
}
public String query() {
return "bsn:" + identity;
}
@Override
public String printExcludingRange() {
return identity;
}
}
public static abstract class SubExpression extends Expression {
Expression[] expressions;
void toString(StringBuilder sb) {
for (Expression e : expressions) {
sb.append("(");
e.toString(sb);
sb.append(")");
}
}
public Expression[] getExpressions() {
return expressions;
}
@Override
public String query() {
if (expressions == null || expressions.length == 0)
return null;
if (expressions[0] instanceof WithRangeExpression) {
return expressions[0].query();
}
List<String> words = new ArrayList<String>();
for (Expression e : expressions) {
String query = e.query();
if (query != null)
words.add(query);
}
return Strings.join(" ", words);
}
}
public static class And extends SubExpression {
private And(List<Expression> exprs) {
this.expressions = exprs.toArray(new Expression[0]);
}
public boolean eval(Map<String, ? > map) {
for (Expression e : expressions) {
if (!e.eval(map))
return false;
}
return true;
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visit(this);
}
static Expression make(List<Expression> exprs) {
for (Iterator<Expression> i = exprs.iterator(); i.hasNext();) {
Expression e = i.next();
if (e == FALSE)
return FALSE;
if (e == TRUE)
i.remove();
}
if (exprs.size() == 0)
return TRUE;
SimpleExpression lower = null;
SimpleExpression higher = null;
WithRangeExpression wre = null;
for (Expression e : exprs) {
if (e instanceof WithRangeExpression) {
wre = (WithRangeExpression) e;
} else if (e instanceof SimpleExpression) {
SimpleExpression se = (SimpleExpression) e;
if (se.key.equals("version") || se.key.equals("bundle-version")) {
if (se.op == Op.GREATER || se.op == Op.GREATER_OR_EQUAL)
lower = se;
else if (se.op == Op.LESS || se.op == Op.LESS_OR_EQUAL)
higher = se;
}
}
}
RangeExpression range = null;
if (lower != null || higher != null) {
if (lower != null && higher != null) {
exprs.remove(lower);
exprs.remove(higher);
range = new RangeExpression("version", lower, higher);
} else if (lower != null && lower.op == Op.GREATER_OR_EQUAL && higher == null) {
exprs.remove(lower);
range = new RangeExpression("version", lower, null);
}
}
if (range != null) {
if (wre != null)
wre.range = range;
else
exprs.add(range);
}
if (exprs.size() == 1)
return exprs.get(0);
return new And(exprs);
}
@Override
public void toString(StringBuilder sb) {
if (expressions != null && expressions.length > 0) {
if (expressions[0] instanceof WithRangeExpression) {
sb.append(expressions[0]);
for (int i = 1; i < expressions.length; i++) {
sb.append("; ");
expressions[i].toString(sb);
}
return;
}
}
sb.append("&");
super.toString(sb);
}
}
public static class Or extends SubExpression {
private Or(List<Expression> exprs) {
this.expressions = exprs.toArray(new Expression[0]);
}
public boolean eval(Map<String, ? > map) {
for (Expression e : expressions) {
if (e.eval(map))
return true;
}
return false;
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visit(this);
}
static Expression make(List<Expression> exprs) {
for (Iterator<Expression> i = exprs.iterator(); i.hasNext();) {
Expression e = i.next();
if (e == TRUE)
return TRUE;
if (e == FALSE)
i.remove();
}
if (exprs.size() == 0)
return FALSE;
if (exprs.size() == 1)
return exprs.get(0);
return new Or(exprs);
}
@Override
public void toString(StringBuilder sb) {
sb.append("|");
super.toString(sb);
}
}
public static class Not extends Expression {
Expression expr;
private Not(Expression expr) {
this.expr = expr;
}
public boolean eval(Map<String, ? > map) {
return !expr.eval(map);
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visit(this);
}
public static Expression make(Expression expr) {
if (expr == TRUE)
return FALSE;
if (expr == FALSE)
return TRUE;
Expression notexpr = expr.not();
if (notexpr != null)
return notexpr;
return new Not(expr);
}
@Override
Expression not() {
return expr;
}
@Override
public void toString(StringBuilder sb) {
sb.append("!(");
expr.toString(sb);
sb.append(")");
}
}
public static class PatternExpression extends SimpleExpression {
final Pattern pattern;
public PatternExpression(String key, String value) {
super(key, Op.EQUAL, value);
value = Pattern.quote(value);
this.pattern = Pattern.compile(value.replace("\\*", ".*"));
}
protected boolean eval(Object scalar) {
if (scalar instanceof String)
return pattern.matcher((String) scalar).matches();
else
return false;
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visit(this);
}
}
public static class ApproximateExpression extends SimpleExpression {
public ApproximateExpression(String key, String value) {
super(key, Op.EQUAL, value);
}
protected boolean eval(Object scalar) {
if (scalar instanceof String) {
return ((String) scalar).trim().equalsIgnoreCase(value);
} else
return false;
}
@Override
public <T> T visit(ExpressionVisitor<T> visitor) {
return visitor.visit(this);
}
}
public static abstract class ExpressionVisitor<T> {
private final T defaultValue;
public ExpressionVisitor(T defaultValue) {
this.defaultValue = defaultValue;
}
public T visit(RangeExpression expr) {
return defaultValue;
}
public T visit(SimpleExpression expr) {
return defaultValue;
}
public T visit(PackageExpression expr) {
return defaultValue;
}
public T visit(HostExpression expr) {
return defaultValue;
}
public T visit(BundleExpression expr) {
return defaultValue;
}
public T visit(IdentityExpression expr) {
return defaultValue;
}
public T visit(And expr) {
return defaultValue;
}
public T visit(Or expr) {
return defaultValue;
}
public T visit(Not expr) {
return defaultValue;
}
public T visit(PatternExpression expr) {
return defaultValue;
}
public T visit(ApproximateExpression expr) {
return defaultValue;
}
public T visitTrue() {
return defaultValue;
}
public T visitFalse() {
return defaultValue;
}
}
static class Rover {
String s;
int n = 0;
char next() {
return s.charAt(n++);
}
char wsNext() {
ws();
return next();
}
char current() {
return s.charAt(n);
}
void ws() {
while (Character.isWhitespace(current()))
n++;
}
String findExpr() {
int nn = n;
int level = 0;
while (nn < s.length()) {
char c = s.charAt(nn++);
switch (c) {
case '(' :
level++;
break;
case '\\' :
nn++;
break;
case ')' :
level--;
if (level == 0)
return s.substring(n, nn);
}
}
// bad expression
return "";
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(s).append("\n");
for (int i = 0; i < n; i++)
sb.append(" ");
sb.append("|");
return sb.toString();
}
private boolean isOpChar(char s) {
return s == '=' || s == '~' || s == '>' || s == '<' || s == '(' || s == ')';
}
String getKey() {
int n = this.n;
while (!isOpChar(current()))
next();
return s.substring(n, this.n).trim();
}
String getValue() {
int n = this.n;
while (current() != ')') {
char c = next();
if (c == '\\') {
// TODO verify if they escape other chars?
this.n++;
}
}
return s.substring(n, this.n);
}
}
public Expression parse(String s) {
Rover rover = new Rover();
rover.s = s;
rover.n = 0;
return parse(rover);
}
public Expression parse(Requirement req) {
String f = req.getDirectives().get("filter");
if (f == null)
return Expression.FALSE;
return parse(f);
}
public Expression parse(Rover rover) {
try {
String cacheKey = rover.findExpr();
Expression e = cache.get(cacheKey);
if (e != null) {
rover.n += cacheKey.length();
return e;
}
rover.ws();
char c = rover.current();
if (c != '(')
throw new IllegalArgumentException("Expression must start with a '('");
rover.next();
rover.ws();
e = parse0(rover);
rover.ws();
c = rover.current();
if (c != ')')
throw new IllegalArgumentException("Expression must end with a ')'");
rover.next();
cache.put(cacheKey, e);
return e;
} catch (RuntimeException re) {
throw new RuntimeException("Parsing failed: " + re.getMessage() + ":\n" + rover + "\n", re);
}
}
Expression parse0(Rover rover) {
rover.ws();
switch (rover.next()) {
case '&' :
return And.make(parseExprs(rover));
case '|' :
return Or.make(parseExprs(rover));
case '!' :
return Not.make(parse(rover));
default :
rover.n--;
String key = rover.getKey();
char s = rover.next();
if (s == '=') {
String value = rover.getValue();
if (value.indexOf('*') >= 0)
return new PatternExpression(key, value);
else
return SimpleExpression.make(key, Op.EQUAL, value);
}
char eq = rover.next();
if (eq != '=')
throw new IllegalArgumentException("Expected an = after " + rover.current());
switch (s) {
case '~' :
return new ApproximateExpression(key, rover.getValue());
case '>' :
return SimpleExpression.make(key, Op.GREATER_OR_EQUAL, rover.getValue());
case '<' :
return SimpleExpression.make(key, Op.LESS_OR_EQUAL, rover.getValue());
default :
throw new IllegalArgumentException("Expected '~=', '>=', '<='");
}
}
}
private List<Expression> parseExprs(Rover rover) {
ArrayList<Expression> exprs = new ArrayList<Expression>();
rover.ws();
while (rover.current() == '(') {
Expression expr = parse(rover);
exprs.add(expr);
rover.ws();
}
return exprs;
}
public static String namespaceToCategory(String namespace) {
String result;
if ("osgi.wiring.package".equals(namespace)) {
result = "Import-Package";
} else if ("osgi.wiring.bundle".equals(namespace)) {
result = "Require-Bundle";
} else if ("osgi.wiring.host".equals(namespace)) {
result = "Fragment-Host";
} else if ("osgi.identity".equals(namespace)) {
result = "ID";
} else if ("osgi.content".equals(namespace)) {
result = "Content";
} else if ("osgi.extender".equals(namespace)) {
result = "Extender";
} else if ("osgi.service".equals(namespace)) {
result = "Service";
} else if ("osgi.contract".equals(namespace)) {
return "Contract";
} else {
result = namespace;
}
return result;
}
public static String toString(Requirement r) {
try {
StringBuilder sb = new StringBuilder();
String category = namespaceToCategory(r.getNamespace());
if (category != null && category.length() > 0)
sb.append(namespaceToCategory(category)).append(": ");
FilterParser fp = new FilterParser();
String filter = r.getDirectives().get("filter");
if (filter == null)
sb.append("<no filter>");
else {
Expression parse = fp.parse(filter);
sb.append(parse);
}
return sb.toString();
} catch (Exception e) {
return Exceptions.toString(e);
}
}
public String simple(Resource resource) {
if (resource == null)
return "<>";
List<Capability> capabilities = resource.getCapabilities("osgi.identity");
if (capabilities.isEmpty())
return resource.toString();
Capability c = capabilities.get(0);
String bsn = (String) c.getAttributes().get("osgi.identity");
Object version = c.getAttributes().get("version");
if (version == null)
return bsn;
else
return bsn + ";version=" + version;
}
}