/* * Copyright (c) OSGi Alliance (2005, 2009). All Rights Reserved. * * Licensed 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.tuscany.sca.extensibility.impl; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collection; import java.util.Dictionary; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.tuscany.sca.extensibility.ServiceDeclaration; /** * This code is derived from <a href="http://svn.apache.org/repos/asf/felix/releases/org.osgi.core-1.4.0/src/main/java/org/osgi/framework/FrameworkUtil.java">FrameworkUtil</a> * <p> * 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. * * <p> * The syntax of a filter string is the string representation of LDAP search * filters as defined in RFC 1960: <i>A String Representation of LDAP Search * Filters</i> (available at http://www.ietf.org/rfc/rfc1960.txt). It should * be noted that RFC 2254: <i>A String Representation of LDAP Search * Filters</i> (available at http://www.ietf.org/rfc/rfc2254.txt) supersedes * RFC 1960 but only adds extensible matching and is not applicable for this * API. * * <p> * The string representation of an LDAP search filter is defined by the * following grammar. It uses a prefix format. * * <pre> * <filter> ::= '(' <filtercomp> ')' * <filtercomp> ::= <and> | <or> | <not> | <item> * <and> ::= '&' <filterlist> * <or> ::= '|' <filterlist> * <not> ::= '!' <filter> * <filterlist> ::= <filter> | <filter> <filterlist> * <item> ::= <simple> | <present> | <substring> * <simple> ::= <attr> <filtertype> <value> * <filtertype> ::= <equal> | <approx> | <greater> | <less> * <equal> ::= '=' * <approx> ::= '˜=' * <greater> ::= '>=' * <less> ::= '<=' * <present> ::= <attr> '=*' * <substring> ::= <attr> '=' <initial> <any> <final> * <initial> ::= NULL | <value> * <any> ::= '*' <starval> * <starval> ::= NULL | <value> '*' <starval> * <final> ::= NULL | <value> * </pre> * * <code><attr></code> 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. * <code><value></code> is a string representing the value, or part of * one, of a key in the properties objects of the registered services. If a * <code><value></code> must contain one of the characters ' * <code>*</code>' or '<code>(</code>' or '<code>)</code>', these characters * should be escaped by preceding them with the backslash '<code>\</code>' * character. Note that although both the <code><substring></code> and * <code><present></code> productions can produce the <code>'attr=*'</code> * construct, this construct is used only to denote a presence filter. * * <p> * Examples of LDAP filters are: * * <pre> * "(cn=Babs Jensen)" * "(!(cn=Tim Howes))" * "(&(" + Constants.OBJECTCLASS + "=Person)(|(sn=Jensen)(cn=Babs J*)))" * "(o=univ*of*mich*)" * </pre> * * <p> * The approximate match (<code>~=</code>) is implementation specific but * should at least ignore case and white space differences. Optional are * codes like soundex or other smart "closeness" comparisons. * * <p> * 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: * * <blockquote> * <TABLE BORDER=0> * <TR> * <TD><b>Property Value Type </b></TD> * <TD><b>Comparison Type</b></TD> * </TR> * <TR> * <TD>String</TD> * <TD>String comparison</TD> * </TR> * <TR valign=top> * <TD>Integer, Long, Float, Double, Byte, Short, BigInteger, BigDecimal</TD> * <TD>numerical comparison</TD> * </TR> * <TR> * <TD>Character</TD> * <TD>character comparison</TD> * </TR> * <TR> * <TD>Boolean</TD> * <TD>equality comparisons only</TD> * </TR> * <TR> * <TD>[] (array)</TD> * <TD>recursively applied to values</TD> * </TR> * <TR> * <TD>Collection</TD> * <TD>recursively applied to values</TD> * </TR> * </TABLE> * Note: arrays of primitives are also supported. </blockquote> * * A filter matches a key that has multiple values if it matches at least * one of those values. For example, * * <pre> * Dictionary d = new Hashtable(); * d.put("cn", new String[] {"a", "b", "c"}); * </pre> * * d will match <code>(cn=a)</code> and also <code>(cn=b)</code> * * <p> * A filter component that references a key having an unrecognizable data * type will evaluate to <code>false</code> . */ public class LDAPFilter { /* filter operators */ 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 operation */ private final int op; /** filter attribute or null if operation AND, OR or NOT */ private final String attr; /** filter operands */ private final Object value; /* normalized filter string for Filter object */ private transient volatile String filterString; /** * Constructs a {@link LDAPFilter} object. This filter object may be * used to match a {@link ServiceReference} or a Dictionary. * * <p> * 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 LDAPFilter newInstance(String filterString) throws InvalidSyntaxException { return new Parser(filterString).parse(); } LDAPFilter(int operation, String attr, Object value) { this.op = operation; this.attr = attr; this.value = value; } /** * Filter using a <code>Dictionary</code>. This <code>Filter</code> is * executed using the specified <code>Dictionary</code>'s keys and * values. The keys are case insensitively matched with this * <code>Filter</code>. * * @param dictionary The <code>Dictionary</code> whose keys are used in * the match. * @return <code>true</code> if the <code>Dictionary</code>'s keys and * values match this filter; <code>false</code> otherwise. * @throws IllegalArgumentException If <code>dictionary</code> contains * case variants of the same key name. */ public boolean match(Dictionary dictionary) { return match0(new CaseInsensitiveDictionary(dictionary)); } public boolean match(Map map) { Properties props = new Properties(); props.putAll(map); return match0(new CaseInsensitiveDictionary(props)); } public static boolean matches(ServiceDeclaration declaration, String filter) { if (filter == null) { return true; } LDAPFilter filterImpl = newInstance(filter); return filterImpl.match(declaration.getAttributes()); } /** * Filter with case sensitivity using a <code>Dictionary</code>. This * <code>Filter</code> is executed using the specified * <code>Dictionary</code>'s keys and values. The keys are case * sensitively matched with this <code>Filter</code>. * * @param dictionary The <code>Dictionary</code> whose keys are used in * the match. * @return <code>true</code> if the <code>Dictionary</code>'s keys and * values match this filter; <code>false</code> otherwise. * @since 1.3 */ public boolean matchCase(Dictionary dictionary) { return match0(dictionary); } /** * Returns this <code>Filter</code>'s filter string. * <p> * The filter string is normalized by removing whitespace which does not * affect the meaning of the filter. * * @return This <code>Filter</code>'s filter string. */ public String toString() { String result = filterString; if (result == null) { filterString = result = normalize(); } return result; } /** * Returns this <code>Filter</code>'s normalized filter string. * <p> * The filter string is normalized by removing whitespace which does not * affect the meaning of the filter. * * @return This <code>Filter</code>'s filter string. */ private String normalize() { StringBuffer sb = new StringBuffer(); sb.append('('); switch (op) { case AND: { sb.append('&'); LDAPFilter[] filters = (LDAPFilter[])value; for (int i = 0, size = filters.length; i < size; i++) { sb.append(filters[i].normalize()); } break; } case OR: { sb.append('|'); LDAPFilter[] filters = (LDAPFilter[])value; for (int i = 0, size = filters.length; i < size; i++) { sb.append(filters[i].normalize()); } break; } case NOT: { sb.append('!'); LDAPFilter filter = (LDAPFilter)value; sb.append(filter.normalize()); break; } case SUBSTRING: { sb.append(attr); sb.append('='); String[] substrings = (String[])value; for (int i = 0, size = substrings.length; i < size; i++) { String substr = substrings[i]; 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(">="); sb.append(encodeValue((String)value)); break; } case LESS: { sb.append(attr); sb.append("<="); sb.append(encodeValue((String)value)); break; } case APPROX: { sb.append(attr); sb.append("~="); sb.append(encodeValue(approxString((String)value))); break; } case PRESENT: { sb.append(attr); sb.append("=*"); break; } } sb.append(')'); return sb.toString(); } /** * Compares this <code>Filter</code> to another <code>Filter</code>. * * <p> * This implementation returns the result of calling * <code>this.toString().equals(obj.toString()</code>. * * @param obj The object to compare against this <code>Filter</code>. * @return If the other object is a <code>Filter</code> object, then * returns the result of calling * <code>this.toString().equals(obj.toString()</code>; * <code>false</code> otherwise. */ public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof LDAPFilter)) { return false; } return this.toString().equals(obj.toString()); } /** * Returns the hashCode for this <code>Filter</code>. * * <p> * This implementation returns the result of calling * <code>this.toString().hashCode()</code>. * * @return The hashCode of this <code>Filter</code>. */ public int hashCode() { return this.toString().hashCode(); } /** * Internal match routine. Dictionary parameter must support * case-insensitive get. * * @param properties A dictionary whose keys are used in the match. * @return If the Dictionary's keys match the filter, return * <code>true</code>. Otherwise, return <code>false</code>. */ private boolean match0(Dictionary properties) { switch (op) { case AND: { LDAPFilter[] filters = (LDAPFilter[])value; for (int i = 0, size = filters.length; i < size; i++) { if (!filters[i].match0(properties)) { return false; } } return true; } case OR: { LDAPFilter[] filters = (LDAPFilter[])value; for (int i = 0, size = filters.length; i < size; i++) { if (filters[i].match0(properties)) { return true; } } return false; } case NOT: { LDAPFilter filter = (LDAPFilter)value; return !filter.match0(properties); } case SUBSTRING: case EQUAL: case GREATER: case LESS: case APPROX: { Object prop = (properties == null) ? null : properties.get(attr); return compare(op, prop, value); } case PRESENT: { Object prop = (properties == null) ? null : properties.get(attr); return prop != null; } } return false; } /** * 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) { return false; } if (value1 instanceof String) { return compare_String(operation, (String)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) { return compare_Comparable(operation, (Comparable)value1, value2); } return compare_Unknown(operation, value1, value2); // RFC 59 } private boolean compare_Collection(int operation, Collection collection, Object value2) { for (Iterator iterator = collection.iterator(); iterator.hasNext();) { if (compare(operation, iterator.next(), value2)) { return true; } } return false; } private boolean compare_ObjectArray(int operation, Object[] array, Object value2) { for (int i = 0, size = array.length; i < size; i++) { if (compare(operation, array[i], value2)) { return true; } } return false; } private boolean compare_PrimitiveArray(int operation, Class type, Object primarray, Object value2) { if (Integer.TYPE.isAssignableFrom(type)) { int[] array = (int[])primarray; for (int i = 0, size = array.length; i < size; i++) { if (compare_Integer(operation, array[i], value2)) { return true; } } return false; } if (Long.TYPE.isAssignableFrom(type)) { long[] array = (long[])primarray; for (int i = 0, size = array.length; i < size; i++) { if (compare_Long(operation, array[i], value2)) { return true; } } return false; } if (Byte.TYPE.isAssignableFrom(type)) { byte[] array = (byte[])primarray; for (int i = 0, size = array.length; i < size; i++) { if (compare_Byte(operation, array[i], value2)) { return true; } } return false; } if (Short.TYPE.isAssignableFrom(type)) { short[] array = (short[])primarray; for (int i = 0, size = array.length; i < size; i++) { if (compare_Short(operation, array[i], value2)) { return true; } } return false; } if (Character.TYPE.isAssignableFrom(type)) { char[] array = (char[])primarray; for (int i = 0, size = array.length; i < size; i++) { if (compare_Character(operation, array[i], value2)) { return true; } } return false; } if (Float.TYPE.isAssignableFrom(type)) { float[] array = (float[])primarray; for (int i = 0, size = array.length; i < size; i++) { if (compare_Float(operation, array[i], value2)) { return true; } } return false; } if (Double.TYPE.isAssignableFrom(type)) { double[] array = (double[])primarray; for (int i = 0, size = array.length; i < size; i++) { if (compare_Double(operation, array[i], value2)) { return true; } } return false; } if (Boolean.TYPE.isAssignableFrom(type)) { boolean[] array = (boolean[])primarray; for (int i = 0, size = array.length; i < size; i++) { if (compare_Boolean(operation, array[i], value2)) { return true; } } return false; } return false; } private boolean compare_String(int operation, String string, Object value2) { switch (operation) { case SUBSTRING: { String[] substrings = (String[])value2; int pos = 0; for (int i = 0, size = substrings.length; i < size; i++) { String substr = substrings[i]; if (i + 1 < size) /* if this is not that last substr */{ if (substr == null) /* * */{ String substr2 = substrings[i + 1]; if (substr2 == null) /* ** */ continue; /* ignore first star */ /* xxx */ int index = string.indexOf(substr2, pos); if (index == -1) { return false; } pos = index + substr2.length(); if (i + 2 < size) // if there are more // substrings, increment // over the string we just // matched; otherwise need // to do the last substr // check i++; } else /* xxx */{ int len = substr.length(); if (string.regionMatches(pos, substr, 0, len)) { pos += len; } else { return false; } } } else /* last substr */{ if (substr == null) /* * */{ return true; } /* xxx */ return string.endsWith(substr); } } return true; } case EQUAL: { return string.equals(value2); } case APPROX: { string = approxString(string); String string2 = approxString((String)value2); return string.equalsIgnoreCase(string2); } case GREATER: { return string.compareTo((String)value2) >= 0; } case LESS: { return string.compareTo((String)value2) <= 0; } } return false; } private boolean compare_Integer(int operation, int intval, Object value2) { if (operation == SUBSTRING) { return false; } int intval2 = Integer.parseInt(((String)value2).trim()); switch (operation) { case APPROX: case EQUAL: { return intval == intval2; } case GREATER: { return intval >= intval2; } case LESS: { return intval <= intval2; } } return false; } private boolean compare_Long(int operation, long longval, Object value2) { if (operation == SUBSTRING) { return false; } long longval2 = Long.parseLong(((String)value2).trim()); switch (operation) { case APPROX: case EQUAL: { return longval == longval2; } case GREATER: { return longval >= longval2; } case LESS: { return longval <= longval2; } } return false; } private boolean compare_Byte(int operation, byte byteval, Object value2) { if (operation == SUBSTRING) { return false; } byte byteval2 = Byte.parseByte(((String)value2).trim()); switch (operation) { case APPROX: case EQUAL: { return byteval == byteval2; } case GREATER: { return byteval >= byteval2; } case LESS: { return byteval <= byteval2; } } return false; } private boolean compare_Short(int operation, short shortval, Object value2) { if (operation == SUBSTRING) { return false; } short shortval2 = Short.parseShort(((String)value2).trim()); switch (operation) { case APPROX: case EQUAL: { return shortval == shortval2; } case GREATER: { return shortval >= shortval2; } case LESS: { return shortval <= shortval2; } } return false; } private boolean compare_Character(int operation, char charval, Object value2) { if (operation == SUBSTRING) { return false; } char charval2 = (((String)value2).trim()).charAt(0); switch (operation) { case EQUAL: { return charval == charval2; } case APPROX: { return (charval == charval2) || (Character.toUpperCase(charval) == Character.toUpperCase(charval2)) || (Character.toLowerCase(charval) == Character.toLowerCase(charval2)); } case GREATER: { return charval >= charval2; } case LESS: { return charval <= charval2; } } return false; } private boolean compare_Boolean(int operation, boolean boolval, Object value2) { if (operation == SUBSTRING) { return false; } boolean boolval2 = Boolean.valueOf(((String)value2).trim()).booleanValue(); switch (operation) { case APPROX: case EQUAL: case GREATER: case LESS: { return boolval == boolval2; } } return false; } private boolean compare_Float(int operation, float floatval, Object value2) { if (operation == SUBSTRING) { return false; } float floatval2 = Float.parseFloat(((String)value2).trim()); switch (operation) { case APPROX: case EQUAL: { return Float.compare(floatval, floatval2) == 0; } case GREATER: { return Float.compare(floatval, floatval2) >= 0; } case LESS: { return Float.compare(floatval, floatval2) <= 0; } } return false; } private boolean compare_Double(int operation, double doubleval, Object value2) { if (operation == SUBSTRING) { return false; } double doubleval2 = Double.parseDouble(((String)value2).trim()); switch (operation) { case APPROX: case EQUAL: { return Double.compare(doubleval, doubleval2) == 0; } case GREATER: { return Double.compare(doubleval, doubleval2) >= 0; } case LESS: { return Double.compare(doubleval, doubleval2) <= 0; } } return false; } private static final Class[] constructorType = new Class[] {String.class}; private boolean compare_Comparable(int operation, Comparable value1, Object value2) { if (operation == SUBSTRING) { return false; } Constructor constructor; try { constructor = value1.getClass().getConstructor(constructorType); } catch (NoSuchMethodException e) { return false; } try { if (!constructor.isAccessible()) AccessController.doPrivileged(new SetAccessibleAction(constructor)); value2 = constructor.newInstance(new Object[] {((String)value2).trim()}); } catch (IllegalAccessException e) { return false; } catch (InvocationTargetException e) { return false; } catch (InstantiationException e) { return false; } switch (operation) { case APPROX: case EQUAL: { return value1.compareTo(value2) == 0; } case GREATER: { return value1.compareTo(value2) >= 0; } case LESS: { return value1.compareTo(value2) <= 0; } } return false; } private boolean compare_Unknown(int operation, Object value1, Object value2) { if (operation == SUBSTRING) { return false; } Constructor constructor; try { constructor = value1.getClass().getConstructor(constructorType); } catch (NoSuchMethodException e) { return false; } try { if (!constructor.isAccessible()) AccessController.doPrivileged(new SetAccessibleAction(constructor)); value2 = constructor.newInstance(new Object[] {((String)value2).trim()}); } catch (IllegalAccessException e) { return false; } catch (InvocationTargetException e) { return false; } catch (InstantiationException e) { return false; } switch (operation) { case APPROX: case EQUAL: case GREATER: case LESS: { return value1.equals(value2); } } return false; } /** * Map a string for an APPROX (~=) comparison. * * This implementation removes white spaces. This is the minimum * implementation allowed by the OSGi spec. * * @param input Input string. * @return String ready for APPROX comparison. */ private static String approxString(String input) { boolean changed = false; char[] output = input.toCharArray(); int cursor = 0; for (int i = 0, length = output.length; i < length; i++) { char c = output[i]; if (Character.isWhitespace(c)) { changed = true; continue; } output[cursor] = c; cursor++; } return changed ? new String(output, 0, cursor) : input; } /** * Parser class for OSGi filter strings. This class parses the complete * filter string and builds a tree of Filter objects rooted at the * parent. */ private static class Parser { private final String filterstring; private final char[] filterChars; private int pos; Parser(String filterstring) { this.filterstring = filterstring; filterChars = filterstring.toCharArray(); pos = 0; } LDAPFilter parse() throws InvalidSyntaxException { LDAPFilter filter; try { filter = parse_filter(); } catch (ArrayIndexOutOfBoundsException e) { throw new InvalidSyntaxException("Filter ended abruptly", filterstring); } if (pos != filterChars.length) { throw new InvalidSyntaxException("Extraneous trailing characters: " + filterstring.substring(pos), filterstring); } return filter; } private LDAPFilter parse_filter() throws InvalidSyntaxException { LDAPFilter filter; skipWhiteSpace(); if (filterChars[pos] != '(') { throw new InvalidSyntaxException("Missing '(': " + filterstring.substring(pos), filterstring); } pos++; filter = parse_filtercomp(); skipWhiteSpace(); if (filterChars[pos] != ')') { throw new InvalidSyntaxException("Missing ')': " + filterstring.substring(pos), filterstring); } pos++; skipWhiteSpace(); return filter; } private LDAPFilter parse_filtercomp() throws InvalidSyntaxException { skipWhiteSpace(); char c = filterChars[pos]; switch (c) { case '&': { pos++; return parse_and(); } case '|': { pos++; return parse_or(); } case '!': { pos++; return parse_not(); } } return parse_item(); } private LDAPFilter parse_and() throws InvalidSyntaxException { skipWhiteSpace(); if (filterChars[pos] != '(') { throw new InvalidSyntaxException("Missing '(': " + filterstring.substring(pos), filterstring); } List operands = new ArrayList(10); while (filterChars[pos] == '(') { LDAPFilter child = parse_filter(); operands.add(child); } return new LDAPFilter(LDAPFilter.AND, null, operands.toArray(new LDAPFilter[operands.size()])); } private LDAPFilter parse_or() throws InvalidSyntaxException { skipWhiteSpace(); if (filterChars[pos] != '(') { throw new InvalidSyntaxException("Missing '(': " + filterstring.substring(pos), filterstring); } List operands = new ArrayList(10); while (filterChars[pos] == '(') { LDAPFilter child = parse_filter(); operands.add(child); } return new LDAPFilter(LDAPFilter.OR, null, operands.toArray(new LDAPFilter[operands.size()])); } private LDAPFilter parse_not() throws InvalidSyntaxException { skipWhiteSpace(); if (filterChars[pos] != '(') { throw new InvalidSyntaxException("Missing '(': " + filterstring.substring(pos), filterstring); } LDAPFilter child = parse_filter(); return new LDAPFilter(LDAPFilter.NOT, null, child); } private LDAPFilter parse_item() throws InvalidSyntaxException { String attr = parse_attr(); skipWhiteSpace(); switch (filterChars[pos]) { case '~': { if (filterChars[pos + 1] == '=') { pos += 2; return new LDAPFilter(LDAPFilter.APPROX, attr, parse_value()); } break; } case '>': { if (filterChars[pos + 1] == '=') { pos += 2; return new LDAPFilter(LDAPFilter.GREATER, attr, parse_value()); } break; } case '<': { if (filterChars[pos + 1] == '=') { pos += 2; return new LDAPFilter(LDAPFilter.LESS, attr, parse_value()); } break; } case '=': { if (filterChars[pos + 1] == '*') { int oldpos = pos; pos += 2; skipWhiteSpace(); if (filterChars[pos] == ')') { return new LDAPFilter(LDAPFilter.PRESENT, attr, null); } pos = oldpos; } pos++; Object string = parse_substring(); if (string instanceof String) { return new LDAPFilter(LDAPFilter.EQUAL, attr, string); } return new LDAPFilter(LDAPFilter.SUBSTRING, attr, string); } } throw new InvalidSyntaxException("Invalid operator: " + filterstring.substring(pos), filterstring); } private String parse_attr() throws InvalidSyntaxException { skipWhiteSpace(); int begin = pos; int end = pos; char c = filterChars[pos]; while (c != '~' && c != '<' && c != '>' && c != '=' && c != '(' && c != ')') { pos++; if (!Character.isWhitespace(c)) { end = pos; } c = filterChars[pos]; } int length = end - begin; if (length == 0) { throw new InvalidSyntaxException("Missing attr: " + filterstring.substring(pos), filterstring); } return new String(filterChars, begin, length); } private String parse_value() throws InvalidSyntaxException { StringBuffer sb = new StringBuffer(filterChars.length - pos); parseloop: while (true) { char c = filterChars[pos]; switch (c) { case ')': { break parseloop; } case '(': { throw new InvalidSyntaxException("Invalid value: " + filterstring.substring(pos), filterstring); } case '\\': { pos++; c = filterChars[pos]; /* fall through into default */ } default: { sb.append(c); pos++; break; } } } if (sb.length() == 0) { throw new InvalidSyntaxException("Missing value: " + filterstring.substring(pos), filterstring); } return sb.toString(); } private Object parse_substring() throws InvalidSyntaxException { StringBuffer sb = new StringBuffer(filterChars.length - pos); List operands = new ArrayList(10); parseloop: while (true) { char c = filterChars[pos]; switch (c) { case ')': { if (sb.length() > 0) { operands.add(sb.toString()); } break parseloop; } case '(': { throw new InvalidSyntaxException("Invalid value: " + filterstring.substring(pos), filterstring); } case '*': { if (sb.length() > 0) { operands.add(sb.toString()); } sb.setLength(0); operands.add(null); pos++; break; } case '\\': { pos++; c = filterChars[pos]; /* fall through into default */ } default: { sb.append(c); pos++; break; } } } int size = operands.size(); if (size == 0) { throw new InvalidSyntaxException("Missing value: " + filterstring.substring(pos), filterstring); } if (size == 1) { Object single = operands.get(0); if (single != null) { return single; } } return operands.toArray(new String[size]); } private void skipWhiteSpace() { for (int length = filterChars.length; (pos < length) && Character.isWhitespace(filterChars[pos]);) { pos++; } } } /** * This Dictionary is used for case-insensitive key lookup during filter * evaluation. This Dictionary implementation only supports the get * operation using a String key as no other operations are used by the * Filter implementation. */ static class CaseInsensitiveDictionary extends Dictionary { private final Dictionary dictionary; private final String[] keys; /** * Create a case insensitive dictionary from the specified dictionary. * * @param dictionary * @throws IllegalArgumentException If <code>dictionary</code> contains * case variants of the same key name. */ CaseInsensitiveDictionary(Dictionary dictionary) { if (dictionary == null) { this.dictionary = null; this.keys = new String[0]; return; } this.dictionary = dictionary; List keyList = new ArrayList(dictionary.size()); for (Enumeration e = dictionary.keys(); e.hasMoreElements();) { Object k = e.nextElement(); if (k instanceof String) { String key = (String)k; for (Iterator i = keyList.iterator(); i.hasNext();) { if (key.equalsIgnoreCase((String)i.next())) { throw new IllegalArgumentException(); } } keyList.add(key); } } this.keys = (String[])keyList.toArray(new String[keyList.size()]); } public Object get(Object o) { String k = (String)o; for (int i = 0, length = keys.length; i < length; i++) { String key = keys[i]; if (key.equalsIgnoreCase(k)) { return dictionary.get(key); } } return null; } public boolean isEmpty() { throw new UnsupportedOperationException(); } public Enumeration keys() { throw new UnsupportedOperationException(); } public Enumeration elements() { throw new UnsupportedOperationException(); } public Object put(Object key, Object value) { throw new UnsupportedOperationException(); } public Object remove(Object key) { throw new UnsupportedOperationException(); } public int size() { throw new UnsupportedOperationException(); } } static class SetAccessibleAction implements PrivilegedAction { private final AccessibleObject accessible; SetAccessibleAction(AccessibleObject accessible) { this.accessible = accessible; } public Object run() { accessible.setAccessible(true); return null; } } }