FELIX-2212: Create a new Utils subproject to hold shared classes
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@924669 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/utils/pom.xml b/utils/pom.xml
new file mode 100644
index 0000000..1a2c3ea
--- /dev/null
+++ b/utils/pom.xml
@@ -0,0 +1,86 @@
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix-parent</artifactId>
+ <version>1.2.0</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>bundle</packaging>
+ <name>Apache Felix Utils</name>
+ <description>Utility classes for OSGi.</description>
+ <version>0.1.0-SNAPSHOT</version>
+ <artifactId>org.apache.felix.utils</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>4.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>4.1.0</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.4</source>
+ <target>1.4</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>2.0.0</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ org.apache.felix.utils*;version=${project.version};-noimport:=true
+ </Export-Package>
+ <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
+ <Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
+ <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>rat-maven-plugin</artifactId>
+ <configuration>
+ <excludeSubProjects>false</excludeSubProjects>
+ <useEclipseDefaultExcludes>true</useEclipseDefaultExcludes>
+ <useMavenDefaultExcludes>true</useMavenDefaultExcludes>
+ <excludes>
+ <param>doc/*</param>
+ <param>maven-eclipse.xml</param>
+ <param>.checkstyle</param>
+ <param>.externalToolBuilders/*</param>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/utils/src/main/java/org/apache/felix/utils/collections/IteratorToEnumeration.java b/utils/src/main/java/org/apache/felix/utils/collections/IteratorToEnumeration.java
new file mode 100644
index 0000000..9f03e81
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/collections/IteratorToEnumeration.java
@@ -0,0 +1,50 @@
+/*
+ * 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.felix.utils.collections;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+
+public class IteratorToEnumeration implements Enumeration
+{
+ private final Iterator iter;
+
+ public IteratorToEnumeration(Iterator iter)
+ {
+ this.iter = iter;
+ }
+
+ public boolean hasMoreElements()
+ {
+ if (iter == null)
+ {
+ return false;
+ }
+ return iter.hasNext();
+ }
+
+ public Object nextElement()
+ {
+ if (iter == null)
+ {
+ return null;
+ }
+ return iter.next();
+ }
+}
diff --git a/utils/src/main/java/org/apache/felix/utils/collections/MapToDictionary.java b/utils/src/main/java/org/apache/felix/utils/collections/MapToDictionary.java
new file mode 100644
index 0000000..d192b4f
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/collections/MapToDictionary.java
@@ -0,0 +1,100 @@
+/*
+ * 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.felix.utils.collections;
+
+
+import java.util.*;
+
+
+/**
+ * This is a simple class that implements a <tt>Dictionary</tt>
+ * from a <tt>Map</tt>. The resulting dictionary is immutatable.
+**/
+public class MapToDictionary extends Dictionary
+{
+ /**
+ * Map source.
+ **/
+ private Map map = null;
+
+ public MapToDictionary(Map map)
+ {
+ this.map = map;
+ }
+
+ public void setSourceMap(Map map)
+ {
+ this.map = map;
+ }
+
+ public Enumeration elements()
+ {
+ if (map == null)
+ {
+ return null;
+ }
+ return new IteratorToEnumeration(map.values().iterator());
+ }
+
+ public Object get(Object key)
+ {
+ if (map == null)
+ {
+ return null;
+ }
+ return map.get(key);
+ }
+
+ public boolean isEmpty()
+ {
+ if (map == null)
+ {
+ return true;
+ }
+ return map.isEmpty();
+ }
+
+ public Enumeration keys()
+ {
+ if (map == null)
+ {
+ return null;
+ }
+ return new IteratorToEnumeration(map.keySet().iterator());
+ }
+
+ public Object put(Object key, Object value)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object remove(Object key)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size()
+ {
+ if (map == null)
+ {
+ return 0;
+ }
+ return map.size();
+ }
+}
\ No newline at end of file
diff --git a/utils/src/main/java/org/apache/felix/utils/filter/FilterImpl.java b/utils/src/main/java/org/apache/felix/utils/filter/FilterImpl.java
new file mode 100644
index 0000000..fc88430
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/filter/FilterImpl.java
@@ -0,0 +1,1643 @@
+/*
+ * 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.felix.utils.filter;
+
+
+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.*;
+
+import org.apache.felix.utils.version.VersionTable;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+
+public class FilterImpl implements Filter {
+
+ /* 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;
+ private static final int SUBSET = 10;
+ private static final int SUPERSET = 11;
+
+ /** 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;
+ /** optim in case of version */
+ private final Object converted;
+
+ /* normalized filter string for Filter object */
+ private transient volatile String filterString;
+
+ /**
+ * Constructs a {@link FilterImpl} object. This filter object may be
+ * used to match a {@link org.osgi.framework.ServiceReference} or a Dictionary.
+ *
+ * <p>
+ * If the filter cannot be parsed, an {@link org.osgi.framework.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 ignoreCase)
+ throws InvalidSyntaxException {
+ return new Parser(filterString, ignoreCase).parse();
+ }
+
+ FilterImpl(int operation, String attr, Object value) {
+ this.op = operation;
+ this.attr = attr;
+ this.value = value;
+ Object conv = null;
+ try {
+ if (op == SUBSET || op == SUPERSET)
+ {
+ conv = getSet(value);
+ }
+ else if ("version".equalsIgnoreCase(attr))
+ {
+ if (value instanceof String) {
+ conv = VersionTable.getVersion((String) value);
+ } else if (value instanceof Version) {
+ conv = (Version) value;
+ }
+ }
+ } catch (Throwable t) {
+ // Ignore any conversion issue
+ }
+ converted = conv;
+ }
+
+
+ /**
+ * Filter using a service's properties.
+ * <p>
+ * This <code>Filter</code> is executed using the keys and values of the
+ * referenced service's properties. The keys are case insensitively
+ * matched with this <code>Filter</code>.
+ *
+ * @param reference The reference to the service whose properties are
+ * used in the match.
+ * @return <code>true</code> if the service's properties match this
+ * <code>Filter</code>; <code>false</code> otherwise.
+ */
+ public boolean match(ServiceReference reference) {
+ return match0(new ServiceReferenceDictionary(reference));
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Filter using a <code>Map</code>. This <code>Filter</code> is
+ * executed using the specified <code>Map</code>'s keys and
+ * values. The keys are case insensitively matched with this
+ * <code>Filter</code>.
+ *
+ * @param map The <code>Map</code> whose keys are used in
+ * the match.
+ * @return <code>true</code> if the <code>Map</code>'s keys and
+ * values match this filter; <code>false</code> otherwise.
+ * @throws IllegalArgumentException If <code>map</code> contains
+ * case variants of the same key name.
+ */
+ public boolean matchCase(Map map) {
+ return match0(map);
+ }
+
+ /**
+ * 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('&');
+
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (int i = 0, size = filters.length; i < size; i++) {
+ sb.append(filters[i].normalize());
+ }
+
+ break;
+ }
+
+ case OR : {
+ sb.append('|');
+
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (int i = 0, size = filters.length; i < size; i++) {
+ sb.append(filters[i].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 (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;
+ }
+ case SUBSET : {
+ sb.append(attr);
+ sb.append("<*");
+ sb.append(encodeValue(approxString((String) value)));
+
+ break;
+ }
+ case SUPERSET : {
+ sb.append(attr);
+ sb.append("*>");
+ sb.append(encodeValue(approxString((String) value)));
+
+ 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 FilterImpl)) {
+ 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 : {
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (int i = 0, size = filters.length; i < size; i++) {
+ if (!filters[i].match0(properties)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ case OR : {
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (int i = 0, size = filters.length; i < size; i++) {
+ if (filters[i].match0(properties)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ case NOT : {
+ FilterImpl filter = (FilterImpl) value;
+
+ return !filter.match0(properties);
+ }
+
+ case SUBSTRING :
+ case EQUAL :
+ case GREATER :
+ case LESS :
+ case APPROX :
+ case SUBSET :
+ case SUPERSET : {
+ 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;
+ }
+
+ private boolean match0(Map properties) {
+ switch (op) {
+ case AND : {
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (int i = 0, size = filters.length; i < size; i++) {
+ if (!filters[i].match0(properties)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ case OR : {
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (int i = 0, size = filters.length; i < size; i++) {
+ if (filters[i].match0(properties)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ case NOT : {
+ FilterImpl filter = (FilterImpl) value;
+
+ return !filter.match0(properties);
+ }
+
+ case SUBSTRING :
+ case EQUAL :
+ case GREATER :
+ case LESS :
+ case APPROX :
+ case SUBSET :
+ case SUPERSET : {
+ 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 Collection getSet(Object value)
+ {
+ Collection s;
+ if (value instanceof Set)
+ {
+ s = (Set) value;
+ }
+ else if (value instanceof Collection)
+ {
+ s = (Collection) value;
+ if (s.size() > 1) {
+ s = new HashSet(s);
+ }
+ }
+ else if (value != null)
+ {
+ String v = value.toString();
+ if (v.indexOf(',') < 0)
+ {
+ s = Collections.singleton(v);
+ }
+ else {
+ StringTokenizer st = new StringTokenizer(value.toString(), ",");
+ s = new HashSet();
+ while (st.hasMoreTokens())
+ {
+ s.add(st.nextToken().trim());
+ }
+ }
+ }
+ else
+ {
+ s = Collections.emptySet();
+ }
+ return s;
+ }
+
+ private boolean compare(int operation, Object value1, Object value2) {
+ if (op == SUPERSET || op == SUBSET)
+ {
+ Collection s1 = getSet(value1);
+ Collection s2 = converted instanceof Collection ? (Collection) converted : getSet(value2);
+ if (op == SUPERSET)
+ {
+ return s1.containsAll(s2);
+ }
+ else
+ {
+ return s2.containsAll(s1);
+ }
+ }
+
+ 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 Version) {
+ if (converted != null) {
+ switch (operation) {
+ case APPROX :
+ case EQUAL : {
+ return ((Version) value1).compareTo(converted) == 0;
+ }
+ case GREATER : {
+ return ((Version) value1).compareTo(converted) >= 0;
+ }
+ case LESS : {
+ return ((Version) value1).compareTo(converted) <= 0;
+ }
+ }
+ } else {
+ return compare_Comparable(operation, (Version) 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) {
+ if (op == SUBSET || op == SUPERSET)
+ {
+ Set set = new HashSet();
+ if (value2 != null)
+ {
+ StringTokenizer st = new StringTokenizer(value2.toString(), ",");
+ while (st.hasMoreTokens())
+ {
+ set.add(st.nextToken().trim());
+ }
+ }
+ if (op == SUBSET)
+ {
+ return set.containsAll(collection);
+ }
+ else
+ {
+ return collection.containsAll(set);
+ }
+ }
+ 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;
+ try {
+ intval2 = Integer.parseInt(((String) value2).trim());
+ }
+ catch (IllegalArgumentException e) {
+ return false;
+ }
+ 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;
+ try {
+ longval2 = Long.parseLong(((String) value2).trim());
+ }
+ catch (IllegalArgumentException e) {
+ return false;
+ }
+
+ 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;
+ try {
+ byteval2 = Byte.parseByte(((String) value2).trim());
+ }
+ catch (IllegalArgumentException e) {
+ return false;
+ }
+
+ 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;
+ try {
+ shortval2 = Short.parseShort(((String) value2).trim());
+ }
+ catch (IllegalArgumentException e) {
+ return false;
+ }
+
+ 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;
+ try {
+ charval2 = ((String) value2).charAt(0);
+ }
+ catch (IndexOutOfBoundsException e) {
+ return false;
+ }
+
+ 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;
+ try {
+ floatval2 = Float.parseFloat(((String) value2).trim());
+ }
+ catch (IllegalArgumentException e) {
+ return false;
+ }
+
+ 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;
+ try {
+ doubleval2 = Double.parseDouble(((String) value2).trim());
+ }
+ catch (IllegalArgumentException e) {
+ return false;
+ }
+
+ 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 boolean ignoreCase;
+ private final char[] filterChars;
+ private int pos;
+
+ Parser(String filterstring, boolean ignoreCase) {
+ this.filterstring = filterstring;
+ this.ignoreCase = ignoreCase;
+ filterChars = filterstring.toCharArray();
+ pos = 0;
+ }
+
+ FilterImpl parse() throws InvalidSyntaxException {
+ FilterImpl 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 FilterImpl parse_filter() throws InvalidSyntaxException {
+ FilterImpl 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 FilterImpl 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 FilterImpl parse_and() throws InvalidSyntaxException {
+ int lookahead = pos;
+ skipWhiteSpace();
+
+ if (filterChars[pos] != '(') {
+ pos = lookahead - 1;
+ return parse_item();
+ }
+
+ List operands = new ArrayList(10);
+
+ while (filterChars[pos] == '(') {
+ FilterImpl child = parse_filter();
+ operands.add(child);
+ }
+
+ return new FilterImpl(FilterImpl.AND, null, operands
+ .toArray(new FilterImpl[operands.size()]));
+ }
+
+ private FilterImpl parse_or() throws InvalidSyntaxException {
+ int lookahead = pos;
+ skipWhiteSpace();
+
+ if (filterChars[pos] != '(') {
+ pos = lookahead - 1;
+ return parse_item();
+ }
+
+ List operands = new ArrayList(10);
+
+ while (filterChars[pos] == '(') {
+ FilterImpl child = parse_filter();
+ operands.add(child);
+ }
+
+ return new FilterImpl(FilterImpl.OR, null, operands
+ .toArray(new FilterImpl[operands.size()]));
+ }
+
+ private FilterImpl parse_not() throws InvalidSyntaxException {
+ int lookahead = pos;
+ skipWhiteSpace();
+
+ if (filterChars[pos] != '(') {
+ pos = lookahead - 1;
+ return parse_item();
+ }
+
+ FilterImpl child = parse_filter();
+
+ return new FilterImpl(FilterImpl.NOT, null, child);
+ }
+
+ private FilterImpl parse_item() throws InvalidSyntaxException {
+ String attr = parse_attr();
+
+ skipWhiteSpace();
+
+ switch (filterChars[pos]) {
+ case '*': {
+ if (filterChars[pos + 1] == '>') {
+ pos += 2;
+ return new FilterImpl(FilterImpl.SUPERSET, attr,
+ parse_value());
+ }
+ break;
+ }
+ case '~' : {
+ if (filterChars[pos + 1] == '=') {
+ pos += 2;
+ return new FilterImpl(FilterImpl.APPROX, attr,
+ parse_value());
+ }
+ break;
+ }
+ case '>' : {
+ if (filterChars[pos + 1] == '=') {
+ pos += 2;
+ return new FilterImpl(FilterImpl.GREATER, attr,
+ parse_value());
+ }
+ break;
+ }
+ case '<' : {
+ if (filterChars[pos + 1] == '=') {
+ pos += 2;
+ return new FilterImpl(FilterImpl.LESS, attr,
+ parse_value());
+ }
+ if (filterChars[pos + 1] == '*') {
+ pos += 2;
+ return new FilterImpl(FilterImpl.SUBSET, attr,
+ parse_value());
+ }
+ break;
+ }
+ case '=' : {
+ if (filterChars[pos + 1] == '*') {
+ int oldpos = pos;
+ pos += 2;
+ skipWhiteSpace();
+ if (filterChars[pos] == ')') {
+ return new FilterImpl(FilterImpl.PRESENT, attr,
+ null);
+ }
+ pos = oldpos;
+ }
+
+ pos++;
+ Object string = parse_substring();
+
+ if (string instanceof String) {
+ return new FilterImpl(FilterImpl.EQUAL, attr,
+ string);
+ }
+ return new FilterImpl(FilterImpl.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 != ')') {
+
+ if (c == '<' && filterChars[pos+1] == '*') {
+ break;
+ }
+ if (c == '*' && filterChars[pos+1] == '>') {
+ break;
+ }
+ 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);
+ }
+
+ String str = new String(filterChars, begin, length);
+ if (ignoreCase)
+ {
+ str = str.toLowerCase();
+ }
+ return str;
+ }
+
+ 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) {
+ return "";
+ }
+
+ 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.
+ */
+ private 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();
+ }
+ }
+
+ private static class SetAccessibleAction implements PrivilegedAction {
+ private final AccessibleObject accessible;
+
+ SetAccessibleAction(AccessibleObject accessible) {
+ this.accessible = accessible;
+ }
+
+ public Object run() {
+ accessible.setAccessible(true);
+ return null;
+ }
+ }
+
+ /**
+ * This Dictionary is used for key lookup from a ServiceReference 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.
+ */
+ private static class ServiceReferenceDictionary extends Dictionary {
+ private final ServiceReference reference;
+
+ ServiceReferenceDictionary(ServiceReference reference) {
+ this.reference = reference;
+ }
+
+ public Object get(Object key) {
+ if (reference == null) {
+ return null;
+ }
+ return reference.getProperty((String) key);
+ }
+
+ 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();
+ }
+ }
+
+}
diff --git a/utils/src/main/java/org/apache/felix/utils/log/Logger.java b/utils/src/main/java/org/apache/felix/utils/log/Logger.java
new file mode 100644
index 0000000..118beff
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/log/Logger.java
@@ -0,0 +1,143 @@
+/*
+ * 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.felix.utils.log;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+
+import java.io.PrintStream;
+
+/**
+ * Internal logger to be used in order to avoid a mandatory dependency on OSGi LogService.
+ * It first tries to log to a log service implementation if there is one available and then fallback to System out/err
+ * in case there is no log service available.
+ */
+public class Logger
+{
+ public static final int LOG_ERROR = 1;
+ public static final int LOG_WARNING = 2;
+ public static final int LOG_INFO = 3;
+ public static final int LOG_DEBUG = 4;
+
+ /**
+ * Bundle context.
+ */
+ private final BundleContext m_context;
+ private boolean m_isLogClassPresent;
+
+ /**
+ * Constructor.
+ *
+ * @param context bundle context
+ */
+ public Logger(BundleContext context)
+ {
+ m_context = context;
+ try
+ {
+ org.osgi.service.log.LogService.class.getName();
+ m_isLogClassPresent = true;
+ }
+ catch (NoClassDefFoundError ex)
+ {
+ m_isLogClassPresent = false;
+ }
+ }
+
+ /**
+ * @see LogService#log(int, String)
+ */
+ public void log(int level, String message)
+ {
+ log(level, message, null);
+ }
+
+ /**
+ * @see LogService#log(int, String, Throwable)
+ */
+ public void log(int level, String message, Throwable exception)
+ {
+ if (!m_isLogClassPresent || !_log(level, message, exception))
+ {
+ final PrintStream stream = getStream(level);
+ stream.println(message);
+ if (exception != null)
+ {
+ exception.printStackTrace(stream);
+ }
+
+ }
+ }
+
+ /**
+ * Lookup the OSGi LogService and if available use it.
+ */
+ private boolean _log(int level, String message, Throwable exception)
+ {
+ try
+ {
+ ServiceReference reference = null;
+ reference = m_context.getServiceReference(LogService.class.getName());
+ if (reference != null)
+ {
+ final LogService logService = (LogService) m_context.getService(reference);
+ if (logService != null)
+ {
+ logService.log(level, message, exception);
+ m_context.ungetService(reference);
+ return true;
+ }
+ }
+ }
+ catch (NoClassDefFoundError e)
+ {
+ //ignore
+ }
+ return false;
+ }
+
+ /**
+ * Return the standard print streams to use depending on log level.
+ *
+ * @param level log level
+ * @return print stream corresponding to log level
+ */
+ private PrintStream getStream(int level)
+ {
+ switch (level)
+ {
+ case LOG_ERROR:
+ System.err.print("ERROR: ");
+ return System.err;
+ case LOG_WARNING:
+ System.err.print("WARNING: ");
+ return System.err;
+ case LOG_INFO:
+ System.out.print("INFO: ");
+ return System.out;
+ case LOG_DEBUG:
+ System.out.print("DEBUG: ");
+ return System.out;
+ default:
+ System.out.print("UNKNOWN: ");
+ return System.out;
+ }
+ }
+}
\ No newline at end of file
diff --git a/utils/src/main/java/org/apache/felix/utils/manifest/Attribute.java b/utils/src/main/java/org/apache/felix/utils/manifest/Attribute.java
new file mode 100644
index 0000000..374c3a4
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/manifest/Attribute.java
@@ -0,0 +1,43 @@
+/*
+ * 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.felix.utils.manifest;
+
+public class Attribute
+{
+
+ private final String name;
+ private final String value;
+
+ public Attribute(String name, String value)
+ {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public String getValue()
+ {
+ return value;
+ }
+
+}
diff --git a/utils/src/main/java/org/apache/felix/utils/manifest/Clause.java b/utils/src/main/java/org/apache/felix/utils/manifest/Clause.java
new file mode 100644
index 0000000..8f4445e
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/manifest/Clause.java
@@ -0,0 +1,74 @@
+/*
+ * 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.felix.utils.manifest;
+
+public class Clause
+{
+
+ private final String name;
+ private final Directive[] directives;
+ private final Attribute[] attributes;
+
+ public Clause(String name, Directive[] directives, Attribute[] attributes)
+ {
+ this.name = name;
+ this.directives = directives;
+ this.attributes = attributes;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public Directive[] getDirectives()
+ {
+ return directives;
+ }
+
+ public Attribute[] getAttributes()
+ {
+ return attributes;
+ }
+
+ public String getDirective(String name)
+ {
+ for (int i = 0; i < directives.length; i++)
+ {
+ if (name.equals(directives[i].getName()))
+ {
+ return directives[i].getValue();
+ }
+ }
+ return null;
+ }
+
+ public String getAttribute(String name)
+ {
+ for (int i = 0; i < attributes.length; i++)
+ {
+ if (name.equals(attributes[i].getName()))
+ {
+ return attributes[i].getValue();
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/utils/src/main/java/org/apache/felix/utils/manifest/Directive.java b/utils/src/main/java/org/apache/felix/utils/manifest/Directive.java
new file mode 100644
index 0000000..a0251b0
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/manifest/Directive.java
@@ -0,0 +1,43 @@
+/*
+ * 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.felix.utils.manifest;
+
+public class Directive
+{
+
+ private final String name;
+ private final String value;
+
+ public Directive(String name, String value)
+ {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public String getValue()
+ {
+ return value;
+ }
+
+}
diff --git a/utils/src/main/java/org/apache/felix/utils/manifest/Parser.java b/utils/src/main/java/org/apache/felix/utils/manifest/Parser.java
new file mode 100644
index 0000000..48a8da1
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/manifest/Parser.java
@@ -0,0 +1,212 @@
+/*
+ * 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.felix.utils.manifest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class Parser
+{
+
+ private Parser() { }
+
+
+ public static Clause[] parseHeader(String header) throws IllegalArgumentException
+ {
+ Clause[] clauses = null;
+ if (header != null)
+ {
+ if (header.length() == 0)
+ {
+ throw new IllegalArgumentException("The header cannot be an empty string.");
+ }
+ String[] ss = parseDelimitedString(header, ",");
+ clauses = parseClauses(ss);
+ }
+ return (clauses == null) ? new Clause[0] : clauses;
+ }
+
+ public static Clause[] parseClauses(String[] ss) throws IllegalArgumentException
+ {
+ if (ss == null)
+ {
+ return null;
+ }
+
+ List completeList = new ArrayList();
+ for (int ssIdx = 0; ssIdx < ss.length; ssIdx++)
+ {
+ // Break string into semi-colon delimited pieces.
+ String[] pieces = parseDelimitedString(ss[ssIdx], ";");
+
+ // Count the number of different clauses; clauses
+ // will not have an '=' in their string. This assumes
+ // that clauses come first, before directives and
+ // attributes.
+ int pathCount = 0;
+ for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++)
+ {
+ if (pieces[pieceIdx].indexOf('=') >= 0)
+ {
+ break;
+ }
+ pathCount++;
+ }
+
+ // Error if no packages were specified.
+ if (pathCount == 0)
+ {
+ throw new IllegalArgumentException("No path specified on clause: " + ss[ssIdx]);
+ }
+
+ // Parse the directives/attributes.
+ Directive[] dirs = new Directive[pieces.length - pathCount];
+ Attribute[] attrs = new Attribute[pieces.length - pathCount];
+ int dirCount = 0, attrCount = 0;
+ int idx = -1;
+ String sep = null;
+ for (int pieceIdx = pathCount; pieceIdx < pieces.length; pieceIdx++)
+ {
+ // Check if it is a directive.
+ if ((idx = pieces[pieceIdx].indexOf(":=")) >= 0)
+ {
+ sep = ":=";
+ }
+ // Check if it is an attribute.
+ else if ((idx = pieces[pieceIdx].indexOf("=")) >= 0)
+ {
+ sep = "=";
+ }
+ // It is an error.
+ else
+ {
+ throw new IllegalArgumentException("Not a directive/attribute: " + ss[ssIdx]);
+ }
+
+ String key = pieces[pieceIdx].substring(0, idx).trim();
+ String value = pieces[pieceIdx].substring(idx + sep.length()).trim();
+
+ // Remove quotes, if value is quoted.
+ if (value.startsWith("\"") && value.endsWith("\""))
+ {
+ value = value.substring(1, value.length() - 1);
+ }
+
+ // Save the directive/attribute in the appropriate array.
+ if (sep.equals(":="))
+ {
+ dirs[dirCount++] = new Directive(key, value);
+ }
+ else
+ {
+ attrs[attrCount++] = new Attribute(key, value);
+ }
+ }
+
+ // Shrink directive array.
+ Directive[] dirsFinal = new Directive[dirCount];
+ System.arraycopy(dirs, 0, dirsFinal, 0, dirCount);
+ // Shrink attribute array.
+ Attribute[] attrsFinal = new Attribute[attrCount];
+ System.arraycopy(attrs, 0, attrsFinal, 0, attrCount);
+
+ // Create package attributes for each package and
+ // set directives/attributes. Add each package to
+ // completel list of packages.
+ Clause[] pkgs = new Clause[pathCount];
+ for (int pkgIdx = 0; pkgIdx < pathCount; pkgIdx++)
+ {
+ pkgs[pkgIdx] = new Clause(pieces[pkgIdx], dirsFinal, attrsFinal);
+ completeList.add(pkgs[pkgIdx]);
+ }
+ }
+
+ Clause[] pkgs = (Clause[]) completeList.toArray(new Clause[completeList.size()]);
+ return pkgs;
+ }
+
+ /**
+ * Parses delimited string and returns an array containing the tokens. This
+ * parser obeys quotes, so the delimiter character will be ignored if it is
+ * inside of a quote. This method assumes that the quote character is not
+ * included in the set of delimiter characters.
+ * @param value the delimited string to parse.
+ * @param delim the characters delimiting the tokens.
+ * @return an array of string tokens or null if there were no tokens.
+ **/
+ public static String[] parseDelimitedString(String value, String delim)
+ {
+ if (value == null)
+ {
+ value = "";
+ }
+
+ List list = new ArrayList();
+
+ int CHAR = 1;
+ int DELIMITER = 2;
+ int STARTQUOTE = 4;
+ int ENDQUOTE = 8;
+
+ StringBuffer sb = new StringBuffer();
+
+ int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+ for (int i = 0; i < value.length(); i++)
+ {
+ char c = value.charAt(i);
+
+ boolean isDelimiter = (delim.indexOf(c) >= 0);
+ boolean isQuote = (c == '"');
+
+ if (isDelimiter && ((expecting & DELIMITER) > 0))
+ {
+ list.add(sb.toString().trim());
+ sb.delete(0, sb.length());
+ expecting = (CHAR | DELIMITER | STARTQUOTE);
+ }
+ else if (isQuote && ((expecting & STARTQUOTE) > 0))
+ {
+ sb.append(c);
+ expecting = CHAR | ENDQUOTE;
+ }
+ else if (isQuote && ((expecting & ENDQUOTE) > 0))
+ {
+ sb.append(c);
+ expecting = (CHAR | STARTQUOTE | DELIMITER);
+ }
+ else if ((expecting & CHAR) > 0)
+ {
+ sb.append(c);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid delimited string: " + value);
+ }
+ }
+
+ if (sb.length() > 0)
+ {
+ list.add(sb.toString().trim());
+ }
+
+ return (String[]) list.toArray(new String[list.size()]);
+ }
+
+}
diff --git a/utils/src/main/java/org/apache/felix/utils/version/VersionCleaner.java b/utils/src/main/java/org/apache/felix/utils/version/VersionCleaner.java
new file mode 100644
index 0000000..4b2078b
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/version/VersionCleaner.java
@@ -0,0 +1,111 @@
+/*
+ * 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.felix.utils.version;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class VersionCleaner {
+
+ private VersionCleaner() { }
+
+
+ private static final Pattern FUZZY_VERSION = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?", Pattern.DOTALL);
+
+ /**
+ * Clean up version parameters. Other builders use more fuzzy definitions of
+ * the version syntax. This method cleans up such a version to match an OSGi
+ * version.
+ *
+ * @param version
+ * @return
+ */
+ public static String clean(String version)
+ {
+ if (version == null || version.length() == 0)
+ {
+ return "0.0.0";
+ }
+ StringBuffer result = new StringBuffer();
+ Matcher m = FUZZY_VERSION.matcher(version);
+ if (m.matches())
+ {
+ String major = m.group(1);
+ String minor = m.group(3);
+ String micro = m.group(5);
+ String qualifier = m.group(7);
+
+ if (major != null)
+ {
+ result.append(major);
+ if (minor != null)
+ {
+ result.append(".");
+ result.append(minor);
+ if (micro != null)
+ {
+ result.append(".");
+ result.append(micro);
+ if (qualifier != null)
+ {
+ result.append(".");
+ cleanupModifier(result, qualifier);
+ }
+ }
+ else if (qualifier != null)
+ {
+ result.append(".0.");
+ cleanupModifier(result, qualifier);
+ }
+ else
+ {
+ result.append(".0");
+ }
+ }
+ else if (qualifier != null)
+ {
+ result.append(".0.0.");
+ cleanupModifier(result, qualifier);
+ }
+ else
+ {
+ result.append(".0.0");
+ }
+ }
+ }
+ else
+ {
+ result.append("0.0.0.");
+ cleanupModifier(result, version);
+ }
+ return result.toString();
+ }
+
+ private static void cleanupModifier(StringBuffer result, String modifier) {
+ for (int i = 0; i < modifier.length(); i++) {
+ char c = modifier.charAt(i);
+ if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')
+ || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
+ result.append(c);
+ else
+ result.append('_');
+ }
+ }
+
+}
diff --git a/utils/src/main/java/org/apache/felix/utils/version/VersionRange.java b/utils/src/main/java/org/apache/felix/utils/version/VersionRange.java
new file mode 100644
index 0000000..5379665
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/version/VersionRange.java
@@ -0,0 +1,436 @@
+/*
+ * 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.felix.utils.version;
+
+import java.io.Serializable;
+
+import org.osgi.framework.Version;
+
+public class VersionRange implements Serializable
+{
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public static final Version INFINITE_VERSION = new Version( Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, "" );
+ public static final VersionRange ANY_VERSION = new VersionRange( false, Version.emptyVersion, INFINITE_VERSION, true );
+
+ public static final int EXACT = 0;
+ public static final int MICRO = 1;
+ public static final int MINOR = 2;
+ public static final int MAJOR = 3;
+ public static final int ANY = 40;
+
+ private final boolean openFloor;
+ private final Version floor;
+ private final Version ceiling;
+ private final boolean openCeiling;
+
+
+ /**
+ * Interval constructor
+ *
+ * @param openFloor Whether the lower bound of the range is inclusive (false) or exclusive (true).
+ * @param floor The lower bound version of the range.
+ * @param ceiling The upper bound version of the range.
+ * @param openCeiling Whether the upper bound of the range is inclusive (false) or exclusive (true).
+ */
+ public VersionRange( boolean openFloor, Version floor, Version ceiling, boolean openCeiling )
+ {
+ this.openFloor = openFloor;
+ this.floor = floor;
+ this.ceiling = ceiling;
+ this.openCeiling = openCeiling;
+ checkRange();
+ }
+
+
+ /**
+ * atLeast constructor
+ *
+ * @param atLeast
+ */
+ public VersionRange( Version atLeast )
+ {
+ this( atLeast, false );
+ }
+
+ /**
+ * atLeast constructor
+ *
+ * @param atLeast
+ */
+ public VersionRange( Version atLeast, boolean exact )
+ {
+
+ this.openFloor = false;
+ this.floor = atLeast;
+ this.ceiling = exact ? atLeast : INFINITE_VERSION;
+ this.openCeiling = exact ? false : true;
+ checkRange();
+ }
+
+
+ public VersionRange( String val ) throws IllegalArgumentException, NumberFormatException
+ {
+ this( val, false );
+ }
+
+ public VersionRange( String val, boolean exact ) throws IllegalArgumentException, NumberFormatException
+ {
+ this( val, exact, true );
+ }
+
+ public VersionRange( String val, boolean exact, boolean clean ) throws IllegalArgumentException, NumberFormatException
+ {
+ val = val.replaceAll( "\\s", "" );
+ val = val.replaceAll( "\"", "" );
+ int fst = val.charAt( 0 );
+ if ( fst == '[' )
+ {
+ openFloor = false;
+ }
+ else if ( fst == '(' )
+ {
+ openFloor = true;
+ }
+ else
+ {
+ openFloor = false;
+ floor = VersionTable.getVersion( val, clean );
+ ceiling = exact ? floor : INFINITE_VERSION;
+ openCeiling = exact ? false : true;
+ return;
+ }
+
+ int lst = val.charAt( val.length() - 1 );
+ if ( lst == ']' )
+ {
+ openCeiling = false;
+ }
+ else if ( lst == ')' )
+ {
+ openCeiling = true;
+ }
+ else
+ {
+ throw new IllegalArgumentException( "illegal version range syntax " + val
+ + ": range must end in ')' or ']'" );
+ }
+
+ String inner = val.substring( 1, val.length() - 1 );
+ String[] floorCeiling = inner.split( "," );
+ if ( floorCeiling.length != 2 )
+ {
+ throw new IllegalArgumentException( "illegal version range syntax " + "too many commas" );
+ }
+ floor = VersionTable.getVersion( floorCeiling[0], clean );
+ ceiling = "*".equals( floorCeiling[1] ) ? INFINITE_VERSION : VersionTable.getVersion( floorCeiling[1], clean );
+ checkRange();
+ }
+
+ public static VersionRange parseVersionRange( String val ) throws IllegalArgumentException, NumberFormatException
+ {
+ if ( val == null || val.trim().length() == 0 )
+ {
+ return ANY_VERSION;
+ }
+
+ return new VersionRange( val );
+ }
+
+
+ public Version getCeiling()
+ {
+ return ceiling;
+ }
+
+
+ public Version getFloor()
+ {
+ return floor;
+ }
+
+
+ public boolean isOpenCeiling()
+ {
+ return openCeiling;
+ }
+
+
+ public boolean isOpenFloor()
+ {
+ return openFloor;
+ }
+
+
+ public boolean isPointVersion()
+ {
+ return !openFloor && !openCeiling && floor.equals( ceiling );
+ }
+
+
+ /**
+ * test a version to see if it falls in the range
+ *
+ * @param version
+ * @return
+ */
+ public boolean contains( Version version )
+ {
+ if ( version.equals( INFINITE_VERSION ) )
+ {
+ return ceiling.equals( INFINITE_VERSION );
+ }
+ else
+ {
+ return ( version.compareTo( floor ) > 0 && version.compareTo( ceiling ) < 0 )
+ || ( !openFloor && version.equals( floor ) ) || ( !openCeiling && version.equals( ceiling ) );
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.aries.application.impl.VersionRange#intersect(VersionRange
+ * range)
+ */
+
+ public VersionRange intersect(VersionRange r)
+ {
+ // Use the highest minimum version.
+ final Version newFloor;
+ final boolean newOpenFloor;
+ int minCompare = floor.compareTo(r.getFloor());
+ if (minCompare > 0)
+ {
+ newFloor = floor;
+ newOpenFloor = openFloor;
+ }
+ else if (minCompare < 0)
+ {
+ newFloor = r.getFloor();
+ newOpenFloor = r.isOpenFloor();
+ }
+ else
+ {
+ newFloor = floor;
+ newOpenFloor = (openFloor || r.isOpenFloor());
+ }
+
+ // Use the lowest maximum version.
+ final Version newCeiling;
+ final boolean newOpenCeiling;
+ // null maximum version means unbounded, so the highest possible value.
+ int maxCompare = ceiling.compareTo(r.getCeiling());
+ if (maxCompare < 0)
+ {
+ newCeiling = ceiling;
+ newOpenCeiling = openCeiling;
+ }
+ else if (maxCompare > 0)
+ {
+ newCeiling = r.getCeiling();
+ newOpenCeiling = r.isOpenCeiling();
+ }
+ else
+ {
+ newCeiling = ceiling;
+ newOpenCeiling = (openCeiling || r.isOpenCeiling());
+ }
+
+ VersionRange result;
+ if (isRangeValid(newOpenFloor, newFloor, newCeiling, newOpenCeiling))
+ {
+ result = new VersionRange(newOpenFloor, newFloor, newCeiling, newOpenCeiling);
+ }
+ else
+ {
+ result = null;
+ }
+ return result;
+ }
+
+ /**
+ * Check if the supplied parameters describe a valid version range.
+ *
+ * @param floor
+ * the minimum version.
+ * @param openFloor
+ * whether the minimum version is exclusive.
+ * @param ceiling
+ * the maximum version.
+ * @param openCeiling
+ * whether the maximum version is exclusive.
+ * @return true is the range is valid; otherwise false.
+ */
+ private static boolean isRangeValid(boolean openFloor, Version floor, Version ceiling, boolean openCeiling) {
+ boolean result;
+ int compare = floor.compareTo(ceiling);
+ if (compare > 0)
+ {
+ // Minimum larger than maximum is invalid.
+ result = false;
+ }
+ else if (compare == 0 && (openFloor || openCeiling))
+ {
+ // If floor and ceiling are the same, and either are exclusive, no valid range
+ // exists.
+ result = false;
+ }
+ else
+ {
+ // Range is valid.
+ result = true;
+ }
+ return result;
+ }
+
+ private void checkRange()
+ {
+ if (!isRangeValid(openFloor, floor, ceiling, openCeiling))
+ {
+ throw new IllegalArgumentException("invalid version range: " + makeString(openFloor, floor, ceiling, openCeiling));
+ }
+ }
+
+
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ( ( ceiling == null ) ? 0 : ceiling.hashCode() );
+ result = prime * result + ( ( floor == null ) ? 0 : floor.hashCode() );
+ result = prime * result + ( openCeiling ? 1231 : 1237 );
+ result = prime * result + ( openFloor ? 1231 : 1237 );
+ return result;
+ }
+
+
+ public boolean equals( Object obj )
+ {
+ if ( this == obj )
+ return true;
+ if ( obj == null )
+ return false;
+ if ( getClass() != obj.getClass() )
+ return false;
+ final VersionRange other = ( VersionRange ) obj;
+ if ( ceiling == null )
+ {
+ if ( other.ceiling != null )
+ return false;
+ }
+ else if ( !ceiling.equals( other.ceiling ) )
+ return false;
+ if ( floor == null )
+ {
+ if ( other.floor != null )
+ return false;
+ }
+ else if ( !floor.equals( other.floor ) )
+ return false;
+ if ( openCeiling != other.openCeiling )
+ return false;
+ if ( openFloor != other.openFloor )
+ return false;
+ return true;
+ }
+
+
+ public String toString()
+ {
+ if ( ANY_VERSION.equals( this ) )
+ {
+ return makeString( openFloor, Version.emptyVersion, INFINITE_VERSION, openCeiling );
+ }
+ return makeString( openFloor, floor, ceiling, openCeiling );
+ }
+
+
+ private String makeString( boolean openFloor, Version floor, Version ceiling, boolean openCeiling )
+ {
+ StringBuffer vr = new StringBuffer( 32 );
+ if ( INFINITE_VERSION.equals( ceiling ) )
+ {
+ vr.append( Version.emptyVersion.equals( floor ) ? "0" : floor.toString() );
+ }
+ else
+ {
+ vr.append( openFloor ? "(" : "[" );
+ String floorStr = Version.emptyVersion.equals( floor ) ? "0" : floor.toString();
+ String ceilingStr = ceiling.toString();
+ vr.append( floorStr ).append( "," ).append( ceilingStr );
+ vr.append( openCeiling ? ")" : "]" );
+ }
+ return vr.toString();
+ }
+
+
+ public static VersionRange newInstance( Version pointVersion,
+ int lowerBoundRule,
+ int upperBoundRule )
+ {
+ Version floor = null;
+ switch ( lowerBoundRule )
+ {
+ case ANY:
+ floor = VersionTable.getVersion( 0, 0, 0 );
+ break;
+ case MAJOR:
+ floor = VersionTable.getVersion( pointVersion.getMajor(), 0, 0 );
+ break;
+ case MINOR:
+ floor = VersionTable.getVersion( pointVersion.getMajor(), pointVersion.getMinor(), 0 );
+ break;
+ case MICRO:
+ floor = VersionTable.getVersion( pointVersion.getMajor(), pointVersion.getMinor(), pointVersion.getMicro() );
+ break;
+ case EXACT:
+ floor = pointVersion;
+ break;
+ }
+
+ Version ceiling = null;
+ boolean openCeiling = true;
+ switch ( upperBoundRule )
+ {
+ case ANY:
+ ceiling = INFINITE_VERSION;
+ break;
+ case MAJOR:
+ ceiling = VersionTable.getVersion( pointVersion.getMajor() + 1, 0, 0 );
+ break;
+ case MINOR:
+ ceiling = VersionTable.getVersion( pointVersion.getMajor(), pointVersion.getMinor() + 1, 0 );
+ break;
+ case MICRO:
+ ceiling = VersionTable.getVersion( pointVersion.getMajor(), pointVersion.getMinor(), pointVersion.getMicro() + 1 );
+ break;
+ case EXACT:
+ ceiling = pointVersion;
+ openCeiling = false;
+ break;
+ }
+
+ return new VersionRange( false, floor, ceiling, openCeiling );
+ }
+}
diff --git a/utils/src/main/java/org/apache/felix/utils/version/VersionTable.java b/utils/src/main/java/org/apache/felix/utils/version/VersionTable.java
new file mode 100644
index 0000000..d836b8b
--- /dev/null
+++ b/utils/src/main/java/org/apache/felix/utils/version/VersionTable.java
@@ -0,0 +1,91 @@
+/*
+ * 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.felix.utils.version;
+
+import java.util.WeakHashMap;
+
+import org.osgi.framework.Version;
+
+/**
+ * Cache of Versions backed by a WeakHashMap to conserve memory.
+ *
+ * VersionTable.getVersion should be used in preference to new Version() or Version.parseVersion.
+ *
+ * @author dave
+ *
+ */
+public final class VersionTable
+{
+ private static final WeakHashMap versions = new WeakHashMap();
+
+ private VersionTable() { }
+
+ public static Version getVersion(String version)
+ {
+ return getVersion( version, true );
+ }
+
+ public static Version getVersion(String version, boolean clean)
+ {
+ if (clean)
+ {
+ version = VersionCleaner.clean(version);
+ }
+ synchronized( versions )
+ {
+ Version v = (Version) versions.get(version);
+ if ( v == null )
+ {
+ v = Version.parseVersion(version);
+ versions.put(version, v);
+ }
+ return v;
+ }
+ }
+
+ public static Version getVersion(int major, int minor, int micro)
+ {
+ return getVersion(major, minor, micro, null);
+ }
+
+ public static Version getVersion(int major, int minor, int micro, String qualifier)
+ {
+ String key;
+
+ if ( qualifier == null || qualifier.length() == 0 )
+ {
+ key = major + "." + minor + "." + micro;
+ }
+ else
+ {
+ key = major + "." + minor + "." + micro + "." + qualifier;
+ }
+
+ synchronized( versions )
+ {
+ Version v = (Version) versions.get(key);
+ if ( v == null )
+ {
+ v = new Version(major, minor, micro, qualifier);
+ versions.put(key, v);
+ }
+ return v;
+ }
+ }
+}
diff --git a/utils/src/test/java/org/apache/felix/utils/filter/FilterImplTest.java b/utils/src/test/java/org/apache/felix/utils/filter/FilterImplTest.java
new file mode 100644
index 0000000..8cbbc58
--- /dev/null
+++ b/utils/src/test/java/org/apache/felix/utils/filter/FilterImplTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.felix.utils.filter;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import junit.framework.TestCase;
+import org.osgi.framework.Version;
+
+public class FilterImplTest extends TestCase
+{
+ public void testStandardLDAP() throws Exception
+ {
+ FilterImpl filterImpl = FilterImpl.newInstance(
+ "(&(package=org.eclipse.core.runtime)(version>=0.0.0)(common=split))");
+
+ Dictionary dict = new Hashtable();
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", "0.0.0");
+ dict.put("common", "split");
+
+ assertTrue(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", "0.0.0");
+ dict.put("common", "split-wrong");
+
+ assertFalse(filterImpl.match(dict));
+ }
+
+ public void testNoneStandardLDAPOperators() throws Exception
+ {
+ FilterImpl filterImpl = FilterImpl.newInstance(
+ "(&(package=org.eclipse.core.runtime)(version>=0.0.0)(common=split)(mandatory:<*common,test))");
+
+ Dictionary dict = new Hashtable();
+ dict.put("somethindifferent", "sonstwas");
+ assertFalse(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("mandatory:", "common");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", new Version("0.0.0"));
+ dict.put("common", "split");
+ assertTrue(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("mandatory:", "common,test");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", new Version("0.0.0"));
+ dict.put("common", "split");
+ assertTrue(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", new Version("0.0.0"));
+ dict.put("common", "split");
+ assertTrue(filterImpl.match(dict));
+
+ filterImpl = FilterImpl.newInstance(
+ "(&(package=org.eclipse.core.runtime)(version>=0.0.0)(common=split)(mandatory:*>common))");
+ dict = new Hashtable();
+ dict.put("mandatory:", "common");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", new Version("0.0.0"));
+ dict.put("common", "split");
+ assertTrue(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("mandatory:", "common,test");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", new Version("0.0.0"));
+ dict.put("common", "split");
+ assertTrue(filterImpl.match(dict));
+
+ filterImpl = FilterImpl.newInstance(
+ "(&(package=org.eclipse.core.runtime)(version>=0.0.0)(common=split)(mandatory:*>common,test))");
+ dict = new Hashtable();
+ dict.put("mandatory:", "common");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", new Version("0.0.0"));
+ dict.put("common", "split");
+ assertFalse(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("mandatory:", "common,test");
+ dict.put("package", "org.eclipse.core.runtime");
+ dict.put("version", new Version("0.0.0"));
+ dict.put("common", "split");
+ assertTrue(filterImpl.match(dict));
+ }
+
+ public void testCaseSensitive() throws Exception
+ {
+ FilterImpl filterImpl = FilterImpl.newInstance("(&(package=org.eclipse.core.runtime))");
+
+ Dictionary dict = new Hashtable();
+ dict.put("PACKAGE", "org.eclipse.core.runtime");
+ assertTrue(filterImpl.match(dict));
+
+ dict = new Hashtable();
+ dict.put("PACKAGE", "org.eclipse.core.runtime");
+ assertFalse(filterImpl.matchCase(dict));
+ }
+
+}
\ No newline at end of file
diff --git a/utils/src/test/java/org/apache/felix/utils/manifest/ParserTest.java b/utils/src/test/java/org/apache/felix/utils/manifest/ParserTest.java
new file mode 100644
index 0000000..7510c46
--- /dev/null
+++ b/utils/src/test/java/org/apache/felix/utils/manifest/ParserTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.felix.utils.manifest;
+
+import junit.framework.TestCase;
+
+public class ParserTest extends TestCase
+{
+
+ public void testSimple() throws Exception {
+ Clause[] paths = Parser.parseHeader("/foo.xml, /foo/bar.xml");
+ assertEquals(2, paths.length);
+ assertEquals("/foo.xml", paths[0].getName());
+ assertEquals(0, paths[0].getAttributes().length);
+ assertEquals(0, paths[0].getDirectives().length);
+ assertEquals("/foo/bar.xml", paths[1].getName());
+ assertEquals(0, paths[1].getAttributes().length);
+ assertEquals(0, paths[1].getDirectives().length);
+ }
+
+ public void testComplex() throws Exception {
+ Clause[] paths = Parser.parseHeader("OSGI-INF/blueprint/comp1_named.xml;ignored-directive:=true,OSGI-INF/blueprint/comp2_named.xml;some-other-attribute=1");
+ assertEquals(2, paths.length);
+ assertEquals("OSGI-INF/blueprint/comp1_named.xml", paths[0].getName());
+ assertEquals(0, paths[0].getAttributes().length);
+ assertEquals(1, paths[0].getDirectives().length);
+ assertEquals("true", paths[0].getDirective("ignored-directive"));
+ assertEquals("OSGI-INF/blueprint/comp2_named.xml", paths[1].getName());
+ assertEquals(1, paths[1].getAttributes().length);
+ assertEquals("1", paths[1].getAttribute("some-other-attribute"));
+ assertEquals(0, paths[1].getDirectives().length);
+ }
+
+ public void testPaths() throws Exception {
+ Clause[] paths = Parser.parseHeader("OSGI-INF/blueprint/comp1_named.xml;ignored-directive:=true,OSGI-INF/blueprint/comp2_named.xml;foo.xml;a=b;1:=2;c:=d;4=5");
+ assertEquals(3, paths.length);
+ assertEquals("OSGI-INF/blueprint/comp1_named.xml", paths[0].getName());
+ assertEquals(0, paths[0].getAttributes().length);
+ assertEquals(1, paths[0].getDirectives().length);
+ assertEquals("true", paths[0].getDirective("ignored-directive"));
+ assertEquals("OSGI-INF/blueprint/comp2_named.xml", paths[1].getName());
+ assertEquals(2, paths[1].getAttributes().length);
+ assertEquals("b", paths[1].getAttribute("a"));
+ assertEquals("5", paths[1].getAttribute("4"));
+ assertEquals(2, paths[1].getDirectives().length);
+ assertEquals("d", paths[1].getDirective("c"));
+ assertEquals("2", paths[1].getDirective("1"));
+ assertEquals("foo.xml", paths[2].getName());
+ assertEquals(2, paths[2].getAttributes().length);
+ assertEquals("b", paths[2].getAttribute("a"));
+ assertEquals("5", paths[2].getAttribute("4"));
+ assertEquals(2, paths[2].getDirectives().length);
+ assertEquals("d", paths[2].getDirective("c"));
+ assertEquals("2", paths[2].getDirective("1"));
+ }
+
+}
diff --git a/utils/src/test/java/org/apache/felix/utils/version/VersionCleanerTest.java b/utils/src/test/java/org/apache/felix/utils/version/VersionCleanerTest.java
new file mode 100644
index 0000000..594b46d
--- /dev/null
+++ b/utils/src/test/java/org/apache/felix/utils/version/VersionCleanerTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.felix.utils.version;
+
+import junit.framework.TestCase;
+
+public class VersionCleanerTest extends TestCase {
+
+ public void testConvertVersionToOsgi()
+ {
+ String osgiVersion;
+
+ osgiVersion = VersionCleaner.clean( "" );
+ assertEquals( "0.0.0", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "2.1.0-SNAPSHOT" );
+ assertEquals( "2.1.0.SNAPSHOT", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "2.1-SNAPSHOT" );
+ assertEquals( "2.1.0.SNAPSHOT", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "2-SNAPSHOT" );
+ assertEquals( "2.0.0.SNAPSHOT", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "2" );
+ assertEquals( "2.0.0", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "2.1" );
+ assertEquals( "2.1.0", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "2.1.3" );
+ assertEquals( "2.1.3", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "2.1.3.4" );
+ assertEquals( "2.1.3.4", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "4aug2000r7-dev" );
+ assertEquals( "0.0.0.4aug2000r7-dev", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "1.1-alpha-2" );
+ assertEquals( "1.1.0.alpha-2", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "1.0-alpha-16-20070122.203121-13" );
+ assertEquals( "1.0.0.alpha-16-20070122_203121-13", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "1.0-20070119.021432-1" );
+ assertEquals( "1.0.0.20070119_021432-1", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "1-20070119.021432-1" );
+ assertEquals( "1.0.0.20070119_021432-1", osgiVersion );
+
+ osgiVersion = VersionCleaner.clean( "1.4.1-20070217.082013-7" );
+ assertEquals( "1.4.1.20070217_082013-7", osgiVersion );
+ }
+
+}
diff --git a/utils/src/test/java/org/apache/felix/utils/version/VersionRangeTest.java b/utils/src/test/java/org/apache/felix/utils/version/VersionRangeTest.java
new file mode 100644
index 0000000..80d45f0
--- /dev/null
+++ b/utils/src/test/java/org/apache/felix/utils/version/VersionRangeTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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.felix.utils.version;
+
+import junit.framework.TestCase;
+import org.osgi.framework.Version;
+
+
+public class VersionRangeTest extends TestCase {
+
+ public void testVersionRange() throws Exception {
+ String version1 = "[1.2.3, 4.5.6]";
+ String version2 = "(1, 2]";
+ String version3 = "[2,4)";
+ String version4 = "(1,2)";
+ String version5 = "2";
+ String version6 = "2.3";
+ String version7 = "[1.2.3.q, 2.3.4.p)";
+ String version8 = "1.2.2.5";
+ String version9 = "a.b.c";
+ String version10 = null;
+ String version11 = "";
+ String version12 = "\"[1.2.3, 4.5.6]\"";
+
+ VersionRange vr = new VersionRange(version1);
+ assertEquals("The value is wrong", "1.2.3", vr.getFloor().toString());
+ assertFalse("The value is wrong", vr.isOpenFloor());
+ assertEquals("The value is wrong", "4.5.6", vr.getCeiling().toString());
+ assertFalse("The value is wrong", vr.isOpenCeiling());
+
+ vr = new VersionRange(version2);
+ assertEquals("The value is wrong", "1.0.0", vr.getFloor().toString());
+ assertTrue("The value is wrong", vr.isOpenFloor());
+ assertEquals("The value is wrong", "2.0.0", vr.getCeiling().toString());
+ assertFalse("The value is wrong", vr.isOpenCeiling());
+
+ vr = new VersionRange(version3);
+
+ assertEquals("The value is wrong", "2.0.0", vr.getFloor().toString());
+ assertFalse("The value is wrong", vr.isOpenFloor());
+ assertEquals("The value is wrong", "4.0.0", vr.getCeiling().toString());
+ assertTrue("The value is wrong", vr.isOpenCeiling());
+
+ vr = new VersionRange(version4);
+
+ assertEquals("The value is wrong", "1.0.0", vr.getFloor().toString());
+ assertTrue("The value is wrong", vr.isOpenFloor());
+ assertEquals("The value is wrong", "2.0.0", vr.getCeiling().toString());
+ assertTrue("The value is wrong", vr.isOpenCeiling());
+
+ vr = new VersionRange(version5);
+ assertEquals("The value is wrong", "2.0.0", vr.getFloor().toString());
+ assertFalse("The value is wrong", vr.isOpenFloor());
+ assertEquals("The value is wrong", VersionRange.INFINITE_VERSION, vr.getCeiling());
+ assertTrue("The value is wrong", vr.isOpenCeiling());
+
+ vr = new VersionRange(version6, true);
+ assertEquals("The value is wrong", "2.3.0", vr.getFloor().toString());
+ assertFalse("The value is wrong", vr.isOpenFloor());
+ assertEquals("The value is wrong", "2.3.0", vr.getCeiling().toString());
+ assertFalse("The value is wrong", vr.isOpenCeiling());
+
+ vr = new VersionRange(version7);
+ assertEquals("The value is wrong", "1.2.3.q", vr.getFloor().toString());
+ assertFalse("The value is wrong", vr.isOpenFloor());
+ assertEquals("The value is wrong", "2.3.4.p", vr.getCeiling().toString());
+ assertTrue("The value is wrong", vr.isOpenCeiling());
+
+ vr = new VersionRange(version8);
+ assertEquals("The value is wrong", "1.2.2.5", vr.getFloor().toString());
+ assertFalse("The value is wrong", vr.isOpenFloor());
+ assertEquals("The value is wrong", VersionRange.INFINITE_VERSION, vr.getCeiling());
+ assertTrue("The value is wrong", vr.isOpenCeiling());
+ boolean exception = false;
+ try {
+ vr = new VersionRange(version9, false, false);
+ } catch (Exception e) {
+ exception = true;
+ }
+ assertTrue("The value is wrong", exception);
+ boolean exceptionNull = false;
+ try {
+ vr = new VersionRange(version10, false, false);
+ } catch (Exception e) {
+ exceptionNull = true;
+ }
+ assertTrue("The value is wrong", exceptionNull);
+ // empty version should be defaulted to >=0.0.0
+ vr = VersionRange.parseVersionRange(version11);
+ assertEquals("The value is wrong", "0.0.0", vr.getFloor().toString());
+ assertFalse("The value is wrong", vr.isOpenFloor());
+ assertEquals("The value is wrong", VersionRange.INFINITE_VERSION, vr.getCeiling());
+ assertTrue("The value is wrong", vr.isOpenCeiling());
+
+ vr = new VersionRange(version12);
+ assertEquals("The value is wrong", "1.2.3", vr.getFloor().toString());
+ assertFalse("The value is wrong", vr.isOpenFloor());
+ assertEquals("The value is wrong", "4.5.6", vr.getCeiling().toString());
+ assertFalse("The value is wrong", vr.isOpenCeiling());
+ }
+
+ public void testInvalidVersions() throws Exception {
+ try {
+ new VersionRange("a", false, false);
+ assertTrue("Should have thrown an exception", false);
+ } catch (IllegalArgumentException e) {
+ }
+
+ }
+
+ public void testMatches() {
+ VersionRange vr = new VersionRange("[1.0.0, 2.0.0]");
+
+ assertFalse(vr.contains(new Version(0, 9, 0)));
+ assertFalse(vr.contains(new Version(2, 1, 0)));
+ assertTrue(vr.contains(new Version(2, 0, 0)));
+ assertTrue(vr.contains(new Version(1, 0, 0)));
+ assertTrue(vr.contains(new Version(1, 5, 0)));
+
+ vr = new VersionRange("[1.0.0, 2.0.0)");
+
+ assertFalse(vr.contains(new Version(0, 9, 0)));
+ assertFalse(vr.contains(new Version(2, 1, 0)));
+ assertFalse(vr.contains(new Version(2, 0, 0)));
+ assertTrue(vr.contains(new Version(1, 0, 0)));
+ assertTrue(vr.contains(new Version(1, 5, 0)));
+
+ vr = new VersionRange("(1.0.0, 2.0.0)");
+
+ assertFalse(vr.contains(new Version(0, 9, 0)));
+ assertFalse(vr.contains(new Version(2, 1, 0)));
+ assertFalse(vr.contains(new Version(2, 0, 0)));
+ assertFalse(vr.contains(new Version(1, 0, 0)));
+ assertTrue(vr.contains(new Version(1, 5, 0)));
+
+ vr = new VersionRange("[1.0.0, 1.0.0]");
+ assertFalse(vr.contains(new Version(0, 9, 0)));
+ assertFalse(vr.contains(new Version(2, 0, 0)));
+ assertTrue(vr.contains(new Version(1, 0, 0)));
+ assertFalse(vr.contains(new Version(1, 5, 0)));
+ assertFalse(vr.contains(new Version(1, 9, 9)));
+ }
+
+ public void testIntersectVersionRange_Valid1() {
+ VersionRange v1 = new VersionRange("[1.0.0,3.0.0]");
+ VersionRange v2 = new VersionRange("[2.0.0,3.0.0)");
+ VersionRange result = v1.intersect(v2);
+ assertNotNull(result);
+ assertEquals("[2.0.0,3.0.0)", result.toString());
+ }
+
+ public void testIntersectVersionRange_Valid2() {
+ VersionRange v1 = new VersionRange("[1.0.0,3.0.0)");
+ VersionRange v2 = new VersionRange("(2.0.0,3.0.0]");
+ VersionRange result = v1.intersect(v2);
+ assertNotNull(result);
+ assertEquals("(2.0.0,3.0.0)", result.toString());
+ }
+
+ public void testIntersectVersionRange_Valid3() {
+ VersionRange v1 = new VersionRange("[2.0.0,2.0.0]");
+ VersionRange v2 = new VersionRange("[1.0.0,3.0.0]");
+ VersionRange result = v1.intersect(v2);
+ assertNotNull(result);
+ assertEquals("[2.0.0,2.0.0]", result.toString());
+ }
+
+ public void testIntersectVersionRange_Invalid1() {
+ VersionRange v1 = new VersionRange("[1.0.0,2.0.0]");
+ VersionRange v2 = new VersionRange("(2.0.0,3.0.0]");
+ VersionRange result = v1.intersect(v2);
+ assertNull(result);
+ }
+
+ public void testIntersectVersionRange_Invalid2() {
+ VersionRange v1 = new VersionRange("[1.0.0,2.0.0)");
+ VersionRange v2 = new VersionRange("[2.0.0,3.0.0]");
+ VersionRange result = v1.intersect(v2);
+ assertNull(result);
+ }
+
+ public void testIntersectVersionRange_Invalid3() {
+ VersionRange v1 = new VersionRange("[1.0.0,1.0.0]");
+ VersionRange v2 = new VersionRange("[2.0.0,2.0.0]");
+ VersionRange result = v1.intersect(v2);
+ assertNull(result);
+ }
+
+}