patch for FELIX-1583 - refactored LDAP and VersionRange classes into own bundle


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@814023 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/sigil/common/osgi/.classpath b/sigil/common/osgi/.classpath
new file mode 100644
index 0000000..c9ffd60
--- /dev/null
+++ b/sigil/common/osgi/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.apache.felix.sigil.classpathContainer"/>
+	<classpathentry kind="output" path="build/classes"/>
+</classpath>
diff --git a/sigil/common/osgi/.project b/sigil/common/osgi/.project
new file mode 100644
index 0000000..9702092
--- /dev/null
+++ b/sigil/common/osgi/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.apache.felix.sigil.common.osgi</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.apache.felix.sigil.eclipse.core.sigilBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.apache.felix.sigil.sigilnature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/sigil/common/osgi/build.xml b/sigil/common/osgi/build.xml
new file mode 100644
index 0000000..8f55817
--- /dev/null
+++ b/sigil/common/osgi/build.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!--
+  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 name="common.osgi" default="build">
+      <import file="../build.xml"/>
+</project>
diff --git a/sigil/common/osgi/ivy.xml b/sigil/common/osgi/ivy.xml
new file mode 100644
index 0000000..a28cff4
--- /dev/null
+++ b/sigil/common/osgi/ivy.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<ivy-module version="1.0">
+  <info 
+        organisation="org.apache"
+        module="felix.sigil.common.osgi"
+        status="integration"/>
+   <publications>
+     <artifact name="org.apache.felix.sigil.common.osgi" />
+   </publications>
+</ivy-module>
diff --git a/sigil/common/osgi/sigil.properties b/sigil/common/osgi/sigil.properties
new file mode 100644
index 0000000..2422c42
--- /dev/null
+++ b/sigil/common/osgi/sigil.properties
@@ -0,0 +1,17 @@
+
+# sigil project file, saved by plugin.
+
+-bundles: \
+	org.apache.felix.sigil.common.osgi, \
+
+-sourcedirs: \
+	src, \
+
+-exports: \
+	org.apache.felix.sigil.common.osgi, \
+
+-imports: \
+	org.apache.felix.sigil.common.osgi, \
+	org.osgi.framework, \
+
+# end
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/And.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/And.java
new file mode 100644
index 0000000..026dd8e
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/And.java
@@ -0,0 +1,148 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+import java.util.Map;
+
+
+public class And implements LDAPExpr
+{
+
+    /**
+     */
+    private static final long serialVersionUID = 1L;
+    private LDAPExpr[] children;
+
+
+    public static LDAPExpr apply( LDAPExpr... terms )
+    {
+        if ( terms == null )
+        {
+            throw new NullPointerException( "terms cannot be null" );
+        }
+        else if ( terms.length == 0 )
+        {
+            return Expressions.T;
+        }
+        else if ( terms.length == 1 )
+        {
+            return terms[0];
+        }
+        LDAPExpr[] filtered = new LDAPExpr[terms.length];
+        int ctr = 0;
+        for ( int i = 0; i < terms.length; i++ )
+        {
+            if ( terms[i].equals( Expressions.F ) )
+                return Expressions.F;
+            if ( terms[i].equals( Expressions.T ) )
+                continue;
+            filtered[ctr] = terms[i];
+            ctr++;
+        }
+        if ( ctr == 0 )
+        {
+            return Expressions.T;
+        }
+        else if ( ctr == 1 )
+        {
+            return filtered[0];
+        }
+        LDAPExpr[] andTerms = new LDAPExpr[ctr];
+        System.arraycopy( filtered, 0, andTerms, 0, ctr );
+
+        return new And( andTerms );
+    }
+
+
+    private And( LDAPExpr... children )
+    {
+        this.children = children;
+    }
+
+
+    public boolean eval( Map<String, ?> map )
+    {
+        for ( int i = 0; i < children.length; i++ )
+        {
+            if ( !children[i].eval( map ) )
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    public void visit( ExprVisitor v )
+    {
+        v.visitAnd( this );
+    }
+
+
+    public LDAPExpr[] getChildren()
+    {
+        return children;
+    }
+
+
+    public void setChildren( LDAPExpr[] children )
+    {
+        this.children = children;
+    }
+
+
+    @Override
+    public boolean equals( Object other )
+    {
+        if ( other instanceof And )
+        {
+            And that = ( And ) other;
+            if ( children.length != that.children.length )
+            {
+                return false;
+            }
+            for ( int i = 0; i < children.length; i++ )
+            {
+                if ( !children[i].equals( that.children[i] ) )
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+
+    @Override
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer( 256 );
+        buf.append( "(&" );
+        for ( int i = 0; i < children.length; i++ )
+        {
+            buf.append( " " ).append( children[i] ).append( " " );
+        }
+        buf.append( ")" );
+        return buf.toString();
+    }
+
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Cardinality.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Cardinality.java
new file mode 100644
index 0000000..b0ad6cb
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Cardinality.java
@@ -0,0 +1,192 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+import java.io.Serializable;
+
+
+/**
+ * Immutable class representing cardinality constraints between two entities.
+ * 
+ */
+public class Cardinality implements Serializable
+{
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+    public static final Cardinality ZERO_TO_MANY = new Cardinality( 0, -1 );
+    public static final Cardinality ONE_TO_MANY = new Cardinality( 1, -1 );
+    public static final Cardinality ZERO_TO_ONE = new Cardinality( 0, 1 );
+    public static final Cardinality ONE_TO_ONE = new Cardinality( 1, 1 );
+
+    private int min;
+    private int max;
+
+
+    /**
+     * @param min
+     *            >=0 (usually 0 or 1)
+     * @param max
+     *            >=min or -1 to indicate an unbounded maximum
+     */
+    public Cardinality( int min, int max )
+    {
+        if ( min < 0 )
+        {
+            throw new IllegalArgumentException( "Min cannot be less than 0" );
+        }
+
+        if ( ( max < min ) && ( max != -1 ) )
+        {
+            throw new IllegalArgumentException( "Max cannot be less than min" );
+        }
+
+        this.min = min;
+        this.max = max;
+    }
+
+
+    public int getMin()
+    {
+        return min;
+    }
+
+
+    public int getMax()
+    {
+        return max;
+    }
+
+
+    public String toString()
+    {
+        return min + ".." + ( ( max == -1 ) ? ( "n" ) : ( Integer.toString( max ) ) );
+    }
+
+
+    public boolean isDefined( Cardinality cardinality )
+    {
+        return ( min <= cardinality.min ) && ( ( max == -1 ) || ( max >= cardinality.max ) );
+    }
+
+
+    public boolean isSingleton()
+    {
+        return ( min == 1 ) && ( max == 1 );
+    }
+
+
+    public static Cardinality parse( String stringRep ) throws IllegalArgumentException
+    {
+        stringRep = stringRep.trim();
+
+        int dotdot = stringRep.indexOf( ".." );
+
+        if ( dotdot == -1 )
+        {
+            throw new IllegalArgumentException( "Invalid cardinality string representation, expected .." );
+        }
+
+        String minStr = stringRep.substring( 0, dotdot );
+        String maxStr = stringRep.substring( dotdot + 2 );
+
+        int min = Integer.parseInt( minStr );
+        int max = min;
+
+        if ( "n".equals( maxStr ) )
+        {
+            max = -1;
+        }
+        else
+        {
+            max = Integer.parseInt( maxStr );
+        }
+
+        return cardinality( min, max );
+    }
+
+
+    public static Cardinality cardinality( int min, int max )
+    {
+        Cardinality c = null;
+
+        if ( min == 0 )
+        {
+            if ( max == 1 )
+            {
+                c = ZERO_TO_ONE;
+            }
+            else if ( max == -1 )
+            {
+                c = ZERO_TO_MANY;
+            }
+        }
+        else if ( min == 1 )
+        {
+            if ( max == 1 )
+            {
+                c = ONE_TO_ONE;
+            }
+            else if ( max == -1 )
+            {
+                c = ONE_TO_MANY;
+            }
+        }
+
+        if ( c == null )
+            c = new Cardinality( min, max );
+
+        return c;
+    }
+
+
+    public int hashCode()
+    {
+        return max ^ min;
+    }
+
+
+    public boolean equals( Object o )
+    {
+        if ( o == this )
+        {
+            return true;
+        }
+
+        if ( o == null )
+        {
+            return false;
+        }
+
+        try
+        {
+            Cardinality c = ( Cardinality ) o;
+
+            return ( min == c.min ) && ( max == c.max );
+        }
+        catch ( ClassCastException cce )
+        {
+            return false;
+        }
+    }
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/ExprVisitor.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/ExprVisitor.java
new file mode 100644
index 0000000..c208f87
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/ExprVisitor.java
@@ -0,0 +1,40 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+public interface ExprVisitor
+{
+
+    void visitAnd( And a );
+
+
+    void visitOr( Or o );
+
+
+    void visitNot( Not n );
+
+
+    void visitSimple( SimpleTerm st );
+
+
+    // if none of the above matches use this
+    void visitAny( LDAPExpr ex );
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Expressions.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Expressions.java
new file mode 100644
index 0000000..6dab3ae
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Expressions.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.sigil.common.osgi;
+
+
+import java.util.Map;
+
+
+public class Expressions
+{
+
+    public static LDAPExpr and( LDAPExpr... terms )
+    {
+        return And.apply( terms );
+    }
+
+
+    public static LDAPExpr or( LDAPExpr... terms )
+    {
+        return Or.apply( terms );
+    }
+
+
+    public static LDAPExpr not( LDAPExpr e )
+    {
+        return Not.apply( e );
+    }
+
+    public static LDAPExpr T = Bool.TRUE;
+    public static LDAPExpr F = Bool.FALSE;
+
+
+    // supports direct use of wildcards for ease of testing, but not literal *s
+    public static SimpleTerm ex( String name, Ops op, String rhs )
+    {
+
+        rhs = rhs.replace( '*', SimpleTerm.WILDCARD );
+        return new SimpleTerm( name, op, rhs );
+    }
+
+}
+
+class Bool implements LDAPExpr
+{
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+    public static final Bool TRUE = new Bool( true );
+    public static final Bool FALSE = new Bool( false );
+
+    private boolean bool;
+
+
+    public Bool( boolean bool )
+    {
+        this.bool = bool;
+    }
+
+
+    public boolean eval( Map<String, ?> map )
+    {
+        return bool;
+    }
+
+
+    public void visit( ExprVisitor v )
+    {
+    }
+
+
+    public LDAPExpr[] getChildren()
+    {
+        return CHILDLESS;
+    }
+
+
+    public String toString()
+    {
+        return "(" + bool + ")";
+    }
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/FilterValidator.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/FilterValidator.java
new file mode 100644
index 0000000..7b21e3d
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/FilterValidator.java
@@ -0,0 +1,40 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+public interface FilterValidator
+{
+
+    public static FilterValidator ACCEPT_ALL = new AcceptEverythingValidator();
+
+
+    boolean validate( LDAPExpr filter );
+
+    static class AcceptEverythingValidator implements FilterValidator
+    {
+
+        public boolean validate( LDAPExpr filter )
+        {
+            return true;
+        }
+
+    }
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/LDAPExpr.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/LDAPExpr.java
new file mode 100644
index 0000000..7d911b6
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/LDAPExpr.java
@@ -0,0 +1,48 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+import java.io.Serializable;
+import java.util.Map;
+
+
+public interface LDAPExpr extends Serializable
+{
+
+    public static final LDAPExpr[] CHILDLESS = new LDAPExpr[]
+        {};
+    public static LDAPExpr ACCEPT_ALL = Expressions.T;
+
+
+    LDAPExpr[] getChildren();
+
+
+    void visit( ExprVisitor v );
+
+
+    boolean equals( Object other );
+
+
+    int hashCode();
+
+
+    boolean eval( Map<String, ?> map );
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/LDAPParseException.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/LDAPParseException.java
new file mode 100644
index 0000000..109a791
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/LDAPParseException.java
@@ -0,0 +1,68 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+public class LDAPParseException extends Exception
+{
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+
+    private ParseState ps;
+    private static final String LINE_SEPARATOR = System.getProperty( "line.separator", "\\r\\n" );
+
+
+    public LDAPParseException( String message, ParseState ps )
+    {
+        super( message );
+        this.ps = ps;
+    }
+
+
+    public LDAPParseException( String message )
+    {
+        super( message );
+    }
+
+
+    @Override
+    public String getMessage()
+    {
+        if ( ps == null )
+        {
+            return super.getMessage();
+        }
+
+        String basicMessage = super.getMessage();
+        StringBuffer buf = new StringBuffer( basicMessage.length() + ps.str.length() * 2 );
+        buf.append( basicMessage ).append( LINE_SEPARATOR );
+        buf.append( ps.str ).append( LINE_SEPARATOR );
+        for ( int i = 0; i < ps.pos; i++ )
+        {
+            buf.append( " " );
+        }
+        buf.append( "^" );
+        return buf.toString();
+    }
+
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/LDAPParser.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/LDAPParser.java
new file mode 100644
index 0000000..11086dd
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/LDAPParser.java
@@ -0,0 +1,276 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+import static org.apache.felix.sigil.common.osgi.Expressions.and;
+import static org.apache.felix.sigil.common.osgi.Expressions.not;
+import static org.apache.felix.sigil.common.osgi.Expressions.or;
+import static org.apache.felix.sigil.common.osgi.Ops.APPROX;
+import static org.apache.felix.sigil.common.osgi.Ops.EQ;
+import static org.apache.felix.sigil.common.osgi.Ops.GE;
+import static org.apache.felix.sigil.common.osgi.Ops.GT;
+import static org.apache.felix.sigil.common.osgi.Ops.LE;
+import static org.apache.felix.sigil.common.osgi.Ops.LT;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class LDAPParser
+{
+
+    private static final LDAPParser parser = new LDAPParser();
+
+
+    public static LDAPExpr parseExpression( String strExpr ) throws LDAPParseException
+    {
+        return parser.parse( strExpr );
+    }
+
+
+    public static void main( String[] args )
+    {
+        for ( String arg : args )
+        {
+            try
+            {
+                System.out.println( parseExpression( arg ) );
+            }
+            catch ( LDAPParseException e )
+            {
+                System.out.println( "Failed to parse " + arg );
+                e.printStackTrace();
+            }
+        }
+    }
+
+
+    public LDAPExpr parse( String strExpr ) throws LDAPParseException
+    {
+
+        if ( strExpr == null || strExpr.trim().length() == 0 )
+        {
+            return LDAPExpr.ACCEPT_ALL;
+        }
+
+        ParseState ps = new ParseState( strExpr );
+        LDAPExpr expr = parseExpr( ps );
+        ps.skipWhitespace();
+        if ( !ps.isEndOfString() )
+        {
+            error( "expected end of expression ", ps );
+        }
+        return expr;
+    }
+
+
+    public LDAPExpr parseExpr( ParseState ps ) throws LDAPParseException
+    {
+        ps.skipWhitespace();
+        if ( !( ps.peek() == '(' ) )
+        {
+            error( "expected (", ps );
+        }
+        ps.read();
+        LDAPExpr expr = null;
+        ps.skipWhitespace();
+        char ch = ps.peek();
+        switch ( ch )
+        {
+            case '&':
+                ps.readAndSkipWhiteSpace();
+                List<LDAPExpr> andList = new ArrayList<LDAPExpr>();
+                while ( ps.peek() == '(' )
+                {
+                    andList.add( parseExpr( ps ) );
+                    ps.skipWhitespace();
+                }
+                LDAPExpr[] andArr = andList.toArray( new LDAPExpr[andList.size()] );
+                expr = and( andArr );
+                break;
+            case '|':
+                ps.readAndSkipWhiteSpace();
+                List<LDAPExpr> orList = new ArrayList<LDAPExpr>();
+                while ( ps.peek() == '(' )
+                {
+                    orList.add( parseExpr( ps ) );
+                    ps.skipWhitespace();
+                }
+                LDAPExpr[] orArray = orList.toArray( new LDAPExpr[orList.size()] );
+                expr = or( orArray );
+                break;
+            case '!':
+                ps.readAndSkipWhiteSpace();
+                expr = not( parseExpr( ps ) );
+                break;
+            default:
+                if ( isNameChar( ch ) )
+                {
+                    expr = parseSimple( ps );
+                }
+                else
+                {
+                    error( "unexpected character: '" + ch + "'", ps );
+                }
+        }
+        ps.skipWhitespace();
+        if ( ps.peek() != ')' )
+        {
+            error( "expected )", ps );
+        }
+        ps.read();
+        return expr;
+
+    }
+
+
+    void error( String message, ParseState ps ) throws LDAPParseException
+    {
+        throw new LDAPParseException( message, ps );
+    }
+
+
+    private SimpleTerm parseSimple( ParseState ps ) throws LDAPParseException
+    {
+        // read name
+        StringBuffer name = new StringBuffer( 16 );
+        for ( char c = ps.peek(); !ps.isEndOfString() && isNameChar( c ); c = ps.peek() )
+        {
+            ps.read();
+            name.append( c );
+        }
+        ps.skipWhitespace();
+        Ops op = null;
+        // read op
+        if ( ps.lookingAt( "=" ) )
+        {
+            op = EQ;
+            ps.skip( 1 );
+        }
+        else if ( ps.lookingAt( ">=" ) )
+        {
+            op = GE;
+            ps.skip( 2 );
+        }
+        else if ( ps.lookingAt( "<=" ) )
+        {
+            op = LE;
+            ps.skip( 2 );
+        }
+        else if ( ps.lookingAt( ">" ) )
+        {
+            op = GT;
+            ps.skip( 1 );
+        }
+        else if ( ps.lookingAt( "<" ) )
+        {
+            op = LT;
+            ps.skip( 1 );
+        }
+        else if ( ps.lookingAt( "-=" ) )
+        {
+            op = APPROX;
+            ps.skip( 2 );
+        }
+        else if ( ps.isEndOfString() )
+        {
+            error( "unexpected end of expression", ps );
+        }
+        else
+        {
+            error( "unexpected character: '" + ps.peek() + "'", ps );
+        }
+        ps.skipWhitespace();
+
+        boolean escaped = false;
+        StringBuffer value = new StringBuffer( 16 );
+
+        while ( !ps.isEndOfString() && !Character.isWhitespace( ps.peek() ) && !( ps.peek() == ')' && !escaped ) )
+        {
+
+            char ch = ps.peek();
+
+            if ( ch == '\\' )
+            {
+                escaped = true;
+                ps.read();
+            }
+            else if ( ch == '*' )
+            {
+                if ( escaped )
+                {
+                    value.append( ch );
+                    escaped = false;
+                }
+                else
+                {
+                    value.append( SimpleTerm.WILDCARD );
+                }
+                ps.read();
+            }
+            else if ( isLiteralValue( ch ) )
+            {
+                if ( escaped )
+                {
+                    error( "incorrectly applied escape of '" + ch + "'", ps );
+                }
+                value.append( ps.read() );
+            }
+            else if ( isEscapedValue( ch ) )
+            {
+                if ( !escaped )
+                {
+                    error( "missing escape for '" + ch + "'", ps );
+                }
+                value.append( ps.read() );
+                escaped = false;
+            }
+            else
+            {
+                error( "unexpected character: '" + ps.peek() + "'", ps );
+            }
+        }
+        ps.skipWhitespace();
+
+        SimpleTerm expr = new SimpleTerm( name.toString(), op, value.toString() );
+
+        return expr;
+    }
+
+
+    private boolean isNameChar( int ch )
+    {
+        return !( Character.isWhitespace( ch ) || ( ch == '(' ) || ( ch == ')' ) || ( ch == '<' ) || ( ch == '>' )
+            || ( ch == '=' ) || ( ch == '~' ) || ( ch == '*' ) || ( ch == '\\' ) );
+    }
+
+
+    private boolean isLiteralValue( int ch )
+    {
+        return !( Character.isWhitespace( ch ) || ( ch == '(' ) || ( ch == ')' ) || ( ch == '*' ) );
+    }
+
+
+    private boolean isEscapedValue( int ch )
+    {
+        return ( ch == '(' ) || ( ch == ')' ) || ( ch == '*' );
+    }
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Not.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Not.java
new file mode 100644
index 0000000..661bd43
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Not.java
@@ -0,0 +1,114 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+import java.util.Map;
+
+
+public class Not implements LDAPExpr
+{
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+    private LDAPExpr[] children;
+
+
+    public static LDAPExpr apply( LDAPExpr e )
+    {
+        if ( e == null )
+        {
+            throw new NullPointerException( "cannot apply Not to a null expression" );
+        }
+        if ( e.equals( Expressions.T ) )
+        {
+            return Expressions.F;
+        }
+        if ( e.equals( Expressions.F ) )
+        {
+            return Expressions.T;
+        }
+        return new Not( e );
+    }
+
+
+    private Not( LDAPExpr child )
+    {
+        this.children = new LDAPExpr[]
+            { child };
+    }
+
+
+    public boolean eval( Map<String, ?> map )
+    {
+        return !children[0].eval( map );
+    }
+
+
+    public LDAPExpr getEx()
+    {
+        return children[0];
+    }
+
+
+    public void visit( ExprVisitor v )
+    {
+        v.visitNot( this );
+    }
+
+
+    public LDAPExpr[] getChildren()
+    {
+        return children;
+    }
+
+
+    public void setChild( LDAPExpr child )
+    {
+        this.children = new LDAPExpr[]
+            { child };
+    }
+
+
+    @Override
+    public boolean equals( Object other )
+    {
+        if ( other instanceof Not )
+        {
+            Not that = ( Not ) other;
+            return children[0].equals( that.children[0] );
+        }
+        return false;
+    }
+
+
+    @Override
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer( 256 );
+        buf.append( "(!" );
+        buf.append( " " ).append( children[0] ).append( " " );
+        buf.append( ")" );
+        return buf.toString();
+    }
+
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Ops.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Ops.java
new file mode 100644
index 0000000..200e497
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Ops.java
@@ -0,0 +1,49 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+public enum Ops
+{
+    EQ, GE, LE, GT, LT, APPROX;
+
+    @Override
+    public String toString()
+    {
+        switch ( this )
+        {
+            case EQ:
+                return "=";
+            case GE:
+                return ">=";
+            case LE:
+                return "<=";
+            case GT:
+                return ">";
+            case LT:
+                return "<";
+            case APPROX:
+                return "~=";
+            default:
+                return super.toString();
+        }
+    }
+
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Or.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Or.java
new file mode 100644
index 0000000..0d2cc03
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Or.java
@@ -0,0 +1,149 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+import java.util.Map;
+
+
+public class Or implements LDAPExpr
+{
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+    private LDAPExpr[] children;
+
+
+    public static LDAPExpr apply( LDAPExpr... terms )
+    {
+        if ( terms == null )
+        {
+            throw new NullPointerException( "terms cannot be null" );
+        }
+        else if ( terms.length == 0 )
+        {
+            return Expressions.T;
+        }
+        else if ( terms.length == 1 )
+        {
+            return terms[0];
+        }
+        LDAPExpr[] filtered = new LDAPExpr[terms.length];
+        int ctr = 0;
+        for ( int i = 0; i < terms.length; i++ )
+        {
+            if ( terms[i].equals( Expressions.T ) )
+                return Expressions.T;
+            if ( terms[i].equals( Expressions.F ) )
+                continue;
+            filtered[ctr] = terms[i];
+            ctr++;
+        }
+        if ( ctr == 0 )
+        {
+            return Expressions.F;
+        }
+        else if ( ctr == 1 )
+        {
+            return filtered[0];
+        }
+        LDAPExpr[] orTerms = new LDAPExpr[ctr];
+        System.arraycopy( filtered, 0, orTerms, 0, ctr );
+
+        return new Or( orTerms );
+    }
+
+
+    private Or( LDAPExpr... children )
+    {
+        this.children = children;
+    }
+
+
+    public boolean eval( Map<String, ?> map )
+    {
+        for ( int i = 0; i < children.length; i++ )
+        {
+            if ( children[i].eval( map ) )
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    public void visit( ExprVisitor v )
+    {
+        v.visitOr( this );
+    }
+
+
+    public LDAPExpr[] getChildren()
+    {
+        return children;
+    }
+
+
+    public void setChildren( LDAPExpr[] children )
+    {
+        this.children = children;
+    }
+
+
+    @Override
+    public boolean equals( Object other )
+    {
+        if ( other instanceof Or )
+        {
+            Or that = ( Or ) other;
+            if ( children.length != that.children.length )
+            {
+                return false;
+            }
+            for ( int i = 0; i < children.length; i++ )
+            {
+                if ( children[i].equals( that.children[i] ) )
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+        return false;
+    }
+
+
+    @Override
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer( 256 );
+        buf.append( "(|" );
+        for ( int i = 0; i < children.length; i++ )
+        {
+            buf.append( " " ).append( children[i] ).append( " " );
+        }
+        buf.append( ")" );
+        return buf.toString();
+    }
+
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/ParseState.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/ParseState.java
new file mode 100644
index 0000000..46e98fd
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/ParseState.java
@@ -0,0 +1,105 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+import java.io.Serializable;
+
+
+/**
+ * @author dave
+ * 
+ */
+class ParseState implements Serializable
+{
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+
+    int pos;
+
+    String str;
+
+
+    ParseState( String str )
+    {
+        this.str = str;
+    }
+
+
+    public boolean lookingAt( String start )
+    {
+        return str.substring( pos ).startsWith( start );
+    }
+
+
+    public CharSequence skip( int n )
+    {
+        int end = pos + n < str.length() ? pos + n : str.length();
+        int start = pos;
+        pos = end;
+        return str.subSequence( start, end );
+    }
+
+
+    public char read()
+    {
+        char ch = str.charAt( pos );
+        if ( pos < str.length() )
+        {
+            pos++;
+        }
+        return ch;
+    }
+
+
+    public char readAndSkipWhiteSpace()
+    {
+        char ch = read();
+        skipWhitespace();
+        return ch;
+    }
+
+
+    char peek()
+    {
+        if ( isEndOfString() )
+        {
+            return ( char ) -1;
+        }
+        return str.charAt( pos );
+    }
+
+
+    boolean isEndOfString()
+    {
+        return pos == str.length();
+    }
+
+
+    void skipWhitespace()
+    {
+        while ( pos < str.length() && Character.isWhitespace( str.charAt( pos ) ) )
+        {
+            pos++;
+        }
+    }
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/SimpleTerm.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/SimpleTerm.java
new file mode 100644
index 0000000..0ebd791
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/SimpleTerm.java
@@ -0,0 +1,353 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+import java.lang.reflect.Constructor;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+
+
+public class SimpleTerm implements LDAPExpr
+{
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+    public static final char WILDCARD = 2 ^ 16 - 1;
+    private static final String WILDCARD_STRING = new String( new char[]
+        { SimpleTerm.WILDCARD } );
+
+    private Ops op;
+    private String name;
+    private String rval;
+
+
+    public SimpleTerm( String name, Ops op, String value )
+    {
+        this.op = op;
+        this.name = name.intern();
+        this.rval = value.intern();
+    }
+
+
+    public String getName()
+    {
+        return name;
+    }
+
+
+    public Ops getOp()
+    {
+        return op;
+    }
+
+
+    public String getRval()
+    {
+        return rval;
+    }
+
+
+    public boolean eval( Map<String, ?> map )
+    {
+
+        Object lval = map.get( name );
+        if ( lval == null )
+        {
+            return false;
+        }
+        else if ( Ops.EQ == op && WILDCARD_STRING.equals( lval ) )
+        {
+            return true;
+        }
+        // any match in the vector will do
+        else if ( lval instanceof Vector<?> )
+        {
+            Vector<?> vec = ( Vector<?> ) lval;
+            for ( Iterator<?> i = vec.iterator(); i.hasNext(); )
+            {
+                if ( check( i.next() ) )
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+        // any match in the array will do
+        else if ( lval instanceof Object[] )
+        {
+            Object[] arr = ( Object[] ) lval;
+            for ( int i = 0; i < arr.length; i++ )
+            {
+                if ( check( arr[i] ) )
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+        return check( lval );
+    }
+
+
+    @SuppressWarnings("unchecked")
+    private boolean check( Object lval )
+    {
+        if ( lval == null )
+        {
+            return false;
+        }
+        else if ( Ops.EQ == op && WILDCARD_STRING.equals( lval ) )
+        {
+            return true;
+        }
+
+        Object rhs = null;
+
+        if ( lval instanceof String )
+        {
+
+            if ( Ops.APPROX == op )
+            {
+                rhs = collapseWhiteSpace( rval );
+                lval = collapseWhiteSpace( ( String ) lval );
+            }
+
+            if ( Ops.EQ == op || Ops.APPROX == op )
+            {
+                return stringCheck( ( String ) lval );
+            }
+            // rhs already a string
+
+        }
+        else if ( lval.getClass() == Byte.class )
+        {
+            rhs = Byte.valueOf( rval );
+        }
+        else if ( lval.getClass() == Short.class )
+        {
+            rhs = Short.valueOf( rval );
+        }
+        else if ( lval.getClass() == Integer.class )
+        {
+            rhs = Integer.valueOf( rval );
+        }
+        else if ( lval.getClass() == Long.class )
+        {
+            rhs = Long.valueOf( rval );
+        }
+        else if ( lval.getClass() == Float.class )
+        {
+            rhs = Float.valueOf( rval );
+        }
+        else if ( lval.getClass() == Double.class )
+        {
+            rhs = Double.valueOf( rval );
+        }
+        else
+        {
+            try
+            {
+                Constructor<?> stringCtor = lval.getClass().getConstructor( new Class[]
+                    { String.class } );
+                rhs = stringCtor.newInstance( rval );
+            }
+            catch ( Exception e )
+            {
+                // log it
+                e.printStackTrace();
+                return false;
+            }
+        }
+
+        if ( !( lval instanceof Comparable ) )
+        {
+            return Ops.EQ == op && lval.equals( rval );
+        }
+        else
+        {
+
+            Comparable<? super Object> lhs = ( Comparable<? super Object> ) lval;
+
+            int compare = lhs.compareTo( rhs );
+
+            switch ( op )
+            {
+                case EQ:
+                    return compare == 0;
+                case APPROX:
+                    return compare == 0;
+                case GE:
+                    return compare >= 0;
+                case LE:
+                    return compare <= 0;
+                case GT:
+                    return compare > 0;
+                case LT:
+                    return compare < 0;
+            }
+        }
+
+        return false;
+    }
+
+
+    private boolean stringCheck( String lhs )
+    {
+
+        String rhs;
+        switch ( op )
+        {
+            case EQ:
+            case APPROX:
+                rhs = rval;
+                break;
+            default:
+                return false;
+        }
+
+        int valLength = lhs.length();
+        int patLength = rval.length();
+
+        if ( valLength == 0 && patLength == 0 )
+        {
+            return true;
+        }
+
+        boolean wc = false;
+        int j = 0;
+        for ( int i = 0; i < patLength; i++ )
+        {
+            // trailing wildcards
+            char pc = rhs.charAt( i );
+            if ( j == valLength )
+            {
+                if ( pc != SimpleTerm.WILDCARD )
+                {
+                    return false;
+                }
+                continue;
+            }
+            if ( pc == SimpleTerm.WILDCARD )
+            {
+                wc = true;
+                continue;
+            }
+            while ( wc && j < valLength - 1 && lhs.charAt( j ) != pc )
+            {
+                j++;
+            }
+            if ( lhs.charAt( j ) != pc )
+            {
+                return false;
+            }
+            else
+            {
+                wc = false;
+                j++;
+            }
+        }
+        return ( wc || j == valLength );
+
+    }
+
+
+    private String collapseWhiteSpace( String in )
+    {
+        StringBuffer out = new StringBuffer( in.trim().length() );
+        boolean white = false;
+        for ( int i = 0; i < in.length(); i++ )
+        {
+            char ch = in.charAt( i );
+            if ( Character.isWhitespace( ch ) )
+            {
+                white = true;
+            }
+            else
+            {
+                if ( white )
+                {
+                    out.append( " " );
+                    white = false;
+                }
+                out.append( ch );
+            }
+        }
+        return out.toString();
+    }
+
+
+    public void visit( ExprVisitor v )
+    {
+        v.visitSimple( this );
+    }
+
+
+    public LDAPExpr[] getChildren()
+    {
+        return CHILDLESS;
+    }
+
+
+    @Override
+    public boolean equals( Object other )
+    {
+        if ( other instanceof SimpleTerm )
+        {
+            SimpleTerm that = ( SimpleTerm ) other;
+            return name.equals( that.name ) && op.equals( that.op ) && rval.equals( that.rval );
+        }
+        return false;
+    }
+
+
+    @Override
+    public String toString()
+    {
+        return "(" + name + " " + op.toString() + " " + escape( rval ) + ")";
+    }
+
+
+    private String escape( String raw )
+    {
+        StringBuffer buf = new StringBuffer( raw.length() + 10 );
+        for ( int i = 0; i < raw.length(); i++ )
+        {
+            char ch = raw.charAt( i );
+            switch ( ch )
+            {
+                case SimpleTerm.WILDCARD:
+                    buf.append( "*" );
+                    break;
+                case '(':
+                case ')':
+                case '*':
+                    buf.append( "\\" ).append( ch );
+                    break;
+                default:
+                    buf.append( ch );
+            }
+        }
+        return buf.toString();
+    }
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Utils.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Utils.java
new file mode 100644
index 0000000..9720702
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/Utils.java
@@ -0,0 +1,85 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+public class Utils
+{
+    public static MapBuilder map( String name, Object value )
+    {
+        return new MapBuilder().put( name, value );
+    }
+
+
+    public static String toString( Map<String, Object> attrs )
+    {
+        if ( attrs == null )
+        {
+            return "NULL";
+        }
+
+        StringBuffer buf = new StringBuffer( 128 );
+        List<String> keys = new ArrayList<String>( attrs.keySet() );
+        Collections.sort( keys );
+        buf.append( "{" );
+
+        for ( int i = 0; i < keys.size(); i++ )
+        {
+            Object name = keys.get( i );
+            Object value = attrs.get( name );
+            buf.append( name ).append( "=" ).append( value ).append( "," );
+        }
+
+        if ( buf.length() > 1 )
+        {
+            buf.delete( buf.length() - 1, buf.length() );
+        }
+
+        buf.append( "}" );
+
+        return buf.toString();
+    }
+
+    public static class MapBuilder
+    {
+        private Map<String, Object> map = new HashMap<String, Object>();
+
+
+        public MapBuilder put( String name, Object value )
+        {
+            map.put( name, value );
+
+            return this;
+        }
+
+
+        public Map<String, Object> toMap()
+        {
+            return map;
+        }
+    }
+}
diff --git a/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/VersionRange.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/VersionRange.java
new file mode 100644
index 0000000..a4a1558
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/VersionRange.java
@@ -0,0 +1,303 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+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 );
+
+    private boolean openFloor;
+    private Version floor;
+    private Version ceiling;
+    private 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;
+    }
+
+
+    /**
+     * atLeast constructor
+     * 
+     * @param openFloor
+     * @param floor
+     */
+    public VersionRange( Version atLeast )
+    {
+        this.openFloor = false;
+        this.floor = atLeast;
+        this.ceiling = INFINITE_VERSION;
+        this.openCeiling = true;
+    }
+
+
+    public static VersionRange parseVersionRange( String val ) throws IllegalArgumentException, NumberFormatException
+    {
+        if ( val == null || val.trim().length() == 0 )
+        {
+            return ANY_VERSION;
+        }
+
+        boolean openFloor;
+        boolean openCeiling;
+        val = val.replaceAll( "\\s", "" );
+        val = val.replaceAll( "\"", "" );
+        int fst = val.charAt( 0 );
+        if ( fst == '[' )
+        {
+            openFloor = false;
+        }
+        else if ( fst == '(' )
+        {
+            openFloor = true;
+        }
+        else
+        {
+            Version atLeast = Version.parseVersion( val );
+            return new VersionRange( atLeast );
+        }
+
+        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" );
+        }
+        Version floor = Version.parseVersion( floorCeiling[0] );
+        Version ceiling = "*".equals( floorCeiling[1] ) ? INFINITE_VERSION : Version.parseVersion( floorCeiling[1] );
+        return new VersionRange( openFloor, floor, ceiling, openCeiling );
+    }
+
+
+    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 ) );
+        }
+    }
+
+
+    @Override
+    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;
+    }
+
+
+    @Override
+    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;
+    }
+
+
+    @Override
+    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, VersionRangeBoundingRule lowerBoundRule,
+        VersionRangeBoundingRule upperBoundRule )
+    {
+        Version floor = null;
+        switch ( lowerBoundRule )
+        {
+            case Any:
+                floor = new Version( 0, 0, 0 );
+                break;
+            case Major:
+                floor = new Version( pointVersion.getMajor(), 0, 0 );
+                break;
+            case Minor:
+                floor = new Version( pointVersion.getMajor(), pointVersion.getMinor(), 0 );
+                break;
+            case Micro:
+                floor = new Version( 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 = new Version( pointVersion.getMajor() + 1, 0, 0 );
+                break;
+            case Minor:
+                ceiling = new Version( pointVersion.getMajor(), pointVersion.getMinor() + 1, 0 );
+                break;
+            case Micro:
+                ceiling = new Version( 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/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/VersionRangeBoundingRule.java b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/VersionRangeBoundingRule.java
new file mode 100644
index 0000000..5ee45da
--- /dev/null
+++ b/sigil/common/osgi/src/org/apache/felix/sigil/common/osgi/VersionRangeBoundingRule.java
@@ -0,0 +1,26 @@
+/*
+ * 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.sigil.common.osgi;
+
+
+public enum VersionRangeBoundingRule
+{
+    Exact, Micro, Minor, Major, Any
+}
\ No newline at end of file