/*******************************************************************************
* Copyright (c) 2003, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.internal.framework;
import java.lang.reflect.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import org.eclipse.osgi.framework.util.CaseInsensitiveDictionaryMap;
import org.eclipse.osgi.internal.debug.Debug;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.internal.serviceregistry.ServiceReferenceImpl;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
/**
* RFC 1960-based Filter. Filter objects can be created by calling
* the constructor with the desired filter string.
* A Filter object can be called numerous times to determine if the
* match argument matches the filter string that was used to create the Filter
* object.
*
*
The syntax of a filter string is the string representation
* of LDAP search filters as defined in RFC 1960:
* A String Representation of LDAP Search Filters (available at
* http://www.ietf.org/rfc/rfc1960.txt).
* It should be noted that RFC 2254:
* A String Representation of LDAP Search Filters
* (available at http://www.ietf.org/rfc/rfc2254.txt) supersedes
* RFC 1960 but only adds extensible matching and is not applicable for this
* API.
*
*
The string representation of an LDAP search filter is defined by the
* following grammar. It uses a prefix format.
*
*
* <attr> is a string representing an attribute, or
* key, in the properties objects of the registered services.
* Attribute names are not case sensitive;
* that is cn and CN both refer to the same attribute.
* <value> is a string representing the value, or part of
* one, of a key in the properties objects of the registered services.
* If a <value> must
* contain one of the characters '*' or '('
* or ')', these characters
* should be escaped by preceding them with the backslash '\'
* character.
* Note that although both the <substring> and
* <present> productions can
* produce the 'attr=*' construct, this construct is used only to
* denote a presence filter.
*
*
The approximate match (~=) is implementation specific but
* should at least ignore case and white space differences. Optional are
* codes like soundex or other smart "closeness" comparisons.
*
*
Comparison of values is not straightforward. Strings
* are compared differently than numbers and it is
* possible for a key to have multiple values. Note that
* that keys in the match argument must always be strings.
* The comparison is defined by the object type of the key's
* value. The following rules apply for comparison:
*
*
* Note: arrays of primitives are also supported.
*
*
* A filter matches a key that has multiple values if it
* matches at least one of those values. For example,
*
* Dictionary d = new Hashtable();
* d.put( "cn", new String[] { "a", "b", "c" } );
*
* d will match (cn=a) and also (cn=b)
*
*
A filter component that references a key having an unrecognizable
* data type will evaluate to false .
*/
public class FilterImpl implements Filter /* since Framework 1.1 */ {
/* public methods in org.osgi.framework.Filter */
/**
* Constructs a {@link FilterImpl} object. This filter object may be used
* to match a {@link ServiceReferenceImpl} or a Dictionary.
*
*
If the filter cannot be parsed, an {@link InvalidSyntaxException}
* will be thrown with a human readable message where the
* filter became unparsable.
*
* @param filterString the filter string.
* @exception InvalidSyntaxException If the filter parameter contains
* an invalid filter string that cannot be parsed.
*/
public static FilterImpl newInstance(String filterString) throws InvalidSyntaxException {
return newInstance(filterString, false);
}
public static FilterImpl newInstance(String filterString, boolean debug) throws InvalidSyntaxException {
return new Parser(filterString, debug).parse();
}
/**
* Filter using a service's properties.
*
* This {@code Filter} is executed using the keys and values of the
* referenced service's properties. The keys are looked up in a case
* insensitive manner.
*
* @param reference The reference to the service whose properties are used
* in the match.
* @return {@code true} if the service's properties match this
* {@code Filter}; {@code false} otherwise.
*/
public boolean match(ServiceReference> reference) {
if (reference instanceof ServiceReferenceImpl) {
return matches(((ServiceReferenceImpl>) reference).getRegistration().getProperties());
}
return matches(new ServiceReferenceMap(reference));
}
/**
* Filter using a {@code Dictionary} with case insensitive key lookup. This
* {@code Filter} is executed using the specified {@code Dictionary}'s keys
* and values. The keys are looked up in a case insensitive manner.
*
* @param dictionary The {@code Dictionary} whose key/value pairs are used
* in the match.
* @return {@code true} if the {@code Dictionary}'s values match this
* filter; {@code false} otherwise.
* @throws IllegalArgumentException If {@code dictionary} contains case
* variants of the same key name.
*/
public boolean match(Dictionary dictionary) {
if (dictionary == null) {
return matches(null);
}
return matches(new CaseInsensitiveDictionaryMap<>(dictionary));
}
/**
* Filter using a {@code Dictionary}. This {@code Filter} is executed using
* the specified {@code Dictionary}'s keys and values. The keys are looked
* up in a normal manner respecting case.
*
* @param dictionary The {@code Dictionary} whose key/value pairs are used
* in the match.
* @return {@code true} if the {@code Dictionary}'s values match this
* filter; {@code false} otherwise.
* @since 1.3
*/
public boolean matchCase(Dictionary dictionary) {
switch (op) {
case AND : {
FilterImpl[] filters = (FilterImpl[]) value;
for (FilterImpl f : filters) {
if (!f.matchCase(dictionary)) {
return false;
}
}
return true;
}
case OR : {
FilterImpl[] filters = (FilterImpl[]) value;
for (FilterImpl f : filters) {
if (f.matchCase(dictionary)) {
return true;
}
}
return false;
}
case NOT : {
FilterImpl filter = (FilterImpl) value;
return !filter.matchCase(dictionary);
}
case SUBSTRING :
case EQUAL :
case GREATER :
case LESS :
case APPROX : {
Object prop = (dictionary == null) ? null : dictionary.get(attr);
return compare(op, prop, value);
}
case PRESENT : {
if (debug) {
Debug.println("PRESENT(" + attr + ")"); //$NON-NLS-1$ //$NON-NLS-2$
}
Object prop = (dictionary == null) ? null : dictionary.get(attr);
return prop != null;
}
}
return false;
}
/**
* Filter using a {@code Map}. This {@code Filter} is executed using the
* specified {@code Map}'s keys and values. The keys are looked up in a
* normal manner respecting case.
*
* @param map The {@code Map} whose key/value pairs are used in the match.
* Maps with {@code null} key or values are not supported. A
* {@code null} value is considered not present to the filter.
* @return {@code true} if the {@code Map}'s values match this filter;
* {@code false} otherwise.
* @since 1.6
*/
public boolean matches(Map map) {
switch (op) {
case AND : {
FilterImpl[] filters = (FilterImpl[]) value;
for (FilterImpl f : filters) {
if (!f.matches(map)) {
return false;
}
}
return true;
}
case OR : {
FilterImpl[] filters = (FilterImpl[]) value;
for (FilterImpl f : filters) {
if (f.matches(map)) {
return true;
}
}
return false;
}
case NOT : {
FilterImpl filter = (FilterImpl) value;
return !filter.matches(map);
}
case SUBSTRING :
case EQUAL :
case GREATER :
case LESS :
case APPROX : {
Object prop = (map == null) ? null : map.get(attr);
return compare(op, prop, value);
}
case PRESENT : {
if (debug) {
Debug.println("PRESENT(" + attr + ")"); //$NON-NLS-1$ //$NON-NLS-2$
}
Object prop = (map == null) ? null : map.get(attr);
return prop != null;
}
}
return false;
}
/**
* Returns this Filter object's filter string.
*
* The filter string is normalized by removing whitespace which does not
* affect the meaning of the filter.
*
* @return Filter string.
*/
public String toString() {
String result = filterString;
if (result == null) {
filterString = result = normalize().toString();
}
return result;
}
/**
* Returns this Filter's normalized filter string.
*
* The filter string is normalized by removing whitespace which does not
* affect the meaning of the filter.
*
* @return This Filter's filter string.
*/
private StringBuffer normalize() {
StringBuffer sb = new StringBuffer();
sb.append('(');
switch (op) {
case AND : {
sb.append('&');
FilterImpl[] filters = (FilterImpl[]) value;
for (FilterImpl f : filters) {
sb.append(f.normalize());
}
break;
}
case OR : {
sb.append('|');
FilterImpl[] filters = (FilterImpl[]) value;
for (FilterImpl f : filters) {
sb.append(f.normalize());
}
break;
}
case NOT : {
sb.append('!');
FilterImpl filter = (FilterImpl) value;
sb.append(filter.normalize());
break;
}
case SUBSTRING : {
sb.append(attr);
sb.append('=');
String[] substrings = (String[]) value;
for (String substr : substrings) {
if (substr == null) /* * */ {
sb.append('*');
} else /* xxx */ {
sb.append(encodeValue(substr));
}
}
break;
}
case EQUAL : {
sb.append(attr);
sb.append('=');
sb.append(encodeValue((String) value));
break;
}
case GREATER : {
sb.append(attr);
sb.append(">="); //$NON-NLS-1$
sb.append(encodeValue((String) value));
break;
}
case LESS : {
sb.append(attr);
sb.append("<="); //$NON-NLS-1$
sb.append(encodeValue((String) value));
break;
}
case APPROX : {
sb.append(attr);
sb.append("~="); //$NON-NLS-1$
sb.append(encodeValue(approxString((String) value)));
break;
}
case PRESENT : {
sb.append(attr);
sb.append("=*"); //$NON-NLS-1$
break;
}
}
sb.append(')');
return sb;
}
/**
* Compares this Filter object to another object.
*
* @param obj The object to compare against this Filter
* object.
* @return If the other object is a Filter object, then
* returns this.toString().equals(obj.toString();
* false otherwise.
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Filter)) {
return false;
}
return this.toString().equals(obj.toString());
}
/**
* Returns the hashCode for this Filter object.
*
* @return The hashCode of the filter string; that is,
* this.toString().hashCode().
*/
public int hashCode() {
return this.toString().hashCode();
}
/* non public fields and methods for the Filter implementation */
/** filter operation */
private final int op;
private static final int EQUAL = 1;
private static final int APPROX = 2;
private static final int GREATER = 3;
private static final int LESS = 4;
private static final int PRESENT = 5;
private static final int SUBSTRING = 6;
private static final int AND = 7;
private static final int OR = 8;
private static final int NOT = 9;
/** filter attribute or null if operation AND, OR or NOT */
private final String attr;
/** filter operands */
private final Object value;
/** debug mode */
private final boolean debug;
/* normalized filter string for topLevel Filter object */
private transient volatile String filterString;
FilterImpl(int operation, String attr, Object value, boolean debug) {
this.op = operation;
this.attr = attr;
this.value = value;
this.debug = debug;
}
/**
* Encode the value string such that '(', '*', ')'
* and '\' are escaped.
*
* @param value unencoded value string.
* @return encoded value string.
*/
private static String encodeValue(String value) {
boolean encoded = false;
int inlen = value.length();
int outlen = inlen << 1; /* inlen * 2 */
char[] output = new char[outlen];
value.getChars(0, inlen, output, inlen);
int cursor = 0;
for (int i = inlen; i < outlen; i++) {
char c = output[i];
switch (c) {
case '(' :
case '*' :
case ')' :
case '\\' : {
output[cursor] = '\\';
cursor++;
encoded = true;
break;
}
}
output[cursor] = c;
cursor++;
}
return encoded ? new String(output, 0, cursor) : value;
}
private boolean compare(int operation, Object value1, Object value2) {
if (value1 == null) {
if (debug) {
Debug.println("compare(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
return false;
}
if (value1 instanceof String) {
return compare_String(operation, (String) value1, value2);
}
if (value1 instanceof Version) {
return compare_Version(operation, (Version) value1, value2);
}
Class> clazz = value1.getClass();
if (clazz.isArray()) {
Class> type = clazz.getComponentType();
if (type.isPrimitive()) {
return compare_PrimitiveArray(operation, type, value1, value2);
}
return compare_ObjectArray(operation, (Object[]) value1, value2);
}
if (value1 instanceof Collection>) {
return compare_Collection(operation, (Collection>) value1, value2);
}
if (value1 instanceof Integer) {
return compare_Integer(operation, ((Integer) value1).intValue(), value2);
}
if (value1 instanceof Long) {
return compare_Long(operation, ((Long) value1).longValue(), value2);
}
if (value1 instanceof Byte) {
return compare_Byte(operation, ((Byte) value1).byteValue(), value2);
}
if (value1 instanceof Short) {
return compare_Short(operation, ((Short) value1).shortValue(), value2);
}
if (value1 instanceof Character) {
return compare_Character(operation, ((Character) value1).charValue(), value2);
}
if (value1 instanceof Float) {
return compare_Float(operation, ((Float) value1).floatValue(), value2);
}
if (value1 instanceof Double) {
return compare_Double(operation, ((Double) value1).doubleValue(), value2);
}
if (value1 instanceof Boolean) {
return compare_Boolean(operation, ((Boolean) value1).booleanValue(), value2);
}
if (value1 instanceof Comparable>) {
@SuppressWarnings("unchecked")
Comparable