You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gn...@apache.org on 2010/03/18 09:40:38 UTC

svn commit: r924669 [1/2] - in /felix/trunk/utils: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/felix/ src/main/java/org/apache/felix/utils/ src/main/java/org/apache/felix/utils/collections/ src...

Author: gnodet
Date: Thu Mar 18 08:40:37 2010
New Revision: 924669

URL: http://svn.apache.org/viewvc?rev=924669&view=rev
Log:
FELIX-2212: Create a new Utils subproject to hold shared classes

Added:
    felix/trunk/utils/
    felix/trunk/utils/pom.xml
    felix/trunk/utils/src/
    felix/trunk/utils/src/main/
    felix/trunk/utils/src/main/java/
    felix/trunk/utils/src/main/java/org/
    felix/trunk/utils/src/main/java/org/apache/
    felix/trunk/utils/src/main/java/org/apache/felix/
    felix/trunk/utils/src/main/java/org/apache/felix/utils/
    felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/
    felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/IteratorToEnumeration.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/MapToDictionary.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/filter/
    felix/trunk/utils/src/main/java/org/apache/felix/utils/filter/FilterImpl.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/log/
    felix/trunk/utils/src/main/java/org/apache/felix/utils/log/Logger.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/
    felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Attribute.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Clause.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Directive.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Parser.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/version/
    felix/trunk/utils/src/main/java/org/apache/felix/utils/version/VersionCleaner.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/version/VersionRange.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/version/VersionTable.java
    felix/trunk/utils/src/test/
    felix/trunk/utils/src/test/java/
    felix/trunk/utils/src/test/java/org/
    felix/trunk/utils/src/test/java/org/apache/
    felix/trunk/utils/src/test/java/org/apache/felix/
    felix/trunk/utils/src/test/java/org/apache/felix/utils/
    felix/trunk/utils/src/test/java/org/apache/felix/utils/filter/
    felix/trunk/utils/src/test/java/org/apache/felix/utils/filter/FilterImplTest.java
    felix/trunk/utils/src/test/java/org/apache/felix/utils/manifest/
    felix/trunk/utils/src/test/java/org/apache/felix/utils/manifest/ParserTest.java
    felix/trunk/utils/src/test/java/org/apache/felix/utils/version/
    felix/trunk/utils/src/test/java/org/apache/felix/utils/version/VersionCleanerTest.java
    felix/trunk/utils/src/test/java/org/apache/felix/utils/version/VersionRangeTest.java

Added: felix/trunk/utils/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/utils/pom.xml?rev=924669&view=auto
==============================================================================
--- felix/trunk/utils/pom.xml (added)
+++ felix/trunk/utils/pom.xml Thu Mar 18 08:40:37 2010
@@ -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>

Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/IteratorToEnumeration.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/IteratorToEnumeration.java?rev=924669&view=auto
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/IteratorToEnumeration.java (added)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/IteratorToEnumeration.java Thu Mar 18 08:40:37 2010
@@ -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();
+    }
+}

Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/MapToDictionary.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/MapToDictionary.java?rev=924669&view=auto
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/MapToDictionary.java (added)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/MapToDictionary.java Thu Mar 18 08:40:37 2010
@@ -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

Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/filter/FilterImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/filter/FilterImpl.java?rev=924669&view=auto
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/filter/FilterImpl.java (added)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/filter/FilterImpl.java Thu Mar 18 08:40:37 2010
@@ -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();
+        }
+    }
+
+}

Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/log/Logger.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/log/Logger.java?rev=924669&view=auto
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/log/Logger.java (added)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/log/Logger.java Thu Mar 18 08:40:37 2010
@@ -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

Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Attribute.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Attribute.java?rev=924669&view=auto
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Attribute.java (added)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Attribute.java Thu Mar 18 08:40:37 2010
@@ -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;
+    }
+
+}

Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Clause.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Clause.java?rev=924669&view=auto
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Clause.java (added)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Clause.java Thu Mar 18 08:40:37 2010
@@ -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;
+    }
+
+}

Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Directive.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Directive.java?rev=924669&view=auto
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Directive.java (added)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Directive.java Thu Mar 18 08:40:37 2010
@@ -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;
+    }
+
+}

Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Parser.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Parser.java?rev=924669&view=auto
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Parser.java (added)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/manifest/Parser.java Thu Mar 18 08:40:37 2010
@@ -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()]);
+    }
+
+}

Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/version/VersionCleaner.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/version/VersionCleaner.java?rev=924669&view=auto
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/version/VersionCleaner.java (added)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/version/VersionCleaner.java Thu Mar 18 08:40:37 2010
@@ -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('_');
+        }
+    }
+
+}