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);
+    }
+
+}