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 2018/04/20 09:23:35 UTC

svn commit: r1829639 [1/3] - in /felix/trunk: bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/ bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/ utils/ utils/src/main/java/org/apache/felix/utils/collections/ u...

Author: gnodet
Date: Fri Apr 20 09:23:35 2018
New Revision: 1829639

URL: http://svn.apache.org/viewvc?rev=1829639&view=rev
Log:
Provide optimized resource / filter / capability / requirement / capability set

Added:
    felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyStringMap.java
    felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyStringMapTest.java
      - copied, changed from r1829637, felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyHashMapTest.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/StringArrayMap.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/ResourceBuilder.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/ResourceImpl.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/SimpleFilter.java
    felix/trunk/utils/src/test/java/org/apache/felix/utils/collections/
    felix/trunk/utils/src/test/java/org/apache/felix/utils/collections/StringArrayMapTest.java
    felix/trunk/utils/src/test/java/org/apache/felix/utils/resource/SimpleFilterTest.java
Removed:
    felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyHashMap.java
    felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyHashMapTest.java
Modified:
    felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FelixResourceAdapter.java
    felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImpl.java
    felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImplTest.java
    felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryXMLTest.java
    felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRequirementAdapterTest.java
    felix/trunk/utils/pom.xml
    felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/AbstractCapabilityRequirement.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java
    felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java
    felix/trunk/utils/src/test/java/org/apache/felix/utils/resource/CapabilityImplTest.java
    felix/trunk/utils/src/test/java/org/apache/felix/utils/resource/RequirementImplTest.java

Modified: felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FelixResourceAdapter.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FelixResourceAdapter.java?rev=1829639&r1=1829638&r2=1829639&view=diff
==============================================================================
--- felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FelixResourceAdapter.java (original)
+++ felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FelixResourceAdapter.java Fri Apr 20 09:23:35 2018
@@ -42,12 +42,12 @@ public class FelixResourceAdapter implem
 
         if (namespace == null || namespace.equals(IdentityNamespace.IDENTITY_NAMESPACE))
         {
-            CapabilityImpl c = OSGiRepositoryImpl.newOSGiIdentityCapability(resource, this);
+            CapabilityImpl c = OSGiRepositoryImpl.newOSGiIdentityCapability(this, resource);
             result.add(c);
         }
         if (namespace == null || namespace.equals(ContentNamespace.CONTENT_NAMESPACE))
         {
-            CapabilityImpl c = OSGiRepositoryImpl.newOSGiContentCapability(resource, this);
+            CapabilityImpl c = OSGiRepositoryImpl.newOSGiContentCapability(this, resource);
             result.add(c);
         }
 

Added: felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyStringMap.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyStringMap.java?rev=1829639&view=auto
==============================================================================
--- felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyStringMap.java (added)
+++ felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyStringMap.java Fri Apr 20 09:23:35 2018
@@ -0,0 +1,67 @@
+/*
+ * 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.bundlerepository.impl;
+
+import java.util.Map;
+
+import org.apache.felix.utils.collections.StringArrayMap;
+
+/**
+ * A map that can delay the computation of certain values up until the moment that they
+ * are actually needed. Useful for expensive to compute values such as the SHA-256.
+ * This map does <b>not</b> support {@code null} values.
+ */
+@SuppressWarnings("serial")
+public class LazyStringMap<V> extends StringArrayMap<V>
+{
+    public LazyStringMap(Map<String, ? extends V> map) {
+        super(map);
+    }
+
+    public LazyStringMap() {
+    }
+
+    public LazyStringMap(int capacity) {
+        super(capacity);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public V get(Object key)
+    {
+        V val = super.get(key);
+        if (val instanceof LazyValue) {
+            val = ((LazyValue<V>) val).compute();
+            if (val == null) {
+                throw new NullPointerException("Lazy computed values may not be null");
+            }
+            put((String) key, val);
+        }
+        return val;
+    }
+
+    public void putLazy(String key, LazyValue<V> lazy) {
+        super.doPut(key, lazy);
+    }
+
+    public interface LazyValue<V>
+    {
+        V compute();
+    }
+}

Modified: felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImpl.java?rev=1829639&r1=1829638&r2=1829639&view=diff
==============================================================================
--- felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImpl.java (original)
+++ felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImpl.java Fri Apr 20 09:23:35 2018
@@ -30,10 +30,9 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.Callable;
 
 import org.apache.felix.bundlerepository.RepositoryAdmin;
-import org.apache.felix.bundlerepository.impl.LazyHashMap.LazyValue;
+import org.apache.felix.bundlerepository.Resource;
 import org.apache.felix.utils.resource.CapabilityImpl;
 import org.osgi.framework.Filter;
 import org.osgi.framework.FrameworkUtil;
@@ -41,7 +40,6 @@ import org.osgi.framework.namespace.Iden
 import org.osgi.resource.Capability;
 import org.osgi.resource.Namespace;
 import org.osgi.resource.Requirement;
-import org.osgi.resource.Resource;
 import org.osgi.service.repository.ContentNamespace;
 import org.osgi.service.repository.Repository;
 
@@ -120,8 +118,7 @@ class OSGiRepositoryImpl implements Repo
         caps.add(idCap);
     }
 
-    static CapabilityImpl newOSGiIdentityCapability(org.apache.felix.bundlerepository.Resource res,
-            org.osgi.resource.Resource targetResource)
+    static CapabilityImpl newOSGiIdentityCapability(org.osgi.resource.Resource or, org.apache.felix.bundlerepository.Resource res)
     {
         @SuppressWarnings("unchecked")
         Map<String, Object> idAttrs = new HashMap<String, Object>(res.getProperties());
@@ -132,32 +129,34 @@ class OSGiRepositoryImpl implements Repo
         if (idAttrs.get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE) == null)
             idAttrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, IdentityNamespace.TYPE_BUNDLE);
 
-        return new CapabilityImpl(IdentityNamespace.IDENTITY_NAMESPACE, idAttrs, Collections.<String, String> emptyMap(), targetResource);
+        return new CapabilityImpl(or, IdentityNamespace.IDENTITY_NAMESPACE, Collections.<String, String> emptyMap(), idAttrs);
     }
 
-    static CapabilityImpl newOSGiContentCapability(org.apache.felix.bundlerepository.Resource resource,
-            org.osgi.resource.Resource targetResource)
+    static CapabilityImpl newOSGiContentCapability(org.osgi.resource.Resource or, Resource resource)
     {
         final String uri = resource.getURI();
-        LazyValue<String, Object> lazyValue =
-            new LazyValue<String, Object>(ContentNamespace.CONTENT_NAMESPACE, new Callable<Object>()
-            {
-                public Object call() throws Exception
-                {
-                    // This is expensive to do, so only compute it when actually obtained...
+        LazyStringMap.LazyValue<String> content = new LazyStringMap.LazyValue<String>() {
+            public String compute() {
+                // This is expensive to do, so only compute it when actually obtained...
+                try {
                     return OSGiRepositoryImpl.getSHA256(uri);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                } catch (NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
                 }
-            });
-
+            }
+        };
         Object mime = resource.getProperties().get("mime");
         if (mime == null)
             mime = "application/vnd.osgi.bundle";
 
-        Map<String, Object> contentAttrs = new LazyHashMap<String, Object>(Collections.singleton(lazyValue));
+        Map<String, Object> contentAttrs = new LazyStringMap<Object>(4);
         contentAttrs.put(ContentNamespace.CAPABILITY_MIME_ATTRIBUTE, mime);
         contentAttrs.put(ContentNamespace.CAPABILITY_SIZE_ATTRIBUTE, resource.getSize());
         contentAttrs.put(ContentNamespace.CAPABILITY_URL_ATTRIBUTE, uri);
-        return new ContentCapabilityImpl(contentAttrs, targetResource);
+        contentAttrs.put(ContentNamespace.CONTENT_NAMESPACE, content);
+        return new CapabilityImpl(or, ContentNamespace.CONTENT_NAMESPACE, Collections.<String, String> emptyMap(), contentAttrs);
     }
 
     static String getSHA256(String uri) throws IOException, NoSuchAlgorithmException // TODO find a good place for this
@@ -183,19 +182,4 @@ class OSGiRepositoryImpl implements Repo
         return sb.toString();
     }
 
-    // This capability variant does not take a private copy of the capabilities so that it
-    // can lazily compute the content hash.
-    private static class ContentCapabilityImpl extends CapabilityImpl implements Capability {
-        private final Map<String, Object> contentAttributes;
-
-        public ContentCapabilityImpl(Map<String, Object> contentAttrs, Resource targetResource) {
-            super(ContentNamespace.CONTENT_NAMESPACE, null, null, targetResource);
-            contentAttributes = Collections.unmodifiableMap(contentAttrs);
-        }
-
-        @Override
-        public Map<String, Object> getAttributes() {
-            return contentAttributes;
-        }
-    }
 }

Copied: felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyStringMapTest.java (from r1829637, felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyHashMapTest.java)
URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyStringMapTest.java?p2=felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyStringMapTest.java&p1=felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyHashMapTest.java&r1=1829637&r2=1829639&rev=1829639&view=diff
==============================================================================
--- felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyHashMapTest.java (original)
+++ felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyStringMapTest.java Fri Apr 20 09:23:35 2018
@@ -21,41 +21,39 @@ package org.apache.felix.bundlerepositor
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import junit.framework.TestCase;
+import org.apache.felix.bundlerepository.impl.LazyStringMap.LazyValue;
 
-import org.apache.felix.bundlerepository.impl.LazyHashMap.LazyValue;
-
-public class LazyHashMapTest extends TestCase
+public class LazyStringMapTest extends TestCase
 {
     public void testLazyHashMap() {
         final AtomicInteger lv1Computed = new AtomicInteger(0);
-        LazyValue<String, Long> lv1 = new LazyValue<String, Long>("42", new Callable<Long>()
-        {
-            public Long call() throws Exception
-            {
+        LazyValue<Long> lv1 = new LazyValue<Long>() {
+            public Long compute() {
                 lv1Computed.incrementAndGet();
                 return 24L;
             }
-        });
+        };
 
         final AtomicInteger lv2Computed = new AtomicInteger(0);
-        LazyValue<String, Long> lv2 = new LazyValue<String, Long>("zero", new Callable<Long>()
-        {
-            public Long call() throws Exception
-            {
+        LazyValue<Long> lv2 = new LazyValue<Long>() {
+            public Long compute() {
                 lv2Computed.incrementAndGet();
                 return 0L;
             }
-        });
+        };
 
-        Collection<LazyValue<String, Long>> lazyValues = new ArrayList<LazyHashMap.LazyValue<String,Long>>();
+        Collection<LazyValue<Long>> lazyValues = new ArrayList<LazyValue<Long>>();
         lazyValues.add(lv1);
         lazyValues.add(lv2);
-        HashMap<String, Long> lhm = new LazyHashMap<String, Long>(lazyValues);
+        LazyStringMap<Long> lhm = new LazyStringMap<Long>();
         lhm.put("1", 2L);
+        lhm.putLazy("42", lv1);
+        lhm.putLazy("zero", lv2);
 
         assertEquals(new Long(2L), lhm.get("1"));
         assertEquals("No computation should have happened yet", 0, lv1Computed.get());

Modified: felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImplTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImplTest.java?rev=1829639&r1=1829638&r2=1829639&view=diff
==============================================================================
--- felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImplTest.java (original)
+++ felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImplTest.java Fri Apr 20 09:23:35 2018
@@ -58,7 +58,7 @@ public class OSGiRepositoryImplTest exte
         repoAdmin.addRepository(url);
 
         Repository repo = new OSGiRepositoryImpl(repoAdmin);
-        Requirement req = new RequirementImpl("osgi.identity", null);
+        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "osgi.identity", null);
 
         Map<Requirement, Collection<Capability>> result = repo.findProviders(Collections.singleton(req));
         assertEquals(1, result.size());
@@ -117,7 +117,7 @@ public class OSGiRepositoryImplTest exte
         repoAdmin.addRepository(url);
 
         Repository repo = new OSGiRepositoryImpl(repoAdmin);
-        Requirement req = new RequirementImpl("osgi.identity", "(osgi.identity=test_file_2)");
+        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "osgi.identity", "(osgi.identity=test_file_2)");
 
         Map<Requirement, Collection<Capability>> result = repo.findProviders(Collections.singleton(req));
         assertEquals(1, result.size());
@@ -137,7 +137,7 @@ public class OSGiRepositoryImplTest exte
         repoAdmin.addRepository(url);
 
         Repository repo = new OSGiRepositoryImpl(repoAdmin);
-        Requirement req = new RequirementImpl("foo", "(someKey=someOtherVal)");
+        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "foo", "(someKey=someOtherVal)");
 
         Map<Requirement, Collection<Capability>> result = repo.findProviders(Collections.singleton(req));
         assertEquals(1, result.size());
@@ -157,7 +157,7 @@ public class OSGiRepositoryImplTest exte
         repoAdmin.addRepository(url);
 
         Repository repo = new OSGiRepositoryImpl(repoAdmin);
-        Requirement req = new RequirementImpl("foo", "(someKey=*)");
+        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "foo", "(someKey=*)");
 
         Map<Requirement, Collection<Capability>> result = repo.findProviders(Collections.singleton(req));
         assertEquals(1, result.size());
@@ -181,7 +181,7 @@ public class OSGiRepositoryImplTest exte
         repoAdmin.addRepository(url);
 
         Repository repo = new OSGiRepositoryImpl(repoAdmin);
-        Requirement req = new RequirementImpl("osgi.wiring.package",
+        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "osgi.wiring.package",
                 "(&(osgi.wiring.package=org.apache.commons.logging)(version>=1.0.1)(!(version>=2)))");
 
         Map<Requirement, Collection<Capability>> result = repo.findProviders(Collections.singleton(req));
@@ -230,12 +230,12 @@ public class OSGiRepositoryImplTest exte
 
         BundleRevision br = Mockito.mock(BundleRevision.class);
         Mockito.when(sysBundle.adapt(BundleRevision.class)).thenReturn(br);
-        Capability cap1 = new CapabilityImpl("some.system.cap",
-                Collections.<String, Object>singletonMap("sys.cap", "something"),
-                Collections.singletonMap("x", "y"));
-        Capability cap2 = new CapabilityImpl("some.system.cap",
-                Collections.<String, Object>singletonMap("sys.cap", "somethingelse"),
-                Collections.<String, String>emptyMap());
+        Capability cap1 = new CapabilityImpl(Mockito.mock(Resource.class), "some.system.cap",
+                Collections.singletonMap("x", "y"),
+                Collections.<String, Object>singletonMap("sys.cap", "something"));
+        Capability cap2 = new CapabilityImpl(Mockito.mock(Resource.class), "some.system.cap",
+                Collections.<String, String>emptyMap(),
+                Collections.<String, Object>singletonMap("sys.cap", "somethingelse"));
         Mockito.when(br.getCapabilities(null)).thenReturn(Arrays.asList(cap1, cap2));
 
         BundleContext bc = Mockito.mock(BundleContext.class);

Modified: felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryXMLTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryXMLTest.java?rev=1829639&r1=1829638&r2=1829639&view=diff
==============================================================================
--- felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryXMLTest.java (original)
+++ felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryXMLTest.java Fri Apr 20 09:23:35 2018
@@ -51,7 +51,8 @@ public class OSGiRepositoryXMLTest exten
         repoAdmin.addRepository(url);
 
         Repository repo = new OSGiRepositoryImpl(repoAdmin);
-        Requirement req = new RequirementImpl("osgi.identity",
+        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),
+                "osgi.identity",
                 "(osgi.identity=cdi-subsystem)");
 
         Map<Requirement, Collection<Capability>> result = repo
@@ -126,7 +127,8 @@ public class OSGiRepositoryXMLTest exten
         repoAdmin.addRepository(url);
 
         Repository repo = new OSGiRepositoryImpl(repoAdmin);
-        Requirement req = new RequirementImpl("osgi.identity",
+        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),
+                "osgi.identity",
                 "(license=http://www.opensource.org/licenses/mytestlicense)");
 
         Map<Requirement, Collection<Capability>> result = repo
@@ -145,7 +147,7 @@ public class OSGiRepositoryXMLTest exten
         repoAdmin.addRepository(url);
 
         Repository repo = new OSGiRepositoryImpl(repoAdmin);
-        Requirement req = new RequirementImpl("foo", "(bar=toast)");
+        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),"foo", "(bar=toast)");
 
         Map<Requirement, Collection<Capability>> result = repo
                 .findProviders(Collections.singleton(req));

Modified: felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRequirementAdapterTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRequirementAdapterTest.java?rev=1829639&r1=1829638&r2=1829639&view=diff
==============================================================================
--- felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRequirementAdapterTest.java (original)
+++ felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRequirementAdapterTest.java Fri Apr 20 09:23:35 2018
@@ -24,7 +24,9 @@ import java.util.Map;
 import junit.framework.TestCase;
 
 import org.apache.felix.utils.resource.RequirementImpl;
+import org.mockito.Mockito;
 import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
 
 public class OSGiRequirementAdapterTest extends TestCase
 {
@@ -38,7 +40,7 @@ public class OSGiRequirementAdapterTest
         dirs.put("resolution", "optional");
         dirs.put("test", "test");
 
-        Requirement req = new RequirementImpl("osgi.wiring.package", attrs, dirs);
+        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "osgi.wiring.package", dirs, attrs);
         OSGiRequirementAdapter adapter = new OSGiRequirementAdapter(req);
 
         assertEquals("(package=y)", adapter.getFilter());

Modified: felix/trunk/utils/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/utils/pom.xml?rev=1829639&r1=1829638&r2=1829639&view=diff
==============================================================================
--- felix/trunk/utils/pom.xml (original)
+++ felix/trunk/utils/pom.xml Fri Apr 20 09:23:35 2018
@@ -36,17 +36,21 @@
         <url>http://svn.apache.org/repos/asf/felix/utils</url>
     </scm>
 
+    <properties>
+        <felix.java.version>7</felix.java.version>
+    </properties>
+
     <dependencies>
         <dependency>
             <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.core</artifactId>
+            <artifactId>osgi.core</artifactId>
             <version>5.0.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.compendium</artifactId>
-            <version>4.2.0</version>
+            <artifactId>osgi.cmpn</artifactId>
+            <version>5.0.0</version>
             <scope>provided</scope>
         </dependency>
     </dependencies>
@@ -62,6 +66,13 @@
                     </excludes>
                 </configuration>
             </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.7</source>
+                    <target>1.7</target>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 </project>

Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/StringArrayMap.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/StringArrayMap.java?rev=1829639&view=auto
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/StringArrayMap.java (added)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/StringArrayMap.java Fri Apr 20 09:23:35 2018
@@ -0,0 +1,339 @@
+/*
+ * 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.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Set;
+
+public class StringArrayMap<V> implements Map<String, V> {
+
+    protected Object[] table;
+    protected int size;
+
+    public static <T> Map<String, T> reduceMemory(Map<String, T> map) {
+        if (map == null) {
+            return Collections.emptyMap();
+        }
+        switch (map.size()) {
+            case 0:
+                return Collections.emptyMap();
+            case 1:
+                Entry<String, T> e = map.entrySet().iterator().next();
+                return Collections.singletonMap(e.getKey().intern(), e.getValue());
+            default:
+                if (map instanceof StringArrayMap) {
+                    @SuppressWarnings("unchecked")
+                    StringArrayMap<T> m = (StringArrayMap) map;
+                    if (m.size == m.table.length / 2) {
+                        return map;
+                    }
+                }
+                return new StringArrayMap<>(map);
+        }
+    }
+
+    public StringArrayMap(Map<String, ? extends V> map) {
+        if (map instanceof StringArrayMap) {
+            size = ((StringArrayMap) map).size;
+            table = Arrays.copyOf(((StringArrayMap) map).table, size * 2);
+        } else {
+            size = 0;
+            table = new Object[map.size() * 2];
+            for (Entry<String, ? extends V> e : map.entrySet()) {
+                int i = size++ << 1;
+                table[i++] = e.getKey().intern();
+                table[i] = e.getValue();
+            }
+        }
+    }
+
+    public StringArrayMap() {
+        this(32);
+    }
+
+    public StringArrayMap(int capacity) {
+        table = new Object[capacity * 2];
+        size = 0;
+    }
+
+    @SuppressWarnings("unchecked")
+    public V get(Object key) {
+        String k = ((String) key).intern();
+        for (int i = 0, l = size << 1; i < l; i += 2) {
+            if (k == table[i]) {
+                return (V) table[i + 1];
+            }
+        }
+        return null;
+    }
+
+    @SuppressWarnings("unchecked")
+    public V put(String key, V value) {
+        return (V) doPut(key, value);
+    }
+
+    protected Object doPut(String key, Object value) {
+        key = key.intern();
+        for (int i = 0, l = size << 1; i < l; i += 2) {
+            if (key == table[i]) {
+                Object old = table[i + 1];
+                table[i + 1] = value;
+                return old;
+            }
+        }
+        if (table.length == 0) {
+            table = new Object[2];
+        } else if (size * 2 == table.length) {
+            Object[] n = new Object[table.length * 2];
+            System.arraycopy(table, 0, n, 0, table.length);
+            table = n;
+        }
+        int i = size++ << 1;
+        table[i++] = key;
+        table[i] = value;
+        return null;
+    }
+
+    public Set<String> keySet() {
+        return new AbstractSet<String>() {
+            @Override
+            public Iterator<String> iterator() {
+                return new Iterator<String>() {
+                    int index = 0;
+
+                    @Override
+                    public boolean hasNext() {
+                        return index < size;
+                    }
+
+                    @Override
+                    public String next() {
+                        if (index >= size) {
+                            throw new NoSuchElementException();
+                        }
+                        return (String) table[(index++ << 1)];
+                    }
+
+                    public void remove() {
+                        throw new UnsupportedOperationException("remove");
+                    }
+                };
+            }
+
+            @Override
+            public int size() {
+                return size;
+            }
+        };
+    }
+
+    public Collection<V> values() {
+        return new AbstractCollection<V>() {
+            @Override
+            public Iterator<V> iterator() {
+                return new Iterator<V>() {
+                    int index = 0;
+
+                    public boolean hasNext() {
+                        return index < size;
+                    }
+
+                    @SuppressWarnings("unchecked")
+                    public V next() {
+                        if (index >= size) {
+                            throw new NoSuchElementException();
+                        }
+                        return (V) table[(index++ << 1) + 1];
+                    }
+
+                    public void remove() {
+                        throw new UnsupportedOperationException("remove");
+                    }
+                };
+            }
+
+            @Override
+            public int size() {
+                return size;
+            }
+        };
+    }
+
+    public Set<Entry<String, V>> entrySet() {
+        return new AbstractSet<Entry<String, V>>() {
+            @Override
+            public Iterator<Entry<String, V>> iterator() {
+                return new Iterator<Entry<String, V>>() {
+                    int index = 0;
+
+                    public boolean hasNext() {
+                        return index < size;
+                    }
+
+                    @SuppressWarnings("unchecked")
+                    public Entry<String, V> next() {
+                        if (index >= size) {
+                            throw new NoSuchElementException();
+                        }
+                        final int i = index << 1;
+                        index++;
+                        return new Entry<String, V>() {
+
+                            public String getKey() {
+                                return (String) table[i];
+                            }
+
+                            public V getValue() {
+                                return (V) table[i + 1];
+                            }
+
+                            public V setValue(V value) {
+                                throw new UnsupportedOperationException();
+                            }
+                        };
+                    }
+
+                    public void remove() {
+                        throw new UnsupportedOperationException("remove");
+                    }
+                };
+            }
+
+            @Override
+            public int size() {
+                return size;
+            }
+        };
+    }
+
+    public int size() {
+        return size;
+    }
+
+    public boolean isEmpty() {
+        return size == 0;
+    }
+
+    public boolean containsKey(Object key) {
+        String k = ((String) key).intern();
+        for (int i = 0, l = size * 2; i < l; i += 2) {
+            if (table[i] == k) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean containsValue(Object value) {
+        for (int i = 0, l = size * 2; i < l; i += 2) {
+            if (Objects.equals(table[i + 1], value)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @SuppressWarnings("unchecked")
+    public V remove(Object key) {
+        String k = ((String) key).intern();
+        for (int i = 0, l = size * 2; i < l; i += 2) {
+            if (table[i] == k) {
+                Object v = table[i + 1];
+                if (i < l - 2) {
+                    System.arraycopy(table, i + 2, table, i, l - 2 - i);
+                }
+                table[l - 1] = null;
+                table[l - 2] = null;
+                size--;
+                return (V) v;
+            }
+        }
+        return null;
+    }
+
+    public void putAll(Map<? extends String, ? extends V> m) {
+        for (Entry<? extends String, ? extends V> e : m.entrySet()) {
+            put(e.getKey(), e.getValue());
+        }
+    }
+
+    public void clear() {
+        size = 0;
+        Arrays.fill(table, null);
+    }
+
+    public int hashCode() {
+        int result = 1;
+        for (int i = 0; i < size * 2; i++)
+            result = 31 * result + (table[i] == null ? 0 : table[i].hashCode());
+        return result;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this)
+            return true;
+        if (!(o instanceof Map))
+            return false;
+        Map<?,?> m = (Map<?,?>) o;
+        if (m.size() != size())
+            return false;
+        try {
+            for (int i = 0, l = size * 2; i < l; i += 2) {
+                Object key = table[i];
+                Object value = table[i+1];
+                if (value == null) {
+                    if (!(m.get(key)==null && m.containsKey(key)))
+                        return false;
+                } else {
+                    if (!value.equals(m.get(key)))
+                        return false;
+                }
+            }
+        } catch (ClassCastException | NullPointerException unused) {
+            return false;
+        }
+        return true;
+    }
+
+    public String toString() {
+        if (size == 0)
+            return "{}";
+
+        StringBuilder sb = new StringBuilder();
+        sb.append('{');
+        for (int i = 0, l = size * 2; i < l; i += 2) {
+            if (i > 0) {
+                sb.append(',').append(' ');
+            }
+            sb.append(table[i]);
+            sb.append('=');
+            sb.append(table[i+1] == this ? "(this Map)" : table[i+1]);
+        }
+        return sb.append('}').toString();
+    }
+
+}

Modified: felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/AbstractCapabilityRequirement.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/AbstractCapabilityRequirement.java?rev=1829639&r1=1829638&r2=1829639&view=diff
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/AbstractCapabilityRequirement.java (original)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/AbstractCapabilityRequirement.java Fri Apr 20 09:23:35 2018
@@ -16,38 +16,32 @@
  */
 package org.apache.felix.utils.resource;
 
+import org.apache.felix.utils.collections.StringArrayMap;
+import org.osgi.framework.Version;
 import org.osgi.resource.Resource;
 
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 abstract class AbstractCapabilityRequirement {
 
-    /** The namespace. Required. */
-    private final String namespace;
+    /** The resource. Required. */
+    protected final Resource resource;
 
-    /** Optional resource. */
-    private final Resource resource;
+    /** The namespace. Required. */
+    protected final String namespace;
 
     /** Optional attributes. Never null. */
-    private final Map<String, Object> attributes;
+    protected final Map<String, String> directives;
 
     /** Optional attributes. Never null. */
-    private final Map<String, String> directives;
+    protected final Map<String, Object> attributes;
 
-    AbstractCapabilityRequirement(final String ns, final Map<String, Object> attrs, final Map<String, String> dirs, final Resource res) {
-        if ( ns == null ) {
-            throw new IllegalArgumentException("Namespace must not be null.");
-        }
-        namespace = ns;
-        attributes = attrs == null
-                ? Collections.<String, Object>emptyMap()
-                : Collections.unmodifiableMap(new HashMap<String, Object>(attrs));
-        directives = dirs == null
-                ? Collections.<String,String>emptyMap()
-                : Collections.unmodifiableMap(new HashMap<String,String>(dirs));
-                resource = res;
+    AbstractCapabilityRequirement(final Resource res, final String ns, final Map<String, String> dirs, final Map<String, Object> attrs) {
+        resource = Objects.requireNonNull(res, "Resource must not be null.");
+        namespace = Objects.requireNonNull(ns, "Namespace must not be null.");
+        directives = StringArrayMap.reduceMemory(dirs);
+        attributes = StringArrayMap.reduceMemory(attrs);
     }
 
     /**
@@ -82,45 +76,98 @@ abstract class AbstractCapabilityRequire
         return resource;
     }
 
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + attributes.hashCode();
-        result = prime * result + directives.hashCode();
-        result = prime * result + namespace.hashCode();
-
-        if (resource != null)
-            result = prime * result + resource.hashCode();
 
-        return result;
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AbstractCapabilityRequirement that = (AbstractCapabilityRequirement) o;
+        return Objects.equals(resource, that.resource) &&
+                Objects.equals(namespace, that.namespace) &&
+                Objects.equals(attributes, that.attributes) &&
+                Objects.equals(directives, that.directives);
     }
 
     @Override
-    public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-        if (obj == null)
-            return false;
-        if (getClass() != obj.getClass())
-            return false;
-        AbstractCapabilityRequirement other = (AbstractCapabilityRequirement) obj;
-        if (!namespace.equals(other.namespace))
-            return false;
-        if (!attributes.equals(other.attributes))
-            return false;
-        if (!directives.equals(other.directives))
-            return false;
-        if (resource == null) {
-            return other.resource == null;
-        } else {
-            return resource.equals(other.resource);
-        }
+    public int hashCode() {
+        return Objects.hash(resource, namespace, attributes, directives);
     }
 
     @Override
     public String toString() {
-        return getClass().getSimpleName() + " [resource=" + resource + ", namespace=" + namespace + ", attributes=" + attributes
-                + ", directives=" + directives + "]";
+        return toString(getResource(), getNamespace(), getAttributes(), getDirectives());
+    }
+
+    public static String toString(Resource res, String namespace, Map<String, Object> attrs, Map<String, String> dirs) {
+        StringBuilder sb = new StringBuilder();
+        if (res != null) {
+            sb.append("[").append(res).append("] ");
+        }
+        sb.append(namespace);
+        for (String key : attrs.keySet()) {
+            sb.append("; ");
+            append(sb, key, attrs.get(key), true);
+        }
+        for (String key : dirs.keySet()) {
+            sb.append("; ");
+            append(sb, key, dirs.get(key), false);
+        }
+        return sb.toString();
+    }
+
+    private static void append(StringBuilder sb, String key, Object val, boolean attribute) {
+        sb.append(key);
+        if (val instanceof Version) {
+            sb.append(":Version=");
+            sb.append(val);
+        } else if (val instanceof Long) {
+            sb.append(":Long=");
+            sb.append(val);
+        } else if (val instanceof Double) {
+            sb.append(":Double=");
+            sb.append(val);
+        } else if (val instanceof Iterable) {
+            Iterable<?> it = (Iterable<?>) val;
+            String scalar = null;
+            for (Object o : it) {
+                String ts;
+                if (o instanceof String) {
+                    ts = "String";
+                } else if (o instanceof Long) {
+                    ts = "Long";
+                } else if (o instanceof Double) {
+                    ts = "Double";
+                } else if (o instanceof Version) {
+                    ts = "Version";
+                } else {
+                    throw new IllegalArgumentException("Unsupported scalar type: " + o);
+                }
+                if (scalar == null) {
+                    scalar = ts;
+                } else if (!scalar.equals(ts)) {
+                    throw new IllegalArgumentException("Unconsistent list type for attribute " + key);
+                }
+            }
+            sb.append(":List<").append(scalar).append(">=");
+            sb.append("\"");
+            boolean first = true;
+            for (Object o : it) {
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(",");
+                }
+                sb.append(o.toString().replace("\"", "\\\"").replace(",", "\\,"));
+            }
+            sb.append("\"");
+        } else {
+            sb.append(attribute ? "=" : ":=");
+            String s = val.toString();
+            if (s.matches("[0-9a-zA-Z_\\-.]*")) {
+                sb.append(s);
+            } else {
+                sb.append("\"").append(s.replace("\"", "\\\\")).append("\"");
+            }
+        }
     }
 }

Modified: felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java?rev=1829639&r1=1829638&r2=1829639&view=diff
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java (original)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java Fri Apr 20 09:23:35 2018
@@ -16,35 +16,50 @@
  */
 package org.apache.felix.utils.resource;
 
+import org.osgi.framework.Constants;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Resource;
 
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Implementation of the OSGi Capability interface.
  */
 public class CapabilityImpl extends AbstractCapabilityRequirement implements Capability {
-    /**
-     * Create a capability that is not associated with a resource.
-     * @param res The resource associated with the capability. May be null.
-     * @param ns The namespace of the capability.
-     * @param attrs The attributes of the capability.
-     * @param dirs The directives of the capability.
-     */
-    public CapabilityImpl(String ns, Map<String, Object> attrs, Map<String, String> dirs) {
-        this(ns, attrs, dirs, null);
-    }
+
+    protected final Set<String> mandatory;
 
     /**
      * Create a capability.
+     * @param res The resource associated with the capability.
      * @param ns The namespace of the capability.
      * @param attrs The attributes of the capability.
      * @param dirs The directives of the capability.
-     * @param res The resource associated with the capability. May be null.
      */
-    public CapabilityImpl(String ns, Map<String, Object> attrs, Map<String, String> dirs, Resource res) {
-        super(ns, attrs, dirs, res);
+    public CapabilityImpl(Resource res, String ns, Map<String, String> dirs, Map<String, Object> attrs) {
+        super(res, ns, dirs, attrs);
+
+        // Handle mandatory directive
+        Set<String> mandatory = Collections.emptySet();
+        String value = this.directives.get(Constants.MANDATORY_DIRECTIVE);
+        if (value != null) {
+            List<String> names = ResourceBuilder.parseDelimitedString(value, ",");
+            mandatory = new HashSet<>(names.size());
+            for (String name : names) {
+                // If attribute exists, then record it as mandatory.
+                if (this.attributes.containsKey(name)) {
+                    mandatory.add(name);
+                    // Otherwise, report an error.
+                } else {
+                    throw new IllegalArgumentException("Mandatory attribute '" + name + "' does not exist.");
+                }
+            }
+        }
+        this.mandatory = mandatory;
     }
 
     /**
@@ -54,6 +69,10 @@ public class CapabilityImpl extends Abst
      * @param resource The resource to be associated with the capability
      */
     public CapabilityImpl(Resource resource, Capability capability) {
-        this(capability.getNamespace(), capability.getAttributes(), capability.getDirectives(), resource);
+        this(resource, capability.getNamespace(), capability.getDirectives(), capability.getAttributes());
+    }
+
+    public boolean isAttributeMandatory(String name) {
+        return !mandatory.isEmpty() && mandatory.contains(name);
     }
 }

Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java?rev=1829639&view=auto
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java (added)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java Fri Apr 20 09:23:35 2018
@@ -0,0 +1,469 @@
+/*
+ * 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.resource;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.felix.utils.version.VersionTable;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.resource.Capability;
+
+@SuppressWarnings("rawtypes")
+public class CapabilitySet {
+
+    private static final Class<?>[] STRING_CLASS = new Class[] {String.class};
+
+    private final Map<String, Map<Object, Set<Capability>>> indices;
+    private final Set<Capability> capSet = new HashSet<>();
+
+    public CapabilitySet(List<String> indexProps) {
+        indices = new TreeMap<>();
+        for (int i = 0; (indexProps != null) && (i < indexProps.size()); i++) {
+            indices.put(indexProps.get(i), new HashMap<Object, Set<Capability>>());
+        }
+    }
+
+    public void dump() {
+        for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) {
+            boolean header1 = false;
+            for (Entry<Object, Set<Capability>> entry2 : entry.getValue().entrySet()) {
+                boolean header2 = false;
+                for (Capability cap : entry2.getValue()) {
+                    if (!header1) {
+                        System.out.println(entry.getKey() + ":");
+                        header1 = true;
+                    }
+                    if (!header2) {
+                        System.out.println("   " + entry2.getKey());
+                        header2 = true;
+                    }
+                    System.out.println("      " + cap);
+                }
+            }
+        }
+    }
+
+    public void addCapability(Capability cap) {
+        capSet.add(cap);
+
+        // Index capability.
+        for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) {
+            Object value = cap.getAttributes().get(entry.getKey());
+            if (value != null) {
+                if (value.getClass().isArray()) {
+                    value = convertArrayToList(value);
+                }
+
+                Map<Object, Set<Capability>> index = entry.getValue();
+
+                if (value instanceof Collection) {
+                    Collection c = (Collection) value;
+                    for (Object o : c) {
+                        indexCapability(index, cap, o);
+                    }
+                } else {
+                    indexCapability(index, cap, value);
+                }
+            }
+        }
+    }
+
+    private void indexCapability(Map<Object, Set<Capability>> index, Capability cap, Object capValue) {
+        // TODO: when JDK8, should be:
+        // TODO: index.computeIfAbsent(capValue, k -> new HashSet<>()).add(cap);
+        Set<Capability> set = index.get(capValue);
+        if (set == null) {
+            set = new HashSet<>();
+            index.put(capValue, set);
+        }
+        set.add(cap);
+    }
+
+    public void removeCapability(Capability cap) {
+        if (capSet.remove(cap)) {
+            for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) {
+                Object value = cap.getAttributes().get(entry.getKey());
+                if (value != null) {
+                    if (value.getClass().isArray()) {
+                        value = convertArrayToList(value);
+                    }
+
+                    Map<Object, Set<Capability>> index = entry.getValue();
+
+                    if (value instanceof Collection) {
+                        Collection c = (Collection) value;
+                        for (Object o : c) {
+                            deindexCapability(index, cap, o);
+                        }
+                    } else {
+                        deindexCapability(index, cap, value);
+                    }
+                }
+            }
+        }
+    }
+
+    private void deindexCapability(
+            Map<Object, Set<Capability>> index, Capability cap, Object value) {
+        Set<Capability> caps = index.get(value);
+        if (caps != null) {
+            caps.remove(cap);
+            if (caps.isEmpty()) {
+                index.remove(value);
+            }
+        }
+    }
+
+    public Set<Capability> match(SimpleFilter sf, boolean obeyMandatory) {
+        Set<Capability> matches = match(capSet, sf);
+        return obeyMandatory
+                ? matchMandatory(matches, sf)
+                : matches;
+    }
+
+    @SuppressWarnings("unchecked")
+    private Set<Capability> match(Set<Capability> caps, SimpleFilter sf) {
+        Set<Capability> matches = new HashSet<>();
+
+        if (sf.getOperation() == SimpleFilter.MATCH_ALL) {
+            matches.addAll(caps);
+        } else if (sf.getOperation() == SimpleFilter.AND) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For AND we calculate the intersection of each subfilter.
+            // We can short-circuit the AND operation if there are no
+            // remaining capabilities.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; (caps.size() > 0) && (i < sfs.size()); i++) {
+                matches = match(caps, sfs.get(i));
+                caps = matches;
+            }
+        } else if (sf.getOperation() == SimpleFilter.OR) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (SimpleFilter sf1 : sfs) {
+                matches.addAll(match(caps, sf1));
+            }
+        } else if (sf.getOperation() == SimpleFilter.NOT) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            matches.addAll(caps);
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (SimpleFilter sf1 : sfs) {
+                matches.removeAll(match(caps, sf1));
+            }
+        } else {
+            Map<Object, Set<Capability>> index = indices.get(sf.getName());
+            if ((sf.getOperation() == SimpleFilter.EQ) && (index != null)) {
+                Set<Capability> existingCaps = index.get(sf.getValue());
+                if (existingCaps != null) {
+                    matches.addAll(existingCaps);
+                    matches.retainAll(caps);
+                }
+            } else {
+                for (Capability cap : caps) {
+                    Object lhs = cap.getAttributes().get(sf.getName());
+                    if (lhs != null) {
+                        if (compare(lhs, sf.getValue(), sf.getOperation())) {
+                            matches.add(cap);
+                        }
+                    }
+                }
+            }
+        }
+
+        return matches;
+    }
+
+    public static boolean matches(Capability cap, SimpleFilter sf) {
+        return matchesInternal(cap, sf) && matchMandatory(cap, sf);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static boolean matchesInternal(Capability cap, SimpleFilter sf) {
+        boolean matched = true;
+
+        if (sf.getOperation() == SimpleFilter.MATCH_ALL) {
+            matched = true;
+        } else if (sf.getOperation() == SimpleFilter.AND) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For AND we calculate the intersection of each subfilter.
+            // We can short-circuit the AND operation if there are no
+            // remaining capabilities.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; matched && (i < sfs.size()); i++) {
+                matched = matchesInternal(cap, sfs.get(i));
+            }
+        } else if (sf.getOperation() == SimpleFilter.OR) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            matched = false;
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; !matched && (i < sfs.size()); i++) {
+                matched = matchesInternal(cap, sfs.get(i));
+            }
+        } else if (sf.getOperation() == SimpleFilter.NOT) {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (SimpleFilter sf1 : sfs) {
+                matched = !(matchesInternal(cap, sf1));
+            }
+        } else {
+            matched = false;
+            Object lhs = cap.getAttributes().get(sf.getName());
+            if (lhs != null) {
+                matched = compare(lhs, sf.getValue(), sf.getOperation());
+            }
+        }
+
+        return matched;
+    }
+
+    private static Set<Capability> matchMandatory(
+            Set<Capability> caps, SimpleFilter sf) {
+        for (Iterator<Capability> it = caps.iterator(); it.hasNext();) {
+            Capability cap = it.next();
+            if (!matchMandatory(cap, sf)) {
+                it.remove();
+            }
+        }
+        return caps;
+    }
+
+    private static boolean matchMandatory(Capability cap, SimpleFilter sf) {
+        if (cap instanceof CapabilityImpl) {
+            for (Entry<String, Object> entry : cap.getAttributes().entrySet()) {
+                if (((CapabilityImpl) cap).isAttributeMandatory(entry.getKey())
+                        && !matchMandatoryAttribute(entry.getKey(), sf)) {
+                    return false;
+                }
+            }
+        } else {
+            String value = cap.getDirectives().get(Constants.MANDATORY_DIRECTIVE);
+            if (value != null) {
+                List<String> names = ResourceBuilder.parseDelimitedString(value, ",");
+                for (Entry<String, Object> entry : cap.getAttributes().entrySet()) {
+                    if (names.contains(entry.getKey())
+                            && !matchMandatoryAttribute(entry.getKey(), sf)) {
+                        return false;
+                    }
+                }
+            }
+
+        }
+        return true;
+    }
+
+    private static boolean matchMandatoryAttribute(String attrName, SimpleFilter sf) {
+        if ((sf.getName() != null) && sf.getName().equals(attrName)) {
+            return true;
+        } else if (sf.getOperation() == SimpleFilter.AND) {
+            List list = (List) sf.getValue();
+            for (Object aList : list) {
+                SimpleFilter sf2 = (SimpleFilter) aList;
+                if ((sf2.getName() != null)
+                        && sf2.getName().equals(attrName)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static boolean compare(Object lhs, Object rhsUnknown, int op) {
+        if (lhs == null) {
+            return false;
+        }
+
+        // If this is a PRESENT operation, then just return true immediately
+        // since we wouldn't be here if the attribute wasn't present.
+        if (op == SimpleFilter.PRESENT) {
+            return true;
+        }
+
+        // If the type is comparable, then we can just return the
+        // result immediately.
+        if (lhs instanceof Comparable) {
+            // Spec says SUBSTRING is false for all types other than string.
+            if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String)) {
+                return false;
+            }
+
+            Object rhs;
+            if (op == SimpleFilter.SUBSTRING) {
+                rhs = rhsUnknown;
+            } else {
+                try {
+                    rhs = coerceType(lhs, (String) rhsUnknown);
+                } catch (Exception ex) {
+                    return false;
+                }
+            }
+
+            switch (op) {
+            case SimpleFilter.EQ:
+                try {
+                    return ((Comparable) lhs).compareTo(rhs) == 0;
+                } catch (Exception ex) {
+                    return false;
+                }
+            case SimpleFilter.GTE:
+                try {
+                    return ((Comparable) lhs).compareTo(rhs) >= 0;
+                } catch (Exception ex) {
+                    return false;
+                }
+            case SimpleFilter.LTE:
+                try {
+                    return ((Comparable) lhs).compareTo(rhs) <= 0;
+                } catch (Exception ex) {
+                    return false;
+                }
+            case SimpleFilter.APPROX:
+                return compareApproximate(lhs, rhs);
+            case SimpleFilter.SUBSTRING:
+                return SimpleFilter.compareSubstring((List<String>) rhs, (String) lhs);
+            default:
+                throw new RuntimeException("Unknown comparison operator: " + op);
+            }
+        }
+
+        // If the LHS is not a comparable or boolean, check if it is an
+        // array. If so, convert it to a list so we can treat it as a
+        // collection.
+        if (lhs.getClass().isArray()) {
+            lhs = convertArrayToList(lhs);
+        }
+
+        // If LHS is a collection, then call compare() on each element
+        // of the collection until a match is found.
+        if (lhs instanceof Collection) {
+            for (Object o : (Collection) lhs) {
+                if (compare(o, rhsUnknown, op)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        // Spec says SUBSTRING is false for all types other than string.
+        if (op == SimpleFilter.SUBSTRING) {
+            return false;
+        }
+
+        // Since we cannot identify the LHS type, then we can only perform
+        // equality comparison.
+        try {
+            return lhs.equals(coerceType(lhs, (String) rhsUnknown));
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+    private static boolean compareApproximate(Object lhs, Object rhs) {
+        if (rhs instanceof String) {
+            return removeWhitespace((String) lhs)
+                    .equalsIgnoreCase(removeWhitespace((String) rhs));
+        } else if (rhs instanceof Character) {
+            return Character.toLowerCase((Character) lhs)
+                    == Character.toLowerCase((Character) rhs);
+        }
+        return lhs.equals(rhs);
+    }
+
+    private static String removeWhitespace(String s) {
+        StringBuilder sb = new StringBuilder(s.length());
+        for (int i = 0; i < s.length(); i++) {
+            if (!Character.isWhitespace(s.charAt(i))) {
+                sb.append(s.charAt(i));
+            }
+        }
+        return sb.toString();
+    }
+
+    private static Object coerceType(Object lhs, String rhsString) throws Exception {
+        // If the LHS expects a string, then we can just return
+        // the RHS since it is a string.
+        if (lhs.getClass() == rhsString.getClass()) {
+            return rhsString;
+        }
+
+        // Try to convert the RHS type to the LHS type by using
+        // the string constructor of the LHS class, if it has one.
+        Object rhs;
+        try {
+            if (lhs instanceof Version) {
+                rhs = VersionTable.getVersion(rhsString, false);
+            } else
+            // The Character class is a special case, since its constructor
+            // does not take a string, so handle it separately.
+            if (lhs instanceof Character) {
+                rhs = rhsString.charAt(0);
+            } else {
+                // Spec says we should trim number types.
+                if ((lhs instanceof Number) || (lhs instanceof Boolean)) {
+                    rhsString = rhsString.trim();
+                }
+                Constructor ctor = lhs.getClass().getConstructor(STRING_CLASS);
+                ctor.setAccessible(true);
+                rhs = ctor.newInstance(rhsString);
+            }
+        } catch (Exception ex) {
+            throw new Exception(
+                    "Could not instantiate class "
+                            + lhs.getClass().getName()
+                            + " from string constructor with argument '"
+                            + rhsString + "' because " + ex
+            );
+        }
+
+        return rhs;
+    }
+
+    /**
+     * This is an ugly utility method to convert an array of primitives
+     * to an array of primitive wrapper objects. This method simplifies
+     * processing LDAP filters since the special case of primitive arrays
+     * can be ignored.
+     *
+     * @param array An array of primitive types.
+     * @return An corresponding array using pritive wrapper objects.
+     */
+    private static List<Object> convertArrayToList(Object array) {
+        int len = Array.getLength(array);
+        List<Object> list = new ArrayList<>(len);
+        for (int i = 0; i < len; i++) {
+            list.add(Array.get(array, i));
+        }
+        return list;
+    }
+}

Modified: felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java?rev=1829639&r1=1829638&r2=1829639&view=diff
==============================================================================
--- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java (original)
+++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java Fri Apr 20 09:23:35 2018
@@ -16,6 +16,8 @@
  */
 package org.apache.felix.utils.resource;
 
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
 import org.osgi.resource.Namespace;
 import org.osgi.resource.Requirement;
 import org.osgi.resource.Resource;
@@ -27,26 +29,19 @@ import java.util.Map;
  * Implementation of the OSGi Requirement interface.
  */
 public class RequirementImpl extends AbstractCapabilityRequirement implements Requirement {
-    /**
-     * Create a requirement that is not associated with a resource.
-     * @param res The resource associated with the requirement.
-     * @param ns The namespace of the requirement.
-     * @param attrs The attributes of the requirement.
-     * @param dirs The directives of the requirement.
-     */
-    public RequirementImpl(String ns, Map<String, Object> attrs, Map<String, String> dirs) {
-        this(ns, attrs, dirs, null);
-    }
+
+    private final SimpleFilter filter;
+    private final boolean optional;
 
     /**
      * Create a requirement.
+     * @param res The resource associated with the requirement.
      * @param ns The namespace of the requirement.
      * @param attrs The attributes of the requirement.
      * @param dirs The directives of the requirement.
-     * @param res The resource associated with the requirement.
      */
-    public RequirementImpl(String ns, Map<String, Object> attrs, Map<String, String> dirs, Resource res) {
-        super(ns, attrs, dirs, res);
+    public RequirementImpl(Resource res, String ns, Map<String, String> dirs, Map<String, Object> attrs) {
+        this(res, ns, dirs, attrs, null);
     }
 
     /**
@@ -54,14 +49,16 @@ public class RequirementImpl extends Abs
       *
       * This is a convenience method that creates a requirement with
       * an empty attributes map and a single 'filter' directive.
+     * @param res The resource associated with the requirement.
       * @param ns The namespace for the requirement.
       * @param filter The filter.
       */
-     public RequirementImpl(String ns, String filter)
+     public RequirementImpl(Resource res, String ns, String filter)
      {
-         this(ns, Collections.<String, Object>emptyMap(),
-             filter == null ? Collections.<String, String> emptyMap() :
-             Collections.singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter));
+         this(res, ns,
+             filter == null ? Collections.<String, String>emptyMap() :
+             Collections.singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter),
+             null);
      }
 
     /**
@@ -71,6 +68,26 @@ public class RequirementImpl extends Abs
      * @param resource The resource to be associated with the requirement
      */
     public RequirementImpl(Resource resource, Requirement requirement) {
-        this(requirement.getNamespace(), requirement.getAttributes(), requirement.getDirectives(), resource);
+        this(resource, requirement.getNamespace(), requirement.getDirectives(), requirement.getAttributes());
+    }
+
+    public RequirementImpl(Resource resource, String path, Map<String, String> dirs, Map<String, Object> attrs, SimpleFilter sf) {
+        super(resource, path, dirs, attrs);
+        this.filter = sf != null ? sf : SimpleFilter.convert(attributes);
+        // Find resolution import directives.
+        this.optional = Constants.RESOLUTION_OPTIONAL.equals(directives.get(Constants.RESOLUTION_DIRECTIVE));
+    }
+
+    public boolean matches(Capability cap) {
+        return CapabilitySet.matches(cap, getFilter());
     }
+
+    public boolean isOptional() {
+        return optional;
+    }
+
+    public SimpleFilter getFilter() {
+        return filter;
+    }
+
 }



Re: svn commit: r1829639 [1/3] - in /felix/trunk: bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/ bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/ utils/ utils/src/main/java/org/apache/felix/utils/collections/ u...

Posted by David Bosschaert <da...@gmail.com>.
Hi all,

This commit disallows the Resource on a Requirement from being null. It
also inverted the tests that were there around this...

The OSGi spec clearly states that Resources on Requirements can be null,
see
https://osgi.org/javadoc/r6/core/org/osgi/resource/Requirement.html#getResource()
so I have changed this behaviour back to allow null Resources.

Best regards,

David

On 20 April 2018 at 10:43, Karl Pauls <ka...@gmail.com> wrote:

> This is great but please try to reference JIRA issues - if we do this
> kind of bigger things we should have a JIRA so that we don't get
> confused in the future.
>
> regards,
>
> Karl
>
>
> On Fri, Apr 20, 2018 at 11:23 AM,  <gn...@apache.org> wrote:
> > Author: gnodet
> > Date: Fri Apr 20 09:23:35 2018
> > New Revision: 1829639
> >
> > URL: http://svn.apache.org/viewvc?rev=1829639&view=rev
> > Log:
> > Provide optimized resource / filter / capability / requirement /
> capability set
> >
> > Added:
> >     felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/LazyStringMap.java
> >     felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/LazyStringMapTest.java
> >       - copied, changed from r1829637, felix/trunk/bundlerepository/
> src/test/java/org/apache/felix/bundlerepository/impl/LazyHashMapTest.java
> >     felix/trunk/utils/src/main/java/org/apache/felix/utils/
> collections/StringArrayMap.java
> >     felix/trunk/utils/src/main/java/org/apache/felix/utils/
> resource/CapabilitySet.java
> >     felix/trunk/utils/src/main/java/org/apache/felix/utils/
> resource/ResourceBuilder.java
> >     felix/trunk/utils/src/main/java/org/apache/felix/utils/
> resource/ResourceImpl.java
> >     felix/trunk/utils/src/main/java/org/apache/felix/utils/
> resource/SimpleFilter.java
> >     felix/trunk/utils/src/test/java/org/apache/felix/utils/collections/
> >     felix/trunk/utils/src/test/java/org/apache/felix/utils/collections/
> StringArrayMapTest.java
> >     felix/trunk/utils/src/test/java/org/apache/felix/utils/
> resource/SimpleFilterTest.java
> > Removed:
> >     felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/LazyHashMap.java
> >     felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/LazyHashMapTest.java
> > Modified:
> >     felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/FelixResourceAdapter.java
> >     felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryImpl.java
> >     felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryImplTest.java
> >     felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryXMLTest.java
> >     felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRequirementAdapterTest.java
> >     felix/trunk/utils/pom.xml
> >     felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/
> AbstractCapabilityRequirement.java
> >     felix/trunk/utils/src/main/java/org/apache/felix/utils/
> resource/CapabilityImpl.java
> >     felix/trunk/utils/src/main/java/org/apache/felix/utils/
> resource/RequirementImpl.java
> >     felix/trunk/utils/src/test/java/org/apache/felix/utils/
> resource/CapabilityImplTest.java
> >     felix/trunk/utils/src/test/java/org/apache/felix/utils/
> resource/RequirementImplTest.java
> >
> > Modified: felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/FelixResourceAdapter.java
> > URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/
> src/main/java/org/apache/felix/bundlerepository/impl/
> FelixResourceAdapter.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> > ============================================================
> ==================
> > --- felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/FelixResourceAdapter.java (original)
> > +++ felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/FelixResourceAdapter.java Fri Apr 20 09:23:35
> 2018
> > @@ -42,12 +42,12 @@ public class FelixResourceAdapter implem
> >
> >          if (namespace == null || namespace.equals(
> IdentityNamespace.IDENTITY_NAMESPACE))
> >          {
> > -            CapabilityImpl c = OSGiRepositoryImpl.
> newOSGiIdentityCapability(resource, this);
> > +            CapabilityImpl c = OSGiRepositoryImpl.
> newOSGiIdentityCapability(this, resource);
> >              result.add(c);
> >          }
> >          if (namespace == null || namespace.equals(
> ContentNamespace.CONTENT_NAMESPACE))
> >          {
> > -            CapabilityImpl c = OSGiRepositoryImpl.
> newOSGiContentCapability(resource, this);
> > +            CapabilityImpl c = OSGiRepositoryImpl.newOSGiContentCapability(this,
> resource);
> >              result.add(c);
> >          }
> >
> >
> > Added: felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/LazyStringMap.java
> > URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/
> src/main/java/org/apache/felix/bundlerepository/impl/
> LazyStringMap.java?rev=1829639&view=auto
> > ============================================================
> ==================
> > --- felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/LazyStringMap.java (added)
> > +++ felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/LazyStringMap.java Fri Apr 20 09:23:35 2018
> > @@ -0,0 +1,67 @@
> > +/*
> > + * 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.bundlerepository.impl;
> > +
> > +import java.util.Map;
> > +
> > +import org.apache.felix.utils.collections.StringArrayMap;
> > +
> > +/**
> > + * A map that can delay the computation of certain values up until the
> moment that they
> > + * are actually needed. Useful for expensive to compute values such as
> the SHA-256.
> > + * This map does <b>not</b> support {@code null} values.
> > + */
> > +@SuppressWarnings("serial")
> > +public class LazyStringMap<V> extends StringArrayMap<V>
> > +{
> > +    public LazyStringMap(Map<String, ? extends V> map) {
> > +        super(map);
> > +    }
> > +
> > +    public LazyStringMap() {
> > +    }
> > +
> > +    public LazyStringMap(int capacity) {
> > +        super(capacity);
> > +    }
> > +
> > +    @Override
> > +    @SuppressWarnings("unchecked")
> > +    public V get(Object key)
> > +    {
> > +        V val = super.get(key);
> > +        if (val instanceof LazyValue) {
> > +            val = ((LazyValue<V>) val).compute();
> > +            if (val == null) {
> > +                throw new NullPointerException("Lazy computed values
> may not be null");
> > +            }
> > +            put((String) key, val);
> > +        }
> > +        return val;
> > +    }
> > +
> > +    public void putLazy(String key, LazyValue<V> lazy) {
> > +        super.doPut(key, lazy);
> > +    }
> > +
> > +    public interface LazyValue<V>
> > +    {
> > +        V compute();
> > +    }
> > +}
> >
> > Modified: felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryImpl.java
> > URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/
> src/main/java/org/apache/felix/bundlerepository/impl/
> OSGiRepositoryImpl.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> > ============================================================
> ==================
> > --- felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryImpl.java (original)
> > +++ felix/trunk/bundlerepository/src/main/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryImpl.java Fri Apr 20 09:23:35
> 2018
> > @@ -30,10 +30,9 @@ import java.util.Collections;
> >  import java.util.HashMap;
> >  import java.util.List;
> >  import java.util.Map;
> > -import java.util.concurrent.Callable;
> >
> >  import org.apache.felix.bundlerepository.RepositoryAdmin;
> > -import org.apache.felix.bundlerepository.impl.LazyHashMap.LazyValue;
> > +import org.apache.felix.bundlerepository.Resource;
> >  import org.apache.felix.utils.resource.CapabilityImpl;
> >  import org.osgi.framework.Filter;
> >  import org.osgi.framework.FrameworkUtil;
> > @@ -41,7 +40,6 @@ import org.osgi.framework.namespace.Iden
> >  import org.osgi.resource.Capability;
> >  import org.osgi.resource.Namespace;
> >  import org.osgi.resource.Requirement;
> > -import org.osgi.resource.Resource;
> >  import org.osgi.service.repository.ContentNamespace;
> >  import org.osgi.service.repository.Repository;
> >
> > @@ -120,8 +118,7 @@ class OSGiRepositoryImpl implements Repo
> >          caps.add(idCap);
> >      }
> >
> > -    static CapabilityImpl newOSGiIdentityCapability(org.
> apache.felix.bundlerepository.Resource res,
> > -            org.osgi.resource.Resource targetResource)
> > +    static CapabilityImpl newOSGiIdentityCapability(org.osgi.resource.Resource
> or, org.apache.felix.bundlerepository.Resource res)
> >      {
> >          @SuppressWarnings("unchecked")
> >          Map<String, Object> idAttrs = new HashMap<String,
> Object>(res.getProperties());
> > @@ -132,32 +129,34 @@ class OSGiRepositoryImpl implements Repo
> >          if (idAttrs.get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE)
> == null)
> >              idAttrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE,
> IdentityNamespace.TYPE_BUNDLE);
> >
> > -        return new CapabilityImpl(IdentityNamespace.IDENTITY_NAMESPACE,
> idAttrs, Collections.<String, String> emptyMap(), targetResource);
> > +        return new CapabilityImpl(or, IdentityNamespace.IDENTITY_NAMESPACE,
> Collections.<String, String> emptyMap(), idAttrs);
> >      }
> >
> > -    static CapabilityImpl newOSGiContentCapability(org.
> apache.felix.bundlerepository.Resource resource,
> > -            org.osgi.resource.Resource targetResource)
> > +    static CapabilityImpl newOSGiContentCapability(org.osgi.resource.Resource
> or, Resource resource)
> >      {
> >          final String uri = resource.getURI();
> > -        LazyValue<String, Object> lazyValue =
> > -            new LazyValue<String, Object>(ContentNamespace.CONTENT_NAMESPACE,
> new Callable<Object>()
> > -            {
> > -                public Object call() throws Exception
> > -                {
> > -                    // This is expensive to do, so only compute it when
> actually obtained...
> > +        LazyStringMap.LazyValue<String> content = new
> LazyStringMap.LazyValue<String>() {
> > +            public String compute() {
> > +                // This is expensive to do, so only compute it when
> actually obtained...
> > +                try {
> >                      return OSGiRepositoryImpl.getSHA256(uri);
> > +                } catch (IOException e) {
> > +                    throw new RuntimeException(e);
> > +                } catch (NoSuchAlgorithmException e) {
> > +                    throw new RuntimeException(e);
> >                  }
> > -            });
> > -
> > +            }
> > +        };
> >          Object mime = resource.getProperties().get("mime");
> >          if (mime == null)
> >              mime = "application/vnd.osgi.bundle";
> >
> > -        Map<String, Object> contentAttrs = new LazyHashMap<String,
> Object>(Collections.singleton(lazyValue));
> > +        Map<String, Object> contentAttrs = new LazyStringMap<Object>(4);
> >          contentAttrs.put(ContentNamespace.CAPABILITY_MIME_ATTRIBUTE,
> mime);
> >          contentAttrs.put(ContentNamespace.CAPABILITY_SIZE_ATTRIBUTE,
> resource.getSize());
> >          contentAttrs.put(ContentNamespace.CAPABILITY_URL_ATTRIBUTE,
> uri);
> > -        return new ContentCapabilityImpl(contentAttrs, targetResource);
> > +        contentAttrs.put(ContentNamespace.CONTENT_NAMESPACE, content);
> > +        return new CapabilityImpl(or, ContentNamespace.CONTENT_NAMESPACE,
> Collections.<String, String> emptyMap(), contentAttrs);
> >      }
> >
> >      static String getSHA256(String uri) throws IOException,
> NoSuchAlgorithmException // TODO find a good place for this
> > @@ -183,19 +182,4 @@ class OSGiRepositoryImpl implements Repo
> >          return sb.toString();
> >      }
> >
> > -    // This capability variant does not take a private copy of the
> capabilities so that it
> > -    // can lazily compute the content hash.
> > -    private static class ContentCapabilityImpl extends CapabilityImpl
> implements Capability {
> > -        private final Map<String, Object> contentAttributes;
> > -
> > -        public ContentCapabilityImpl(Map<String, Object> contentAttrs,
> Resource targetResource) {
> > -            super(ContentNamespace.CONTENT_NAMESPACE, null, null,
> targetResource);
> > -            contentAttributes = Collections.unmodifiableMap(
> contentAttrs);
> > -        }
> > -
> > -        @Override
> > -        public Map<String, Object> getAttributes() {
> > -            return contentAttributes;
> > -        }
> > -    }
> >  }
> >
> > Copied: felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/LazyStringMapTest.java (from r1829637,
> felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/LazyHashMapTest.java)
> > URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/
> src/test/java/org/apache/felix/bundlerepository/impl/
> LazyStringMapTest.java?p2=felix/trunk/bundlerepository/
> src/test/java/org/apache/felix/bundlerepository/impl/
> LazyStringMapTest.java&p1=felix/trunk/bundlerepository/
> src/test/java/org/apache/felix/bundlerepository/impl/
> LazyHashMapTest.java&r1=1829637&r2=1829639&rev=1829639&view=diff
> > ============================================================
> ==================
> > --- felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/LazyHashMapTest.java (original)
> > +++ felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/LazyStringMapTest.java Fri Apr 20 09:23:35
> 2018
> > @@ -21,41 +21,39 @@ package org.apache.felix.bundlerepositor
> >  import java.util.ArrayList;
> >  import java.util.Collection;
> >  import java.util.HashMap;
> > +import java.util.Map;
> >  import java.util.concurrent.Callable;
> >  import java.util.concurrent.atomic.AtomicInteger;
> >
> >  import junit.framework.TestCase;
> > +import org.apache.felix.bundlerepository.impl.LazyStringMap.LazyValue;
> >
> > -import org.apache.felix.bundlerepository.impl.LazyHashMap.LazyValue;
> > -
> > -public class LazyHashMapTest extends TestCase
> > +public class LazyStringMapTest extends TestCase
> >  {
> >      public void testLazyHashMap() {
> >          final AtomicInteger lv1Computed = new AtomicInteger(0);
> > -        LazyValue<String, Long> lv1 = new LazyValue<String, Long>("42",
> new Callable<Long>()
> > -        {
> > -            public Long call() throws Exception
> > -            {
> > +        LazyValue<Long> lv1 = new LazyValue<Long>() {
> > +            public Long compute() {
> >                  lv1Computed.incrementAndGet();
> >                  return 24L;
> >              }
> > -        });
> > +        };
> >
> >          final AtomicInteger lv2Computed = new AtomicInteger(0);
> > -        LazyValue<String, Long> lv2 = new LazyValue<String,
> Long>("zero", new Callable<Long>()
> > -        {
> > -            public Long call() throws Exception
> > -            {
> > +        LazyValue<Long> lv2 = new LazyValue<Long>() {
> > +            public Long compute() {
> >                  lv2Computed.incrementAndGet();
> >                  return 0L;
> >              }
> > -        });
> > +        };
> >
> > -        Collection<LazyValue<String, Long>> lazyValues = new
> ArrayList<LazyHashMap.LazyValue<String,Long>>();
> > +        Collection<LazyValue<Long>> lazyValues = new
> ArrayList<LazyValue<Long>>();
> >          lazyValues.add(lv1);
> >          lazyValues.add(lv2);
> > -        HashMap<String, Long> lhm = new LazyHashMap<String,
> Long>(lazyValues);
> > +        LazyStringMap<Long> lhm = new LazyStringMap<Long>();
> >          lhm.put("1", 2L);
> > +        lhm.putLazy("42", lv1);
> > +        lhm.putLazy("zero", lv2);
> >
> >          assertEquals(new Long(2L), lhm.get("1"));
> >          assertEquals("No computation should have happened yet", 0,
> lv1Computed.get());
> >
> > Modified: felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryImplTest.java
> > URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/
> src/test/java/org/apache/felix/bundlerepository/impl/
> OSGiRepositoryImplTest.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> > ============================================================
> ==================
> > --- felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryImplTest.java (original)
> > +++ felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryImplTest.java Fri Apr 20
> 09:23:35 2018
> > @@ -58,7 +58,7 @@ public class OSGiRepositoryImplTest exte
> >          repoAdmin.addRepository(url);
> >
> >          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> > -        Requirement req = new RequirementImpl("osgi.identity", null);
> > +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),
> "osgi.identity", null);
> >
> >          Map<Requirement, Collection<Capability>> result =
> repo.findProviders(Collections.singleton(req));
> >          assertEquals(1, result.size());
> > @@ -117,7 +117,7 @@ public class OSGiRepositoryImplTest exte
> >          repoAdmin.addRepository(url);
> >
> >          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> > -        Requirement req = new RequirementImpl("osgi.identity",
> "(osgi.identity=test_file_2)");
> > +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),
> "osgi.identity", "(osgi.identity=test_file_2)");
> >
> >          Map<Requirement, Collection<Capability>> result =
> repo.findProviders(Collections.singleton(req));
> >          assertEquals(1, result.size());
> > @@ -137,7 +137,7 @@ public class OSGiRepositoryImplTest exte
> >          repoAdmin.addRepository(url);
> >
> >          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> > -        Requirement req = new RequirementImpl("foo",
> "(someKey=someOtherVal)");
> > +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),
> "foo", "(someKey=someOtherVal)");
> >
> >          Map<Requirement, Collection<Capability>> result =
> repo.findProviders(Collections.singleton(req));
> >          assertEquals(1, result.size());
> > @@ -157,7 +157,7 @@ public class OSGiRepositoryImplTest exte
> >          repoAdmin.addRepository(url);
> >
> >          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> > -        Requirement req = new RequirementImpl("foo", "(someKey=*)");
> > +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),
> "foo", "(someKey=*)");
> >
> >          Map<Requirement, Collection<Capability>> result =
> repo.findProviders(Collections.singleton(req));
> >          assertEquals(1, result.size());
> > @@ -181,7 +181,7 @@ public class OSGiRepositoryImplTest exte
> >          repoAdmin.addRepository(url);
> >
> >          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> > -        Requirement req = new RequirementImpl("osgi.wiring.package",
> > +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),
> "osgi.wiring.package",
> >                  "(&(osgi.wiring.package=org.apache.commons.logging)(
> version>=1.0.1)(!(version>=2)))");
> >
> >          Map<Requirement, Collection<Capability>> result =
> repo.findProviders(Collections.singleton(req));
> > @@ -230,12 +230,12 @@ public class OSGiRepositoryImplTest exte
> >
> >          BundleRevision br = Mockito.mock(BundleRevision.class);
> >          Mockito.when(sysBundle.adapt(BundleRevision.class)).
> thenReturn(br);
> > -        Capability cap1 = new CapabilityImpl("some.system.cap",
> > -                Collections.<String, Object>singletonMap("sys.cap",
> "something"),
> > -                Collections.singletonMap("x", "y"));
> > -        Capability cap2 = new CapabilityImpl("some.system.cap",
> > -                Collections.<String, Object>singletonMap("sys.cap",
> "somethingelse"),
> > -                Collections.<String, String>emptyMap());
> > +        Capability cap1 = new CapabilityImpl(Mockito.mock(Resource.class),
> "some.system.cap",
> > +                Collections.singletonMap("x", "y"),
> > +                Collections.<String, Object>singletonMap("sys.cap",
> "something"));
> > +        Capability cap2 = new CapabilityImpl(Mockito.mock(Resource.class),
> "some.system.cap",
> > +                Collections.<String, String>emptyMap(),
> > +                Collections.<String, Object>singletonMap("sys.cap",
> "somethingelse"));
> >          Mockito.when(br.getCapabilities(null)).thenReturn(Arrays.asList(cap1,
> cap2));
> >
> >          BundleContext bc = Mockito.mock(BundleContext.class);
> >
> > Modified: felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryXMLTest.java
> > URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/
> src/test/java/org/apache/felix/bundlerepository/impl/
> OSGiRepositoryXMLTest.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> > ============================================================
> ==================
> > --- felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryXMLTest.java (original)
> > +++ felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRepositoryXMLTest.java Fri Apr 20
> 09:23:35 2018
> > @@ -51,7 +51,8 @@ public class OSGiRepositoryXMLTest exten
> >          repoAdmin.addRepository(url);
> >
> >          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> > -        Requirement req = new RequirementImpl("osgi.identity",
> > +        Requirement req = new RequirementImpl(Mockito.mock(
> Resource.class),
> > +                "osgi.identity",
> >                  "(osgi.identity=cdi-subsystem)");
> >
> >          Map<Requirement, Collection<Capability>> result = repo
> > @@ -126,7 +127,8 @@ public class OSGiRepositoryXMLTest exten
> >          repoAdmin.addRepository(url);
> >
> >          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> > -        Requirement req = new RequirementImpl("osgi.identity",
> > +        Requirement req = new RequirementImpl(Mockito.mock(
> Resource.class),
> > +                "osgi.identity",
> >                  "(license=http://www.opensource.org/licenses/
> mytestlicense)");
> >
> >          Map<Requirement, Collection<Capability>> result = repo
> > @@ -145,7 +147,7 @@ public class OSGiRepositoryXMLTest exten
> >          repoAdmin.addRepository(url);
> >
> >          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> > -        Requirement req = new RequirementImpl("foo", "(bar=toast)");
> > +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),"foo",
> "(bar=toast)");
> >
> >          Map<Requirement, Collection<Capability>> result = repo
> >                  .findProviders(Collections.singleton(req));
> >
> > Modified: felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRequirementAdapterTest.java
> > URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/
> src/test/java/org/apache/felix/bundlerepository/impl/
> OSGiRequirementAdapterTest.java?rev=1829639&r1=1829638&
> r2=1829639&view=diff
> > ============================================================
> ==================
> > --- felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRequirementAdapterTest.java (original)
> > +++ felix/trunk/bundlerepository/src/test/java/org/apache/
> felix/bundlerepository/impl/OSGiRequirementAdapterTest.java Fri Apr 20
> 09:23:35 2018
> > @@ -24,7 +24,9 @@ import java.util.Map;
> >  import junit.framework.TestCase;
> >
> >  import org.apache.felix.utils.resource.RequirementImpl;
> > +import org.mockito.Mockito;
> >  import org.osgi.resource.Requirement;
> > +import org.osgi.resource.Resource;
> >
> >  public class OSGiRequirementAdapterTest extends TestCase
> >  {
> > @@ -38,7 +40,7 @@ public class OSGiRequirementAdapterTest
> >          dirs.put("resolution", "optional");
> >          dirs.put("test", "test");
> >
> > -        Requirement req = new RequirementImpl("osgi.wiring.package",
> attrs, dirs);
> > +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),
> "osgi.wiring.package", dirs, attrs);
> >          OSGiRequirementAdapter adapter = new
> OSGiRequirementAdapter(req);
> >
> >          assertEquals("(package=y)", adapter.getFilter());
> >
> > Modified: felix/trunk/utils/pom.xml
> > URL: http://svn.apache.org/viewvc/felix/trunk/utils/pom.xml?rev=
> 1829639&r1=1829638&r2=1829639&view=diff
> > ============================================================
> ==================
> > --- felix/trunk/utils/pom.xml (original)
> > +++ felix/trunk/utils/pom.xml Fri Apr 20 09:23:35 2018
> > @@ -36,17 +36,21 @@
> >          <url>http://svn.apache.org/repos/asf/felix/utils</url>
> >      </scm>
> >
> > +    <properties>
> > +        <felix.java.version>7</felix.java.version>
> > +    </properties>
> > +
> >      <dependencies>
> >          <dependency>
> >              <groupId>org.osgi</groupId>
> > -            <artifactId>org.osgi.core</artifactId>
> > +            <artifactId>osgi.core</artifactId>
> >              <version>5.0.0</version>
> >              <scope>provided</scope>
> >          </dependency>
> >          <dependency>
> >              <groupId>org.osgi</groupId>
> > -            <artifactId>org.osgi.compendium</artifactId>
> > -            <version>4.2.0</version>
> > +            <artifactId>osgi.cmpn</artifactId>
> > +            <version>5.0.0</version>
> >              <scope>provided</scope>
> >          </dependency>
> >      </dependencies>
> > @@ -62,6 +66,13 @@
> >                      </excludes>
> >                  </configuration>
> >              </plugin>
> > +            <plugin>
> > +                <artifactId>maven-compiler-plugin</artifactId>
> > +                <configuration>
> > +                    <source>1.7</source>
> > +                    <target>1.7</target>
> > +                </configuration>
> > +            </plugin>
> >          </plugins>
> >      </build>
> >  </project>
> >
> > Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/
> collections/StringArrayMap.java
> > URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/
> java/org/apache/felix/utils/collections/StringArrayMap.
> java?rev=1829639&view=auto
> > ============================================================
> ==================
> > --- felix/trunk/utils/src/main/java/org/apache/felix/utils/
> collections/StringArrayMap.java (added)
> > +++ felix/trunk/utils/src/main/java/org/apache/felix/utils/
> collections/StringArrayMap.java Fri Apr 20 09:23:35 2018
> > @@ -0,0 +1,339 @@
> > +/*
> > + * 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.AbstractCollection;
> > +import java.util.AbstractSet;
> > +import java.util.Arrays;
> > +import java.util.Collection;
> > +import java.util.Collections;
> > +import java.util.Iterator;
> > +import java.util.Map;
> > +import java.util.NoSuchElementException;
> > +import java.util.Objects;
> > +import java.util.Set;
> > +
> > +public class StringArrayMap<V> implements Map<String, V> {
> > +
> > +    protected Object[] table;
> > +    protected int size;
> > +
> > +    public static <T> Map<String, T> reduceMemory(Map<String, T> map) {
> > +        if (map == null) {
> > +            return Collections.emptyMap();
> > +        }
> > +        switch (map.size()) {
> > +            case 0:
> > +                return Collections.emptyMap();
> > +            case 1:
> > +                Entry<String, T> e = map.entrySet().iterator().next();
> > +                return Collections.singletonMap(e.getKey().intern(),
> e.getValue());
> > +            default:
> > +                if (map instanceof StringArrayMap) {
> > +                    @SuppressWarnings("unchecked")
> > +                    StringArrayMap<T> m = (StringArrayMap) map;
> > +                    if (m.size == m.table.length / 2) {
> > +                        return map;
> > +                    }
> > +                }
> > +                return new StringArrayMap<>(map);
> > +        }
> > +    }
> > +
> > +    public StringArrayMap(Map<String, ? extends V> map) {
> > +        if (map instanceof StringArrayMap) {
> > +            size = ((StringArrayMap) map).size;
> > +            table = Arrays.copyOf(((StringArrayMap) map).table, size *
> 2);
> > +        } else {
> > +            size = 0;
> > +            table = new Object[map.size() * 2];
> > +            for (Entry<String, ? extends V> e : map.entrySet()) {
> > +                int i = size++ << 1;
> > +                table[i++] = e.getKey().intern();
> > +                table[i] = e.getValue();
> > +            }
> > +        }
> > +    }
> > +
> > +    public StringArrayMap() {
> > +        this(32);
> > +    }
> > +
> > +    public StringArrayMap(int capacity) {
> > +        table = new Object[capacity * 2];
> > +        size = 0;
> > +    }
> > +
> > +    @SuppressWarnings("unchecked")
> > +    public V get(Object key) {
> > +        String k = ((String) key).intern();
> > +        for (int i = 0, l = size << 1; i < l; i += 2) {
> > +            if (k == table[i]) {
> > +                return (V) table[i + 1];
> > +            }
> > +        }
> > +        return null;
> > +    }
> > +
> > +    @SuppressWarnings("unchecked")
> > +    public V put(String key, V value) {
> > +        return (V) doPut(key, value);
> > +    }
> > +
> > +    protected Object doPut(String key, Object value) {
> > +        key = key.intern();
> > +        for (int i = 0, l = size << 1; i < l; i += 2) {
> > +            if (key == table[i]) {
> > +                Object old = table[i + 1];
> > +                table[i + 1] = value;
> > +                return old;
> > +            }
> > +        }
> > +        if (table.length == 0) {
> > +            table = new Object[2];
> > +        } else if (size * 2 == table.length) {
> > +            Object[] n = new Object[table.length * 2];
> > +            System.arraycopy(table, 0, n, 0, table.length);
> > +            table = n;
> > +        }
> > +        int i = size++ << 1;
> > +        table[i++] = key;
> > +        table[i] = value;
> > +        return null;
> > +    }
> > +
> > +    public Set<String> keySet() {
> > +        return new AbstractSet<String>() {
> > +            @Override
> > +            public Iterator<String> iterator() {
> > +                return new Iterator<String>() {
> > +                    int index = 0;
> > +
> > +                    @Override
> > +                    public boolean hasNext() {
> > +                        return index < size;
> > +                    }
> > +
> > +                    @Override
> > +                    public String next() {
> > +                        if (index >= size) {
> > +                            throw new NoSuchElementException();
> > +                        }
> > +                        return (String) table[(index++ << 1)];
> > +                    }
> > +
> > +                    public void remove() {
> > +                        throw new UnsupportedOperationException(
> "remove");
> > +                    }
> > +                };
> > +            }
> > +
> > +            @Override
> > +            public int size() {
> > +                return size;
> > +            }
> > +        };
> > +    }
> > +
> > +    public Collection<V> values() {
> > +        return new AbstractCollection<V>() {
> > +            @Override
> > +            public Iterator<V> iterator() {
> > +                return new Iterator<V>() {
> > +                    int index = 0;
> > +
> > +                    public boolean hasNext() {
> > +                        return index < size;
> > +                    }
> > +
> > +                    @SuppressWarnings("unchecked")
> > +                    public V next() {
> > +                        if (index >= size) {
> > +                            throw new NoSuchElementException();
> > +                        }
> > +                        return (V) table[(index++ << 1) + 1];
> > +                    }
> > +
> > +                    public void remove() {
> > +                        throw new UnsupportedOperationException(
> "remove");
> > +                    }
> > +                };
> > +            }
> > +
> > +            @Override
> > +            public int size() {
> > +                return size;
> > +            }
> > +        };
> > +    }
> > +
> > +    public Set<Entry<String, V>> entrySet() {
> > +        return new AbstractSet<Entry<String, V>>() {
> > +            @Override
> > +            public Iterator<Entry<String, V>> iterator() {
> > +                return new Iterator<Entry<String, V>>() {
> > +                    int index = 0;
> > +
> > +                    public boolean hasNext() {
> > +                        return index < size;
> > +                    }
> > +
> > +                    @SuppressWarnings("unchecked")
> > +                    public Entry<String, V> next() {
> > +                        if (index >= size) {
> > +                            throw new NoSuchElementException();
> > +                        }
> > +                        final int i = index << 1;
> > +                        index++;
> > +                        return new Entry<String, V>() {
> > +
> > +                            public String getKey() {
> > +                                return (String) table[i];
> > +                            }
> > +
> > +                            public V getValue() {
> > +                                return (V) table[i + 1];
> > +                            }
> > +
> > +                            public V setValue(V value) {
> > +                                throw new UnsupportedOperationException(
> );
> > +                            }
> > +                        };
> > +                    }
> > +
> > +                    public void remove() {
> > +                        throw new UnsupportedOperationException(
> "remove");
> > +                    }
> > +                };
> > +            }
> > +
> > +            @Override
> > +            public int size() {
> > +                return size;
> > +            }
> > +        };
> > +    }
> > +
> > +    public int size() {
> > +        return size;
> > +    }
> > +
> > +    public boolean isEmpty() {
> > +        return size == 0;
> > +    }
> > +
> > +    public boolean containsKey(Object key) {
> > +        String k = ((String) key).intern();
> > +        for (int i = 0, l = size * 2; i < l; i += 2) {
> > +            if (table[i] == k) {
> > +                return true;
> > +            }
> > +        }
> > +        return false;
> > +    }
> > +
> > +    public boolean containsValue(Object value) {
> > +        for (int i = 0, l = size * 2; i < l; i += 2) {
> > +            if (Objects.equals(table[i + 1], value)) {
> > +                return true;
> > +            }
> > +        }
> > +        return false;
> > +    }
> > +
> > +    @SuppressWarnings("unchecked")
> > +    public V remove(Object key) {
> > +        String k = ((String) key).intern();
> > +        for (int i = 0, l = size * 2; i < l; i += 2) {
> > +            if (table[i] == k) {
> > +                Object v = table[i + 1];
> > +                if (i < l - 2) {
> > +                    System.arraycopy(table, i + 2, table, i, l - 2 - i);
> > +                }
> > +                table[l - 1] = null;
> > +                table[l - 2] = null;
> > +                size--;
> > +                return (V) v;
> > +            }
> > +        }
> > +        return null;
> > +    }
> > +
> > +    public void putAll(Map<? extends String, ? extends V> m) {
> > +        for (Entry<? extends String, ? extends V> e : m.entrySet()) {
> > +            put(e.getKey(), e.getValue());
> > +        }
> > +    }
> > +
> > +    public void clear() {
> > +        size = 0;
> > +        Arrays.fill(table, null);
> > +    }
> > +
> > +    public int hashCode() {
> > +        int result = 1;
> > +        for (int i = 0; i < size * 2; i++)
> > +            result = 31 * result + (table[i] == null ? 0 :
> table[i].hashCode());
> > +        return result;
> > +    }
> > +
> > +    public boolean equals(Object o) {
> > +        if (o == this)
> > +            return true;
> > +        if (!(o instanceof Map))
> > +            return false;
> > +        Map<?,?> m = (Map<?,?>) o;
> > +        if (m.size() != size())
> > +            return false;
> > +        try {
> > +            for (int i = 0, l = size * 2; i < l; i += 2) {
> > +                Object key = table[i];
> > +                Object value = table[i+1];
> > +                if (value == null) {
> > +                    if (!(m.get(key)==null && m.containsKey(key)))
> > +                        return false;
> > +                } else {
> > +                    if (!value.equals(m.get(key)))
> > +                        return false;
> > +                }
> > +            }
> > +        } catch (ClassCastException | NullPointerException unused) {
> > +            return false;
> > +        }
> > +        return true;
> > +    }
> > +
> > +    public String toString() {
> > +        if (size == 0)
> > +            return "{}";
> > +
> > +        StringBuilder sb = new StringBuilder();
> > +        sb.append('{');
> > +        for (int i = 0, l = size * 2; i < l; i += 2) {
> > +            if (i > 0) {
> > +                sb.append(',').append(' ');
> > +            }
> > +            sb.append(table[i]);
> > +            sb.append('=');
> > +            sb.append(table[i+1] == this ? "(this Map)" : table[i+1]);
> > +        }
> > +        return sb.append('}').toString();
> > +    }
> > +
> > +}
> >
> > Modified: felix/trunk/utils/src/main/java/org/apache/felix/utils/
> resource/AbstractCapabilityRequirement.java
> > URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/
> java/org/apache/felix/utils/resource/AbstractCapabilityRequirement.
> java?rev=1829639&r1=1829638&r2=1829639&view=diff
> > ============================================================
> ==================
> > --- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/
> AbstractCapabilityRequirement.java (original)
> > +++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/
> AbstractCapabilityRequirement.java Fri Apr 20 09:23:35 2018
> > @@ -16,38 +16,32 @@
> >   */
> >  package org.apache.felix.utils.resource;
> >
> > +import org.apache.felix.utils.collections.StringArrayMap;
> > +import org.osgi.framework.Version;
> >  import org.osgi.resource.Resource;
> >
> > -import java.util.Collections;
> > -import java.util.HashMap;
> >  import java.util.Map;
> > +import java.util.Objects;
> >
> >  abstract class AbstractCapabilityRequirement {
> >
> > -    /** The namespace. Required. */
> > -    private final String namespace;
> > +    /** The resource. Required. */
> > +    protected final Resource resource;
> >
> > -    /** Optional resource. */
> > -    private final Resource resource;
> > +    /** The namespace. Required. */
> > +    protected final String namespace;
> >
> >      /** Optional attributes. Never null. */
> > -    private final Map<String, Object> attributes;
> > +    protected final Map<String, String> directives;
> >
> >      /** Optional attributes. Never null. */
> > -    private final Map<String, String> directives;
> > +    protected final Map<String, Object> attributes;
> >
> > -    AbstractCapabilityRequirement(final String ns, final Map<String,
> Object> attrs, final Map<String, String> dirs, final Resource res) {
> > -        if ( ns == null ) {
> > -            throw new IllegalArgumentException("Namespace must not be
> null.");
> > -        }
> > -        namespace = ns;
> > -        attributes = attrs == null
> > -                ? Collections.<String, Object>emptyMap()
> > -                : Collections.unmodifiableMap(new HashMap<String,
> Object>(attrs));
> > -        directives = dirs == null
> > -                ? Collections.<String,String>emptyMap()
> > -                : Collections.unmodifiableMap(new
> HashMap<String,String>(dirs));
> > -                resource = res;
> > +    AbstractCapabilityRequirement(final Resource res, final String ns,
> final Map<String, String> dirs, final Map<String, Object> attrs) {
> > +        resource = Objects.requireNonNull(res, "Resource must not be
> null.");
> > +        namespace = Objects.requireNonNull(ns, "Namespace must not be
> null.");
> > +        directives = StringArrayMap.reduceMemory(dirs);
> > +        attributes = StringArrayMap.reduceMemory(attrs);
> >      }
> >
> >      /**
> > @@ -82,45 +76,98 @@ abstract class AbstractCapabilityRequire
> >          return resource;
> >      }
> >
> > -    @Override
> > -    public int hashCode() {
> > -        final int prime = 31;
> > -        int result = 1;
> > -        result = prime * result + attributes.hashCode();
> > -        result = prime * result + directives.hashCode();
> > -        result = prime * result + namespace.hashCode();
> > -
> > -        if (resource != null)
> > -            result = prime * result + resource.hashCode();
> >
> > -        return result;
> > +    @Override
> > +    public boolean equals(Object o) {
> > +        if (this == o) return true;
> > +        if (o == null || getClass() != o.getClass()) return false;
> > +        AbstractCapabilityRequirement that = (AbstractCapabilityRequirement)
> o;
> > +        return Objects.equals(resource, that.resource) &&
> > +                Objects.equals(namespace, that.namespace) &&
> > +                Objects.equals(attributes, that.attributes) &&
> > +                Objects.equals(directives, that.directives);
> >      }
> >
> >      @Override
> > -    public boolean equals(Object obj) {
> > -        if (this == obj)
> > -            return true;
> > -        if (obj == null)
> > -            return false;
> > -        if (getClass() != obj.getClass())
> > -            return false;
> > -        AbstractCapabilityRequirement other = (AbstractCapabilityRequirement)
> obj;
> > -        if (!namespace.equals(other.namespace))
> > -            return false;
> > -        if (!attributes.equals(other.attributes))
> > -            return false;
> > -        if (!directives.equals(other.directives))
> > -            return false;
> > -        if (resource == null) {
> > -            return other.resource == null;
> > -        } else {
> > -            return resource.equals(other.resource);
> > -        }
> > +    public int hashCode() {
> > +        return Objects.hash(resource, namespace, attributes,
> directives);
> >      }
> >
> >      @Override
> >      public String toString() {
> > -        return getClass().getSimpleName() + " [resource=" + resource +
> ", namespace=" + namespace + ", attributes=" + attributes
> > -                + ", directives=" + directives + "]";
> > +        return toString(getResource(), getNamespace(), getAttributes(),
> getDirectives());
> > +    }
> > +
> > +    public static String toString(Resource res, String namespace,
> Map<String, Object> attrs, Map<String, String> dirs) {
> > +        StringBuilder sb = new StringBuilder();
> > +        if (res != null) {
> > +            sb.append("[").append(res).append("] ");
> > +        }
> > +        sb.append(namespace);
> > +        for (String key : attrs.keySet()) {
> > +            sb.append("; ");
> > +            append(sb, key, attrs.get(key), true);
> > +        }
> > +        for (String key : dirs.keySet()) {
> > +            sb.append("; ");
> > +            append(sb, key, dirs.get(key), false);
> > +        }
> > +        return sb.toString();
> > +    }
> > +
> > +    private static void append(StringBuilder sb, String key, Object
> val, boolean attribute) {
> > +        sb.append(key);
> > +        if (val instanceof Version) {
> > +            sb.append(":Version=");
> > +            sb.append(val);
> > +        } else if (val instanceof Long) {
> > +            sb.append(":Long=");
> > +            sb.append(val);
> > +        } else if (val instanceof Double) {
> > +            sb.append(":Double=");
> > +            sb.append(val);
> > +        } else if (val instanceof Iterable) {
> > +            Iterable<?> it = (Iterable<?>) val;
> > +            String scalar = null;
> > +            for (Object o : it) {
> > +                String ts;
> > +                if (o instanceof String) {
> > +                    ts = "String";
> > +                } else if (o instanceof Long) {
> > +                    ts = "Long";
> > +                } else if (o instanceof Double) {
> > +                    ts = "Double";
> > +                } else if (o instanceof Version) {
> > +                    ts = "Version";
> > +                } else {
> > +                    throw new IllegalArgumentException("Unsupported
> scalar type: " + o);
> > +                }
> > +                if (scalar == null) {
> > +                    scalar = ts;
> > +                } else if (!scalar.equals(ts)) {
> > +                    throw new IllegalArgumentException("Unconsistent
> list type for attribute " + key);
> > +                }
> > +            }
> > +            sb.append(":List<").append(scalar).append(">=");
> > +            sb.append("\"");
> > +            boolean first = true;
> > +            for (Object o : it) {
> > +                if (first) {
> > +                    first = false;
> > +                } else {
> > +                    sb.append(",");
> > +                }
> > +                sb.append(o.toString().replace("\"",
> "\\\"").replace(",", "\\,"));
> > +            }
> > +            sb.append("\"");
> > +        } else {
> > +            sb.append(attribute ? "=" : ":=");
> > +            String s = val.toString();
> > +            if (s.matches("[0-9a-zA-Z_\\-.]*")) {
> > +                sb.append(s);
> > +            } else {
> > +                sb.append("\"").append(s.replace("\"",
> "\\\\")).append("\"");
> > +            }
> > +        }
> >      }
> >  }
> >
> > Modified: felix/trunk/utils/src/main/java/org/apache/felix/utils/
> resource/CapabilityImpl.java
> > URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/
> java/org/apache/felix/utils/resource/CapabilityImpl.java?
> rev=1829639&r1=1829638&r2=1829639&view=diff
> > ============================================================
> ==================
> > --- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java
> (original)
> > +++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java
> Fri Apr 20 09:23:35 2018
> > @@ -16,35 +16,50 @@
> >   */
> >  package org.apache.felix.utils.resource;
> >
> > +import org.osgi.framework.Constants;
> >  import org.osgi.resource.Capability;
> >  import org.osgi.resource.Resource;
> >
> > +import java.util.Collections;
> > +import java.util.HashSet;
> > +import java.util.List;
> >  import java.util.Map;
> > +import java.util.Set;
> >
> >  /**
> >   * Implementation of the OSGi Capability interface.
> >   */
> >  public class CapabilityImpl extends AbstractCapabilityRequirement
> implements Capability {
> > -    /**
> > -     * Create a capability that is not associated with a resource.
> > -     * @param res The resource associated with the capability. May be
> null.
> > -     * @param ns The namespace of the capability.
> > -     * @param attrs The attributes of the capability.
> > -     * @param dirs The directives of the capability.
> > -     */
> > -    public CapabilityImpl(String ns, Map<String, Object> attrs,
> Map<String, String> dirs) {
> > -        this(ns, attrs, dirs, null);
> > -    }
> > +
> > +    protected final Set<String> mandatory;
> >
> >      /**
> >       * Create a capability.
> > +     * @param res The resource associated with the capability.
> >       * @param ns The namespace of the capability.
> >       * @param attrs The attributes of the capability.
> >       * @param dirs The directives of the capability.
> > -     * @param res The resource associated with the capability. May be
> null.
> >       */
> > -    public CapabilityImpl(String ns, Map<String, Object> attrs,
> Map<String, String> dirs, Resource res) {
> > -        super(ns, attrs, dirs, res);
> > +    public CapabilityImpl(Resource res, String ns, Map<String, String>
> dirs, Map<String, Object> attrs) {
> > +        super(res, ns, dirs, attrs);
> > +
> > +        // Handle mandatory directive
> > +        Set<String> mandatory = Collections.emptySet();
> > +        String value = this.directives.get(Constants.
> MANDATORY_DIRECTIVE);
> > +        if (value != null) {
> > +            List<String> names = ResourceBuilder.parseDelimitedString(value,
> ",");
> > +            mandatory = new HashSet<>(names.size());
> > +            for (String name : names) {
> > +                // If attribute exists, then record it as mandatory.
> > +                if (this.attributes.containsKey(name)) {
> > +                    mandatory.add(name);
> > +                    // Otherwise, report an error.
> > +                } else {
> > +                    throw new IllegalArgumentException("Mandatory
> attribute '" + name + "' does not exist.");
> > +                }
> > +            }
> > +        }
> > +        this.mandatory = mandatory;
> >      }
> >
> >      /**
> > @@ -54,6 +69,10 @@ public class CapabilityImpl extends Abst
> >       * @param resource The resource to be associated with the capability
> >       */
> >      public CapabilityImpl(Resource resource, Capability capability) {
> > -        this(capability.getNamespace(), capability.getAttributes(),
> capability.getDirectives(), resource);
> > +        this(resource, capability.getNamespace(),
> capability.getDirectives(), capability.getAttributes());
> > +    }
> > +
> > +    public boolean isAttributeMandatory(String name) {
> > +        return !mandatory.isEmpty() && mandatory.contains(name);
> >      }
> >  }
> >
> > Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/
> resource/CapabilitySet.java
> > URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/
> java/org/apache/felix/utils/resource/CapabilitySet.java?
> rev=1829639&view=auto
> > ============================================================
> ==================
> > --- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java
> (added)
> > +++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java
> Fri Apr 20 09:23:35 2018
> > @@ -0,0 +1,469 @@
> > +/*
> > + * 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.resource;
> > +
> > +import java.lang.reflect.Array;
> > +import java.lang.reflect.Constructor;
> > +import java.util.ArrayList;
> > +import java.util.Collection;
> > +import java.util.HashMap;
> > +import java.util.HashSet;
> > +import java.util.Iterator;
> > +import java.util.List;
> > +import java.util.Map;
> > +import java.util.Map.Entry;
> > +import java.util.Set;
> > +import java.util.TreeMap;
> > +
> > +import org.apache.felix.utils.version.VersionTable;
> > +import org.osgi.framework.Constants;
> > +import org.osgi.framework.Version;
> > +import org.osgi.resource.Capability;
> > +
> > +@SuppressWarnings("rawtypes")
> > +public class CapabilitySet {
> > +
> > +    private static final Class<?>[] STRING_CLASS = new Class[]
> {String.class};
> > +
> > +    private final Map<String, Map<Object, Set<Capability>>> indices;
> > +    private final Set<Capability> capSet = new HashSet<>();
> > +
> > +    public CapabilitySet(List<String> indexProps) {
> > +        indices = new TreeMap<>();
> > +        for (int i = 0; (indexProps != null) && (i <
> indexProps.size()); i++) {
> > +            indices.put(indexProps.get(i), new HashMap<Object,
> Set<Capability>>());
> > +        }
> > +    }
> > +
> > +    public void dump() {
> > +        for (Entry<String, Map<Object, Set<Capability>>> entry :
> indices.entrySet()) {
> > +            boolean header1 = false;
> > +            for (Entry<Object, Set<Capability>> entry2 :
> entry.getValue().entrySet()) {
> > +                boolean header2 = false;
> > +                for (Capability cap : entry2.getValue()) {
> > +                    if (!header1) {
> > +                        System.out.println(entry.getKey() + ":");
> > +                        header1 = true;
> > +                    }
> > +                    if (!header2) {
> > +                        System.out.println("   " + entry2.getKey());
> > +                        header2 = true;
> > +                    }
> > +                    System.out.println("      " + cap);
> > +                }
> > +            }
> > +        }
> > +    }
> > +
> > +    public void addCapability(Capability cap) {
> > +        capSet.add(cap);
> > +
> > +        // Index capability.
> > +        for (Entry<String, Map<Object, Set<Capability>>> entry :
> indices.entrySet()) {
> > +            Object value = cap.getAttributes().get(entry.getKey());
> > +            if (value != null) {
> > +                if (value.getClass().isArray()) {
> > +                    value = convertArrayToList(value);
> > +                }
> > +
> > +                Map<Object, Set<Capability>> index = entry.getValue();
> > +
> > +                if (value instanceof Collection) {
> > +                    Collection c = (Collection) value;
> > +                    for (Object o : c) {
> > +                        indexCapability(index, cap, o);
> > +                    }
> > +                } else {
> > +                    indexCapability(index, cap, value);
> > +                }
> > +            }
> > +        }
> > +    }
> > +
> > +    private void indexCapability(Map<Object, Set<Capability>> index,
> Capability cap, Object capValue) {
> > +        // TODO: when JDK8, should be:
> > +        // TODO: index.computeIfAbsent(capValue, k -> new
> HashSet<>()).add(cap);
> > +        Set<Capability> set = index.get(capValue);
> > +        if (set == null) {
> > +            set = new HashSet<>();
> > +            index.put(capValue, set);
> > +        }
> > +        set.add(cap);
> > +    }
> > +
> > +    public void removeCapability(Capability cap) {
> > +        if (capSet.remove(cap)) {
> > +            for (Entry<String, Map<Object, Set<Capability>>> entry :
> indices.entrySet()) {
> > +                Object value = cap.getAttributes().get(entry.getKey());
> > +                if (value != null) {
> > +                    if (value.getClass().isArray()) {
> > +                        value = convertArrayToList(value);
> > +                    }
> > +
> > +                    Map<Object, Set<Capability>> index =
> entry.getValue();
> > +
> > +                    if (value instanceof Collection) {
> > +                        Collection c = (Collection) value;
> > +                        for (Object o : c) {
> > +                            deindexCapability(index, cap, o);
> > +                        }
> > +                    } else {
> > +                        deindexCapability(index, cap, value);
> > +                    }
> > +                }
> > +            }
> > +        }
> > +    }
> > +
> > +    private void deindexCapability(
> > +            Map<Object, Set<Capability>> index, Capability cap, Object
> value) {
> > +        Set<Capability> caps = index.get(value);
> > +        if (caps != null) {
> > +            caps.remove(cap);
> > +            if (caps.isEmpty()) {
> > +                index.remove(value);
> > +            }
> > +        }
> > +    }
> > +
> > +    public Set<Capability> match(SimpleFilter sf, boolean
> obeyMandatory) {
> > +        Set<Capability> matches = match(capSet, sf);
> > +        return obeyMandatory
> > +                ? matchMandatory(matches, sf)
> > +                : matches;
> > +    }
> > +
> > +    @SuppressWarnings("unchecked")
> > +    private Set<Capability> match(Set<Capability> caps, SimpleFilter
> sf) {
> > +        Set<Capability> matches = new HashSet<>();
> > +
> > +        if (sf.getOperation() == SimpleFilter.MATCH_ALL) {
> > +            matches.addAll(caps);
> > +        } else if (sf.getOperation() == SimpleFilter.AND) {
> > +            // Evaluate each subfilter against the remaining
> capabilities.
> > +            // For AND we calculate the intersection of each subfilter.
> > +            // We can short-circuit the AND operation if there are no
> > +            // remaining capabilities.
> > +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> > +            for (int i = 0; (caps.size() > 0) && (i < sfs.size()); i++)
> {
> > +                matches = match(caps, sfs.get(i));
> > +                caps = matches;
> > +            }
> > +        } else if (sf.getOperation() == SimpleFilter.OR) {
> > +            // Evaluate each subfilter against the remaining
> capabilities.
> > +            // For OR we calculate the union of each subfilter.
> > +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> > +            for (SimpleFilter sf1 : sfs) {
> > +                matches.addAll(match(caps, sf1));
> > +            }
> > +        } else if (sf.getOperation() == SimpleFilter.NOT) {
> > +            // Evaluate each subfilter against the remaining
> capabilities.
> > +            // For OR we calculate the union of each subfilter.
> > +            matches.addAll(caps);
> > +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> > +            for (SimpleFilter sf1 : sfs) {
> > +                matches.removeAll(match(caps, sf1));
> > +            }
> > +        } else {
> > +            Map<Object, Set<Capability>> index =
> indices.get(sf.getName());
> > +            if ((sf.getOperation() == SimpleFilter.EQ) && (index !=
> null)) {
> > +                Set<Capability> existingCaps = index.get(sf.getValue());
> > +                if (existingCaps != null) {
> > +                    matches.addAll(existingCaps);
> > +                    matches.retainAll(caps);
> > +                }
> > +            } else {
> > +                for (Capability cap : caps) {
> > +                    Object lhs = cap.getAttributes().get(sf.getName());
> > +                    if (lhs != null) {
> > +                        if (compare(lhs, sf.getValue(),
> sf.getOperation())) {
> > +                            matches.add(cap);
> > +                        }
> > +                    }
> > +                }
> > +            }
> > +        }
> > +
> > +        return matches;
> > +    }
> > +
> > +    public static boolean matches(Capability cap, SimpleFilter sf) {
> > +        return matchesInternal(cap, sf) && matchMandatory(cap, sf);
> > +    }
> > +
> > +    @SuppressWarnings("unchecked")
> > +    private static boolean matchesInternal(Capability cap, SimpleFilter
> sf) {
> > +        boolean matched = true;
> > +
> > +        if (sf.getOperation() == SimpleFilter.MATCH_ALL) {
> > +            matched = true;
> > +        } else if (sf.getOperation() == SimpleFilter.AND) {
> > +            // Evaluate each subfilter against the remaining
> capabilities.
> > +            // For AND we calculate the intersection of each subfilter.
> > +            // We can short-circuit the AND operation if there are no
> > +            // remaining capabilities.
> > +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> > +            for (int i = 0; matched && (i < sfs.size()); i++) {
> > +                matched = matchesInternal(cap, sfs.get(i));
> > +            }
> > +        } else if (sf.getOperation() == SimpleFilter.OR) {
> > +            // Evaluate each subfilter against the remaining
> capabilities.
> > +            // For OR we calculate the union of each subfilter.
> > +            matched = false;
> > +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> > +            for (int i = 0; !matched && (i < sfs.size()); i++) {
> > +                matched = matchesInternal(cap, sfs.get(i));
> > +            }
> > +        } else if (sf.getOperation() == SimpleFilter.NOT) {
> > +            // Evaluate each subfilter against the remaining
> capabilities.
> > +            // For OR we calculate the union of each subfilter.
> > +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> > +            for (SimpleFilter sf1 : sfs) {
> > +                matched = !(matchesInternal(cap, sf1));
> > +            }
> > +        } else {
> > +            matched = false;
> > +            Object lhs = cap.getAttributes().get(sf.getName());
> > +            if (lhs != null) {
> > +                matched = compare(lhs, sf.getValue(),
> sf.getOperation());
> > +            }
> > +        }
> > +
> > +        return matched;
> > +    }
> > +
> > +    private static Set<Capability> matchMandatory(
> > +            Set<Capability> caps, SimpleFilter sf) {
> > +        for (Iterator<Capability> it = caps.iterator(); it.hasNext();) {
> > +            Capability cap = it.next();
> > +            if (!matchMandatory(cap, sf)) {
> > +                it.remove();
> > +            }
> > +        }
> > +        return caps;
> > +    }
> > +
> > +    private static boolean matchMandatory(Capability cap, SimpleFilter
> sf) {
> > +        if (cap instanceof CapabilityImpl) {
> > +            for (Entry<String, Object> entry :
> cap.getAttributes().entrySet()) {
> > +                if (((CapabilityImpl) cap).isAttributeMandatory(
> entry.getKey())
> > +                        && !matchMandatoryAttribute(entry.getKey(),
> sf)) {
> > +                    return false;
> > +                }
> > +            }
> > +        } else {
> > +            String value = cap.getDirectives().get(
> Constants.MANDATORY_DIRECTIVE);
> > +            if (value != null) {
> > +                List<String> names = ResourceBuilder.parseDelimitedString(value,
> ",");
> > +                for (Entry<String, Object> entry :
> cap.getAttributes().entrySet()) {
> > +                    if (names.contains(entry.getKey())
> > +                            && !matchMandatoryAttribute(entry.getKey(),
> sf)) {
> > +                        return false;
> > +                    }
> > +                }
> > +            }
> > +
> > +        }
> > +        return true;
> > +    }
> > +
> > +    private static boolean matchMandatoryAttribute(String attrName,
> SimpleFilter sf) {
> > +        if ((sf.getName() != null) && sf.getName().equals(attrName)) {
> > +            return true;
> > +        } else if (sf.getOperation() == SimpleFilter.AND) {
> > +            List list = (List) sf.getValue();
> > +            for (Object aList : list) {
> > +                SimpleFilter sf2 = (SimpleFilter) aList;
> > +                if ((sf2.getName() != null)
> > +                        && sf2.getName().equals(attrName)) {
> > +                    return true;
> > +                }
> > +            }
> > +        }
> > +        return false;
> > +    }
> > +
> > +    @SuppressWarnings("unchecked")
> > +    private static boolean compare(Object lhs, Object rhsUnknown, int
> op) {
> > +        if (lhs == null) {
> > +            return false;
> > +        }
> > +
> > +        // If this is a PRESENT operation, then just return true
> immediately
> > +        // since we wouldn't be here if the attribute wasn't present.
> > +        if (op == SimpleFilter.PRESENT) {
> > +            return true;
> > +        }
> > +
> > +        // If the type is comparable, then we can just return the
> > +        // result immediately.
> > +        if (lhs instanceof Comparable) {
> > +            // Spec says SUBSTRING is false for all types other than
> string.
> > +            if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof
> String)) {
> > +                return false;
> > +            }
> > +
> > +            Object rhs;
> > +            if (op == SimpleFilter.SUBSTRING) {
> > +                rhs = rhsUnknown;
> > +            } else {
> > +                try {
> > +                    rhs = coerceType(lhs, (String) rhsUnknown);
> > +                } catch (Exception ex) {
> > +                    return false;
> > +                }
> > +            }
> > +
> > +            switch (op) {
> > +            case SimpleFilter.EQ:
> > +                try {
> > +                    return ((Comparable) lhs).compareTo(rhs) == 0;
> > +                } catch (Exception ex) {
> > +                    return false;
> > +                }
> > +            case SimpleFilter.GTE:
> > +                try {
> > +                    return ((Comparable) lhs).compareTo(rhs) >= 0;
> > +                } catch (Exception ex) {
> > +                    return false;
> > +                }
> > +            case SimpleFilter.LTE:
> > +                try {
> > +                    return ((Comparable) lhs).compareTo(rhs) <= 0;
> > +                } catch (Exception ex) {
> > +                    return false;
> > +                }
> > +            case SimpleFilter.APPROX:
> > +                return compareApproximate(lhs, rhs);
> > +            case SimpleFilter.SUBSTRING:
> > +                return SimpleFilter.compareSubstring((List<String>)
> rhs, (String) lhs);
> > +            default:
> > +                throw new RuntimeException("Unknown comparison
> operator: " + op);
> > +            }
> > +        }
> > +
> > +        // If the LHS is not a comparable or boolean, check if it is an
> > +        // array. If so, convert it to a list so we can treat it as a
> > +        // collection.
> > +        if (lhs.getClass().isArray()) {
> > +            lhs = convertArrayToList(lhs);
> > +        }
> > +
> > +        // If LHS is a collection, then call compare() on each element
> > +        // of the collection until a match is found.
> > +        if (lhs instanceof Collection) {
> > +            for (Object o : (Collection) lhs) {
> > +                if (compare(o, rhsUnknown, op)) {
> > +                    return true;
> > +                }
> > +            }
> > +
> > +            return false;
> > +        }
> > +
> > +        // Spec says SUBSTRING is false for all types other than string.
> > +        if (op == SimpleFilter.SUBSTRING) {
> > +            return false;
> > +        }
> > +
> > +        // Since we cannot identify the LHS type, then we can only
> perform
> > +        // equality comparison.
> > +        try {
> > +            return lhs.equals(coerceType(lhs, (String) rhsUnknown));
> > +        } catch (Exception ex) {
> > +            return false;
> > +        }
> > +    }
> > +
> > +    private static boolean compareApproximate(Object lhs, Object rhs) {
> > +        if (rhs instanceof String) {
> > +            return removeWhitespace((String) lhs)
> > +                    .equalsIgnoreCase(removeWhitespace((String) rhs));
> > +        } else if (rhs instanceof Character) {
> > +            return Character.toLowerCase((Character) lhs)
> > +                    == Character.toLowerCase((Character) rhs);
> > +        }
> > +        return lhs.equals(rhs);
> > +    }
> > +
> > +    private static String removeWhitespace(String s) {
> > +        StringBuilder sb = new StringBuilder(s.length());
> > +        for (int i = 0; i < s.length(); i++) {
> > +            if (!Character.isWhitespace(s.charAt(i))) {
> > +                sb.append(s.charAt(i));
> > +            }
> > +        }
> > +        return sb.toString();
> > +    }
> > +
> > +    private static Object coerceType(Object lhs, String rhsString)
> throws Exception {
> > +        // If the LHS expects a string, then we can just return
> > +        // the RHS since it is a string.
> > +        if (lhs.getClass() == rhsString.getClass()) {
> > +            return rhsString;
> > +        }
> > +
> > +        // Try to convert the RHS type to the LHS type by using
> > +        // the string constructor of the LHS class, if it has one.
> > +        Object rhs;
> > +        try {
> > +            if (lhs instanceof Version) {
> > +                rhs = VersionTable.getVersion(rhsString, false);
> > +            } else
> > +            // The Character class is a special case, since its
> constructor
> > +            // does not take a string, so handle it separately.
> > +            if (lhs instanceof Character) {
> > +                rhs = rhsString.charAt(0);
> > +            } else {
> > +                // Spec says we should trim number types.
> > +                if ((lhs instanceof Number) || (lhs instanceof
> Boolean)) {
> > +                    rhsString = rhsString.trim();
> > +                }
> > +                Constructor ctor = lhs.getClass().getConstructor(
> STRING_CLASS);
> > +                ctor.setAccessible(true);
> > +                rhs = ctor.newInstance(rhsString);
> > +            }
> > +        } catch (Exception ex) {
> > +            throw new Exception(
> > +                    "Could not instantiate class "
> > +                            + lhs.getClass().getName()
> > +                            + " from string constructor with argument '"
> > +                            + rhsString + "' because " + ex
> > +            );
> > +        }
> > +
> > +        return rhs;
> > +    }
> > +
> > +    /**
> > +     * This is an ugly utility method to convert an array of primitives
> > +     * to an array of primitive wrapper objects. This method simplifies
> > +     * processing LDAP filters since the special case of primitive
> arrays
> > +     * can be ignored.
> > +     *
> > +     * @param array An array of primitive types.
> > +     * @return An corresponding array using pritive wrapper objects.
> > +     */
> > +    private static List<Object> convertArrayToList(Object array) {
> > +        int len = Array.getLength(array);
> > +        List<Object> list = new ArrayList<>(len);
> > +        for (int i = 0; i < len; i++) {
> > +            list.add(Array.get(array, i));
> > +        }
> > +        return list;
> > +    }
> > +}
> >
> > Modified: felix/trunk/utils/src/main/java/org/apache/felix/utils/
> resource/RequirementImpl.java
> > URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/
> java/org/apache/felix/utils/resource/RequirementImpl.java?
> rev=1829639&r1=1829638&r2=1829639&view=diff
> > ============================================================
> ==================
> > --- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java
> (original)
> > +++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java
> Fri Apr 20 09:23:35 2018
> > @@ -16,6 +16,8 @@
> >   */
> >  package org.apache.felix.utils.resource;
> >
> > +import org.osgi.framework.Constants;
> > +import org.osgi.resource.Capability;
> >  import org.osgi.resource.Namespace;
> >  import org.osgi.resource.Requirement;
> >  import org.osgi.resource.Resource;
> > @@ -27,26 +29,19 @@ import java.util.Map;
> >   * Implementation of the OSGi Requirement interface.
> >   */
> >  public class RequirementImpl extends AbstractCapabilityRequirement
> implements Requirement {
> > -    /**
> > -     * Create a requirement that is not associated with a resource.
> > -     * @param res The resource associated with the requirement.
> > -     * @param ns The namespace of the requirement.
> > -     * @param attrs The attributes of the requirement.
> > -     * @param dirs The directives of the requirement.
> > -     */
> > -    public RequirementImpl(String ns, Map<String, Object> attrs,
> Map<String, String> dirs) {
> > -        this(ns, attrs, dirs, null);
> > -    }
> > +
> > +    private final SimpleFilter filter;
> > +    private final boolean optional;
> >
> >      /**
> >       * Create a requirement.
> > +     * @param res The resource associated with the requirement.
> >       * @param ns The namespace of the requirement.
> >       * @param attrs The attributes of the requirement.
> >       * @param dirs The directives of the requirement.
> > -     * @param res The resource associated with the requirement.
> >       */
> > -    public RequirementImpl(String ns, Map<String, Object> attrs,
> Map<String, String> dirs, Resource res) {
> > -        super(ns, attrs, dirs, res);
> > +    public RequirementImpl(Resource res, String ns, Map<String, String>
> dirs, Map<String, Object> attrs) {
> > +        this(res, ns, dirs, attrs, null);
> >      }
> >
> >      /**
> > @@ -54,14 +49,16 @@ public class RequirementImpl extends Abs
> >        *
> >        * This is a convenience method that creates a requirement with
> >        * an empty attributes map and a single 'filter' directive.
> > +     * @param res The resource associated with the requirement.
> >        * @param ns The namespace for the requirement.
> >        * @param filter The filter.
> >        */
> > -     public RequirementImpl(String ns, String filter)
> > +     public RequirementImpl(Resource res, String ns, String filter)
> >       {
> > -         this(ns, Collections.<String, Object>emptyMap(),
> > -             filter == null ? Collections.<String, String> emptyMap() :
> > -             Collections.singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE,
> filter));
> > +         this(res, ns,
> > +             filter == null ? Collections.<String, String>emptyMap() :
> > +             Collections.singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE,
> filter),
> > +             null);
> >       }
> >
> >      /**
> > @@ -71,6 +68,26 @@ public class RequirementImpl extends Abs
> >       * @param resource The resource to be associated with the
> requirement
> >       */
> >      public RequirementImpl(Resource resource, Requirement requirement) {
> > -        this(requirement.getNamespace(), requirement.getAttributes(),
> requirement.getDirectives(), resource);
> > +        this(resource, requirement.getNamespace(),
> requirement.getDirectives(), requirement.getAttributes());
> > +    }
> > +
> > +    public RequirementImpl(Resource resource, String path, Map<String,
> String> dirs, Map<String, Object> attrs, SimpleFilter sf) {
> > +        super(resource, path, dirs, attrs);
> > +        this.filter = sf != null ? sf : SimpleFilter.convert(
> attributes);
> > +        // Find resolution import directives.
> > +        this.optional = Constants.RESOLUTION_OPTIONAL.
> equals(directives.get(Constants.RESOLUTION_DIRECTIVE));
> > +    }
> > +
> > +    public boolean matches(Capability cap) {
> > +        return CapabilitySet.matches(cap, getFilter());
> >      }
> > +
> > +    public boolean isOptional() {
> > +        return optional;
> > +    }
> > +
> > +    public SimpleFilter getFilter() {
> > +        return filter;
> > +    }
> > +
> >  }
> >
> >
>
>
>
> --
> Karl Pauls
> karlpauls@gmail.com
>

Re: svn commit: r1829639 [1/3] - in /felix/trunk: bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/ bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/ utils/ utils/src/main/java/org/apache/felix/utils/collections/ u...

Posted by Karl Pauls <ka...@gmail.com>.
This is great but please try to reference JIRA issues - if we do this
kind of bigger things we should have a JIRA so that we don't get
confused in the future.

regards,

Karl


On Fri, Apr 20, 2018 at 11:23 AM,  <gn...@apache.org> wrote:
> Author: gnodet
> Date: Fri Apr 20 09:23:35 2018
> New Revision: 1829639
>
> URL: http://svn.apache.org/viewvc?rev=1829639&view=rev
> Log:
> Provide optimized resource / filter / capability / requirement / capability set
>
> Added:
>     felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyStringMap.java
>     felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyStringMapTest.java
>       - copied, changed from r1829637, felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyHashMapTest.java
>     felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/StringArrayMap.java
>     felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java
>     felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/ResourceBuilder.java
>     felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/ResourceImpl.java
>     felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/SimpleFilter.java
>     felix/trunk/utils/src/test/java/org/apache/felix/utils/collections/
>     felix/trunk/utils/src/test/java/org/apache/felix/utils/collections/StringArrayMapTest.java
>     felix/trunk/utils/src/test/java/org/apache/felix/utils/resource/SimpleFilterTest.java
> Removed:
>     felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyHashMap.java
>     felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyHashMapTest.java
> Modified:
>     felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FelixResourceAdapter.java
>     felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImpl.java
>     felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImplTest.java
>     felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryXMLTest.java
>     felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRequirementAdapterTest.java
>     felix/trunk/utils/pom.xml
>     felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/AbstractCapabilityRequirement.java
>     felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java
>     felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java
>     felix/trunk/utils/src/test/java/org/apache/felix/utils/resource/CapabilityImplTest.java
>     felix/trunk/utils/src/test/java/org/apache/felix/utils/resource/RequirementImplTest.java
>
> Modified: felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FelixResourceAdapter.java
> URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FelixResourceAdapter.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> ==============================================================================
> --- felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FelixResourceAdapter.java (original)
> +++ felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/FelixResourceAdapter.java Fri Apr 20 09:23:35 2018
> @@ -42,12 +42,12 @@ public class FelixResourceAdapter implem
>
>          if (namespace == null || namespace.equals(IdentityNamespace.IDENTITY_NAMESPACE))
>          {
> -            CapabilityImpl c = OSGiRepositoryImpl.newOSGiIdentityCapability(resource, this);
> +            CapabilityImpl c = OSGiRepositoryImpl.newOSGiIdentityCapability(this, resource);
>              result.add(c);
>          }
>          if (namespace == null || namespace.equals(ContentNamespace.CONTENT_NAMESPACE))
>          {
> -            CapabilityImpl c = OSGiRepositoryImpl.newOSGiContentCapability(resource, this);
> +            CapabilityImpl c = OSGiRepositoryImpl.newOSGiContentCapability(this, resource);
>              result.add(c);
>          }
>
>
> Added: felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyStringMap.java
> URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyStringMap.java?rev=1829639&view=auto
> ==============================================================================
> --- felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyStringMap.java (added)
> +++ felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyStringMap.java Fri Apr 20 09:23:35 2018
> @@ -0,0 +1,67 @@
> +/*
> + * 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.bundlerepository.impl;
> +
> +import java.util.Map;
> +
> +import org.apache.felix.utils.collections.StringArrayMap;
> +
> +/**
> + * A map that can delay the computation of certain values up until the moment that they
> + * are actually needed. Useful for expensive to compute values such as the SHA-256.
> + * This map does <b>not</b> support {@code null} values.
> + */
> +@SuppressWarnings("serial")
> +public class LazyStringMap<V> extends StringArrayMap<V>
> +{
> +    public LazyStringMap(Map<String, ? extends V> map) {
> +        super(map);
> +    }
> +
> +    public LazyStringMap() {
> +    }
> +
> +    public LazyStringMap(int capacity) {
> +        super(capacity);
> +    }
> +
> +    @Override
> +    @SuppressWarnings("unchecked")
> +    public V get(Object key)
> +    {
> +        V val = super.get(key);
> +        if (val instanceof LazyValue) {
> +            val = ((LazyValue<V>) val).compute();
> +            if (val == null) {
> +                throw new NullPointerException("Lazy computed values may not be null");
> +            }
> +            put((String) key, val);
> +        }
> +        return val;
> +    }
> +
> +    public void putLazy(String key, LazyValue<V> lazy) {
> +        super.doPut(key, lazy);
> +    }
> +
> +    public interface LazyValue<V>
> +    {
> +        V compute();
> +    }
> +}
>
> Modified: felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImpl.java
> URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImpl.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> ==============================================================================
> --- felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImpl.java (original)
> +++ felix/trunk/bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImpl.java Fri Apr 20 09:23:35 2018
> @@ -30,10 +30,9 @@ import java.util.Collections;
>  import java.util.HashMap;
>  import java.util.List;
>  import java.util.Map;
> -import java.util.concurrent.Callable;
>
>  import org.apache.felix.bundlerepository.RepositoryAdmin;
> -import org.apache.felix.bundlerepository.impl.LazyHashMap.LazyValue;
> +import org.apache.felix.bundlerepository.Resource;
>  import org.apache.felix.utils.resource.CapabilityImpl;
>  import org.osgi.framework.Filter;
>  import org.osgi.framework.FrameworkUtil;
> @@ -41,7 +40,6 @@ import org.osgi.framework.namespace.Iden
>  import org.osgi.resource.Capability;
>  import org.osgi.resource.Namespace;
>  import org.osgi.resource.Requirement;
> -import org.osgi.resource.Resource;
>  import org.osgi.service.repository.ContentNamespace;
>  import org.osgi.service.repository.Repository;
>
> @@ -120,8 +118,7 @@ class OSGiRepositoryImpl implements Repo
>          caps.add(idCap);
>      }
>
> -    static CapabilityImpl newOSGiIdentityCapability(org.apache.felix.bundlerepository.Resource res,
> -            org.osgi.resource.Resource targetResource)
> +    static CapabilityImpl newOSGiIdentityCapability(org.osgi.resource.Resource or, org.apache.felix.bundlerepository.Resource res)
>      {
>          @SuppressWarnings("unchecked")
>          Map<String, Object> idAttrs = new HashMap<String, Object>(res.getProperties());
> @@ -132,32 +129,34 @@ class OSGiRepositoryImpl implements Repo
>          if (idAttrs.get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE) == null)
>              idAttrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, IdentityNamespace.TYPE_BUNDLE);
>
> -        return new CapabilityImpl(IdentityNamespace.IDENTITY_NAMESPACE, idAttrs, Collections.<String, String> emptyMap(), targetResource);
> +        return new CapabilityImpl(or, IdentityNamespace.IDENTITY_NAMESPACE, Collections.<String, String> emptyMap(), idAttrs);
>      }
>
> -    static CapabilityImpl newOSGiContentCapability(org.apache.felix.bundlerepository.Resource resource,
> -            org.osgi.resource.Resource targetResource)
> +    static CapabilityImpl newOSGiContentCapability(org.osgi.resource.Resource or, Resource resource)
>      {
>          final String uri = resource.getURI();
> -        LazyValue<String, Object> lazyValue =
> -            new LazyValue<String, Object>(ContentNamespace.CONTENT_NAMESPACE, new Callable<Object>()
> -            {
> -                public Object call() throws Exception
> -                {
> -                    // This is expensive to do, so only compute it when actually obtained...
> +        LazyStringMap.LazyValue<String> content = new LazyStringMap.LazyValue<String>() {
> +            public String compute() {
> +                // This is expensive to do, so only compute it when actually obtained...
> +                try {
>                      return OSGiRepositoryImpl.getSHA256(uri);
> +                } catch (IOException e) {
> +                    throw new RuntimeException(e);
> +                } catch (NoSuchAlgorithmException e) {
> +                    throw new RuntimeException(e);
>                  }
> -            });
> -
> +            }
> +        };
>          Object mime = resource.getProperties().get("mime");
>          if (mime == null)
>              mime = "application/vnd.osgi.bundle";
>
> -        Map<String, Object> contentAttrs = new LazyHashMap<String, Object>(Collections.singleton(lazyValue));
> +        Map<String, Object> contentAttrs = new LazyStringMap<Object>(4);
>          contentAttrs.put(ContentNamespace.CAPABILITY_MIME_ATTRIBUTE, mime);
>          contentAttrs.put(ContentNamespace.CAPABILITY_SIZE_ATTRIBUTE, resource.getSize());
>          contentAttrs.put(ContentNamespace.CAPABILITY_URL_ATTRIBUTE, uri);
> -        return new ContentCapabilityImpl(contentAttrs, targetResource);
> +        contentAttrs.put(ContentNamespace.CONTENT_NAMESPACE, content);
> +        return new CapabilityImpl(or, ContentNamespace.CONTENT_NAMESPACE, Collections.<String, String> emptyMap(), contentAttrs);
>      }
>
>      static String getSHA256(String uri) throws IOException, NoSuchAlgorithmException // TODO find a good place for this
> @@ -183,19 +182,4 @@ class OSGiRepositoryImpl implements Repo
>          return sb.toString();
>      }
>
> -    // This capability variant does not take a private copy of the capabilities so that it
> -    // can lazily compute the content hash.
> -    private static class ContentCapabilityImpl extends CapabilityImpl implements Capability {
> -        private final Map<String, Object> contentAttributes;
> -
> -        public ContentCapabilityImpl(Map<String, Object> contentAttrs, Resource targetResource) {
> -            super(ContentNamespace.CONTENT_NAMESPACE, null, null, targetResource);
> -            contentAttributes = Collections.unmodifiableMap(contentAttrs);
> -        }
> -
> -        @Override
> -        public Map<String, Object> getAttributes() {
> -            return contentAttributes;
> -        }
> -    }
>  }
>
> Copied: felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyStringMapTest.java (from r1829637, felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyHashMapTest.java)
> URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyStringMapTest.java?p2=felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyStringMapTest.java&p1=felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyHashMapTest.java&r1=1829637&r2=1829639&rev=1829639&view=diff
> ==============================================================================
> --- felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyHashMapTest.java (original)
> +++ felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/LazyStringMapTest.java Fri Apr 20 09:23:35 2018
> @@ -21,41 +21,39 @@ package org.apache.felix.bundlerepositor
>  import java.util.ArrayList;
>  import java.util.Collection;
>  import java.util.HashMap;
> +import java.util.Map;
>  import java.util.concurrent.Callable;
>  import java.util.concurrent.atomic.AtomicInteger;
>
>  import junit.framework.TestCase;
> +import org.apache.felix.bundlerepository.impl.LazyStringMap.LazyValue;
>
> -import org.apache.felix.bundlerepository.impl.LazyHashMap.LazyValue;
> -
> -public class LazyHashMapTest extends TestCase
> +public class LazyStringMapTest extends TestCase
>  {
>      public void testLazyHashMap() {
>          final AtomicInteger lv1Computed = new AtomicInteger(0);
> -        LazyValue<String, Long> lv1 = new LazyValue<String, Long>("42", new Callable<Long>()
> -        {
> -            public Long call() throws Exception
> -            {
> +        LazyValue<Long> lv1 = new LazyValue<Long>() {
> +            public Long compute() {
>                  lv1Computed.incrementAndGet();
>                  return 24L;
>              }
> -        });
> +        };
>
>          final AtomicInteger lv2Computed = new AtomicInteger(0);
> -        LazyValue<String, Long> lv2 = new LazyValue<String, Long>("zero", new Callable<Long>()
> -        {
> -            public Long call() throws Exception
> -            {
> +        LazyValue<Long> lv2 = new LazyValue<Long>() {
> +            public Long compute() {
>                  lv2Computed.incrementAndGet();
>                  return 0L;
>              }
> -        });
> +        };
>
> -        Collection<LazyValue<String, Long>> lazyValues = new ArrayList<LazyHashMap.LazyValue<String,Long>>();
> +        Collection<LazyValue<Long>> lazyValues = new ArrayList<LazyValue<Long>>();
>          lazyValues.add(lv1);
>          lazyValues.add(lv2);
> -        HashMap<String, Long> lhm = new LazyHashMap<String, Long>(lazyValues);
> +        LazyStringMap<Long> lhm = new LazyStringMap<Long>();
>          lhm.put("1", 2L);
> +        lhm.putLazy("42", lv1);
> +        lhm.putLazy("zero", lv2);
>
>          assertEquals(new Long(2L), lhm.get("1"));
>          assertEquals("No computation should have happened yet", 0, lv1Computed.get());
>
> Modified: felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImplTest.java
> URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImplTest.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> ==============================================================================
> --- felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImplTest.java (original)
> +++ felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryImplTest.java Fri Apr 20 09:23:35 2018
> @@ -58,7 +58,7 @@ public class OSGiRepositoryImplTest exte
>          repoAdmin.addRepository(url);
>
>          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> -        Requirement req = new RequirementImpl("osgi.identity", null);
> +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "osgi.identity", null);
>
>          Map<Requirement, Collection<Capability>> result = repo.findProviders(Collections.singleton(req));
>          assertEquals(1, result.size());
> @@ -117,7 +117,7 @@ public class OSGiRepositoryImplTest exte
>          repoAdmin.addRepository(url);
>
>          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> -        Requirement req = new RequirementImpl("osgi.identity", "(osgi.identity=test_file_2)");
> +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "osgi.identity", "(osgi.identity=test_file_2)");
>
>          Map<Requirement, Collection<Capability>> result = repo.findProviders(Collections.singleton(req));
>          assertEquals(1, result.size());
> @@ -137,7 +137,7 @@ public class OSGiRepositoryImplTest exte
>          repoAdmin.addRepository(url);
>
>          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> -        Requirement req = new RequirementImpl("foo", "(someKey=someOtherVal)");
> +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "foo", "(someKey=someOtherVal)");
>
>          Map<Requirement, Collection<Capability>> result = repo.findProviders(Collections.singleton(req));
>          assertEquals(1, result.size());
> @@ -157,7 +157,7 @@ public class OSGiRepositoryImplTest exte
>          repoAdmin.addRepository(url);
>
>          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> -        Requirement req = new RequirementImpl("foo", "(someKey=*)");
> +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "foo", "(someKey=*)");
>
>          Map<Requirement, Collection<Capability>> result = repo.findProviders(Collections.singleton(req));
>          assertEquals(1, result.size());
> @@ -181,7 +181,7 @@ public class OSGiRepositoryImplTest exte
>          repoAdmin.addRepository(url);
>
>          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> -        Requirement req = new RequirementImpl("osgi.wiring.package",
> +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "osgi.wiring.package",
>                  "(&(osgi.wiring.package=org.apache.commons.logging)(version>=1.0.1)(!(version>=2)))");
>
>          Map<Requirement, Collection<Capability>> result = repo.findProviders(Collections.singleton(req));
> @@ -230,12 +230,12 @@ public class OSGiRepositoryImplTest exte
>
>          BundleRevision br = Mockito.mock(BundleRevision.class);
>          Mockito.when(sysBundle.adapt(BundleRevision.class)).thenReturn(br);
> -        Capability cap1 = new CapabilityImpl("some.system.cap",
> -                Collections.<String, Object>singletonMap("sys.cap", "something"),
> -                Collections.singletonMap("x", "y"));
> -        Capability cap2 = new CapabilityImpl("some.system.cap",
> -                Collections.<String, Object>singletonMap("sys.cap", "somethingelse"),
> -                Collections.<String, String>emptyMap());
> +        Capability cap1 = new CapabilityImpl(Mockito.mock(Resource.class), "some.system.cap",
> +                Collections.singletonMap("x", "y"),
> +                Collections.<String, Object>singletonMap("sys.cap", "something"));
> +        Capability cap2 = new CapabilityImpl(Mockito.mock(Resource.class), "some.system.cap",
> +                Collections.<String, String>emptyMap(),
> +                Collections.<String, Object>singletonMap("sys.cap", "somethingelse"));
>          Mockito.when(br.getCapabilities(null)).thenReturn(Arrays.asList(cap1, cap2));
>
>          BundleContext bc = Mockito.mock(BundleContext.class);
>
> Modified: felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryXMLTest.java
> URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryXMLTest.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> ==============================================================================
> --- felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryXMLTest.java (original)
> +++ felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRepositoryXMLTest.java Fri Apr 20 09:23:35 2018
> @@ -51,7 +51,8 @@ public class OSGiRepositoryXMLTest exten
>          repoAdmin.addRepository(url);
>
>          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> -        Requirement req = new RequirementImpl("osgi.identity",
> +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),
> +                "osgi.identity",
>                  "(osgi.identity=cdi-subsystem)");
>
>          Map<Requirement, Collection<Capability>> result = repo
> @@ -126,7 +127,8 @@ public class OSGiRepositoryXMLTest exten
>          repoAdmin.addRepository(url);
>
>          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> -        Requirement req = new RequirementImpl("osgi.identity",
> +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),
> +                "osgi.identity",
>                  "(license=http://www.opensource.org/licenses/mytestlicense)");
>
>          Map<Requirement, Collection<Capability>> result = repo
> @@ -145,7 +147,7 @@ public class OSGiRepositoryXMLTest exten
>          repoAdmin.addRepository(url);
>
>          Repository repo = new OSGiRepositoryImpl(repoAdmin);
> -        Requirement req = new RequirementImpl("foo", "(bar=toast)");
> +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class),"foo", "(bar=toast)");
>
>          Map<Requirement, Collection<Capability>> result = repo
>                  .findProviders(Collections.singleton(req));
>
> Modified: felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRequirementAdapterTest.java
> URL: http://svn.apache.org/viewvc/felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRequirementAdapterTest.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> ==============================================================================
> --- felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRequirementAdapterTest.java (original)
> +++ felix/trunk/bundlerepository/src/test/java/org/apache/felix/bundlerepository/impl/OSGiRequirementAdapterTest.java Fri Apr 20 09:23:35 2018
> @@ -24,7 +24,9 @@ import java.util.Map;
>  import junit.framework.TestCase;
>
>  import org.apache.felix.utils.resource.RequirementImpl;
> +import org.mockito.Mockito;
>  import org.osgi.resource.Requirement;
> +import org.osgi.resource.Resource;
>
>  public class OSGiRequirementAdapterTest extends TestCase
>  {
> @@ -38,7 +40,7 @@ public class OSGiRequirementAdapterTest
>          dirs.put("resolution", "optional");
>          dirs.put("test", "test");
>
> -        Requirement req = new RequirementImpl("osgi.wiring.package", attrs, dirs);
> +        Requirement req = new RequirementImpl(Mockito.mock(Resource.class), "osgi.wiring.package", dirs, attrs);
>          OSGiRequirementAdapter adapter = new OSGiRequirementAdapter(req);
>
>          assertEquals("(package=y)", adapter.getFilter());
>
> Modified: felix/trunk/utils/pom.xml
> URL: http://svn.apache.org/viewvc/felix/trunk/utils/pom.xml?rev=1829639&r1=1829638&r2=1829639&view=diff
> ==============================================================================
> --- felix/trunk/utils/pom.xml (original)
> +++ felix/trunk/utils/pom.xml Fri Apr 20 09:23:35 2018
> @@ -36,17 +36,21 @@
>          <url>http://svn.apache.org/repos/asf/felix/utils</url>
>      </scm>
>
> +    <properties>
> +        <felix.java.version>7</felix.java.version>
> +    </properties>
> +
>      <dependencies>
>          <dependency>
>              <groupId>org.osgi</groupId>
> -            <artifactId>org.osgi.core</artifactId>
> +            <artifactId>osgi.core</artifactId>
>              <version>5.0.0</version>
>              <scope>provided</scope>
>          </dependency>
>          <dependency>
>              <groupId>org.osgi</groupId>
> -            <artifactId>org.osgi.compendium</artifactId>
> -            <version>4.2.0</version>
> +            <artifactId>osgi.cmpn</artifactId>
> +            <version>5.0.0</version>
>              <scope>provided</scope>
>          </dependency>
>      </dependencies>
> @@ -62,6 +66,13 @@
>                      </excludes>
>                  </configuration>
>              </plugin>
> +            <plugin>
> +                <artifactId>maven-compiler-plugin</artifactId>
> +                <configuration>
> +                    <source>1.7</source>
> +                    <target>1.7</target>
> +                </configuration>
> +            </plugin>
>          </plugins>
>      </build>
>  </project>
>
> Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/StringArrayMap.java
> URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/StringArrayMap.java?rev=1829639&view=auto
> ==============================================================================
> --- felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/StringArrayMap.java (added)
> +++ felix/trunk/utils/src/main/java/org/apache/felix/utils/collections/StringArrayMap.java Fri Apr 20 09:23:35 2018
> @@ -0,0 +1,339 @@
> +/*
> + * 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.AbstractCollection;
> +import java.util.AbstractSet;
> +import java.util.Arrays;
> +import java.util.Collection;
> +import java.util.Collections;
> +import java.util.Iterator;
> +import java.util.Map;
> +import java.util.NoSuchElementException;
> +import java.util.Objects;
> +import java.util.Set;
> +
> +public class StringArrayMap<V> implements Map<String, V> {
> +
> +    protected Object[] table;
> +    protected int size;
> +
> +    public static <T> Map<String, T> reduceMemory(Map<String, T> map) {
> +        if (map == null) {
> +            return Collections.emptyMap();
> +        }
> +        switch (map.size()) {
> +            case 0:
> +                return Collections.emptyMap();
> +            case 1:
> +                Entry<String, T> e = map.entrySet().iterator().next();
> +                return Collections.singletonMap(e.getKey().intern(), e.getValue());
> +            default:
> +                if (map instanceof StringArrayMap) {
> +                    @SuppressWarnings("unchecked")
> +                    StringArrayMap<T> m = (StringArrayMap) map;
> +                    if (m.size == m.table.length / 2) {
> +                        return map;
> +                    }
> +                }
> +                return new StringArrayMap<>(map);
> +        }
> +    }
> +
> +    public StringArrayMap(Map<String, ? extends V> map) {
> +        if (map instanceof StringArrayMap) {
> +            size = ((StringArrayMap) map).size;
> +            table = Arrays.copyOf(((StringArrayMap) map).table, size * 2);
> +        } else {
> +            size = 0;
> +            table = new Object[map.size() * 2];
> +            for (Entry<String, ? extends V> e : map.entrySet()) {
> +                int i = size++ << 1;
> +                table[i++] = e.getKey().intern();
> +                table[i] = e.getValue();
> +            }
> +        }
> +    }
> +
> +    public StringArrayMap() {
> +        this(32);
> +    }
> +
> +    public StringArrayMap(int capacity) {
> +        table = new Object[capacity * 2];
> +        size = 0;
> +    }
> +
> +    @SuppressWarnings("unchecked")
> +    public V get(Object key) {
> +        String k = ((String) key).intern();
> +        for (int i = 0, l = size << 1; i < l; i += 2) {
> +            if (k == table[i]) {
> +                return (V) table[i + 1];
> +            }
> +        }
> +        return null;
> +    }
> +
> +    @SuppressWarnings("unchecked")
> +    public V put(String key, V value) {
> +        return (V) doPut(key, value);
> +    }
> +
> +    protected Object doPut(String key, Object value) {
> +        key = key.intern();
> +        for (int i = 0, l = size << 1; i < l; i += 2) {
> +            if (key == table[i]) {
> +                Object old = table[i + 1];
> +                table[i + 1] = value;
> +                return old;
> +            }
> +        }
> +        if (table.length == 0) {
> +            table = new Object[2];
> +        } else if (size * 2 == table.length) {
> +            Object[] n = new Object[table.length * 2];
> +            System.arraycopy(table, 0, n, 0, table.length);
> +            table = n;
> +        }
> +        int i = size++ << 1;
> +        table[i++] = key;
> +        table[i] = value;
> +        return null;
> +    }
> +
> +    public Set<String> keySet() {
> +        return new AbstractSet<String>() {
> +            @Override
> +            public Iterator<String> iterator() {
> +                return new Iterator<String>() {
> +                    int index = 0;
> +
> +                    @Override
> +                    public boolean hasNext() {
> +                        return index < size;
> +                    }
> +
> +                    @Override
> +                    public String next() {
> +                        if (index >= size) {
> +                            throw new NoSuchElementException();
> +                        }
> +                        return (String) table[(index++ << 1)];
> +                    }
> +
> +                    public void remove() {
> +                        throw new UnsupportedOperationException("remove");
> +                    }
> +                };
> +            }
> +
> +            @Override
> +            public int size() {
> +                return size;
> +            }
> +        };
> +    }
> +
> +    public Collection<V> values() {
> +        return new AbstractCollection<V>() {
> +            @Override
> +            public Iterator<V> iterator() {
> +                return new Iterator<V>() {
> +                    int index = 0;
> +
> +                    public boolean hasNext() {
> +                        return index < size;
> +                    }
> +
> +                    @SuppressWarnings("unchecked")
> +                    public V next() {
> +                        if (index >= size) {
> +                            throw new NoSuchElementException();
> +                        }
> +                        return (V) table[(index++ << 1) + 1];
> +                    }
> +
> +                    public void remove() {
> +                        throw new UnsupportedOperationException("remove");
> +                    }
> +                };
> +            }
> +
> +            @Override
> +            public int size() {
> +                return size;
> +            }
> +        };
> +    }
> +
> +    public Set<Entry<String, V>> entrySet() {
> +        return new AbstractSet<Entry<String, V>>() {
> +            @Override
> +            public Iterator<Entry<String, V>> iterator() {
> +                return new Iterator<Entry<String, V>>() {
> +                    int index = 0;
> +
> +                    public boolean hasNext() {
> +                        return index < size;
> +                    }
> +
> +                    @SuppressWarnings("unchecked")
> +                    public Entry<String, V> next() {
> +                        if (index >= size) {
> +                            throw new NoSuchElementException();
> +                        }
> +                        final int i = index << 1;
> +                        index++;
> +                        return new Entry<String, V>() {
> +
> +                            public String getKey() {
> +                                return (String) table[i];
> +                            }
> +
> +                            public V getValue() {
> +                                return (V) table[i + 1];
> +                            }
> +
> +                            public V setValue(V value) {
> +                                throw new UnsupportedOperationException();
> +                            }
> +                        };
> +                    }
> +
> +                    public void remove() {
> +                        throw new UnsupportedOperationException("remove");
> +                    }
> +                };
> +            }
> +
> +            @Override
> +            public int size() {
> +                return size;
> +            }
> +        };
> +    }
> +
> +    public int size() {
> +        return size;
> +    }
> +
> +    public boolean isEmpty() {
> +        return size == 0;
> +    }
> +
> +    public boolean containsKey(Object key) {
> +        String k = ((String) key).intern();
> +        for (int i = 0, l = size * 2; i < l; i += 2) {
> +            if (table[i] == k) {
> +                return true;
> +            }
> +        }
> +        return false;
> +    }
> +
> +    public boolean containsValue(Object value) {
> +        for (int i = 0, l = size * 2; i < l; i += 2) {
> +            if (Objects.equals(table[i + 1], value)) {
> +                return true;
> +            }
> +        }
> +        return false;
> +    }
> +
> +    @SuppressWarnings("unchecked")
> +    public V remove(Object key) {
> +        String k = ((String) key).intern();
> +        for (int i = 0, l = size * 2; i < l; i += 2) {
> +            if (table[i] == k) {
> +                Object v = table[i + 1];
> +                if (i < l - 2) {
> +                    System.arraycopy(table, i + 2, table, i, l - 2 - i);
> +                }
> +                table[l - 1] = null;
> +                table[l - 2] = null;
> +                size--;
> +                return (V) v;
> +            }
> +        }
> +        return null;
> +    }
> +
> +    public void putAll(Map<? extends String, ? extends V> m) {
> +        for (Entry<? extends String, ? extends V> e : m.entrySet()) {
> +            put(e.getKey(), e.getValue());
> +        }
> +    }
> +
> +    public void clear() {
> +        size = 0;
> +        Arrays.fill(table, null);
> +    }
> +
> +    public int hashCode() {
> +        int result = 1;
> +        for (int i = 0; i < size * 2; i++)
> +            result = 31 * result + (table[i] == null ? 0 : table[i].hashCode());
> +        return result;
> +    }
> +
> +    public boolean equals(Object o) {
> +        if (o == this)
> +            return true;
> +        if (!(o instanceof Map))
> +            return false;
> +        Map<?,?> m = (Map<?,?>) o;
> +        if (m.size() != size())
> +            return false;
> +        try {
> +            for (int i = 0, l = size * 2; i < l; i += 2) {
> +                Object key = table[i];
> +                Object value = table[i+1];
> +                if (value == null) {
> +                    if (!(m.get(key)==null && m.containsKey(key)))
> +                        return false;
> +                } else {
> +                    if (!value.equals(m.get(key)))
> +                        return false;
> +                }
> +            }
> +        } catch (ClassCastException | NullPointerException unused) {
> +            return false;
> +        }
> +        return true;
> +    }
> +
> +    public String toString() {
> +        if (size == 0)
> +            return "{}";
> +
> +        StringBuilder sb = new StringBuilder();
> +        sb.append('{');
> +        for (int i = 0, l = size * 2; i < l; i += 2) {
> +            if (i > 0) {
> +                sb.append(',').append(' ');
> +            }
> +            sb.append(table[i]);
> +            sb.append('=');
> +            sb.append(table[i+1] == this ? "(this Map)" : table[i+1]);
> +        }
> +        return sb.append('}').toString();
> +    }
> +
> +}
>
> Modified: felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/AbstractCapabilityRequirement.java
> URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/AbstractCapabilityRequirement.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> ==============================================================================
> --- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/AbstractCapabilityRequirement.java (original)
> +++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/AbstractCapabilityRequirement.java Fri Apr 20 09:23:35 2018
> @@ -16,38 +16,32 @@
>   */
>  package org.apache.felix.utils.resource;
>
> +import org.apache.felix.utils.collections.StringArrayMap;
> +import org.osgi.framework.Version;
>  import org.osgi.resource.Resource;
>
> -import java.util.Collections;
> -import java.util.HashMap;
>  import java.util.Map;
> +import java.util.Objects;
>
>  abstract class AbstractCapabilityRequirement {
>
> -    /** The namespace. Required. */
> -    private final String namespace;
> +    /** The resource. Required. */
> +    protected final Resource resource;
>
> -    /** Optional resource. */
> -    private final Resource resource;
> +    /** The namespace. Required. */
> +    protected final String namespace;
>
>      /** Optional attributes. Never null. */
> -    private final Map<String, Object> attributes;
> +    protected final Map<String, String> directives;
>
>      /** Optional attributes. Never null. */
> -    private final Map<String, String> directives;
> +    protected final Map<String, Object> attributes;
>
> -    AbstractCapabilityRequirement(final String ns, final Map<String, Object> attrs, final Map<String, String> dirs, final Resource res) {
> -        if ( ns == null ) {
> -            throw new IllegalArgumentException("Namespace must not be null.");
> -        }
> -        namespace = ns;
> -        attributes = attrs == null
> -                ? Collections.<String, Object>emptyMap()
> -                : Collections.unmodifiableMap(new HashMap<String, Object>(attrs));
> -        directives = dirs == null
> -                ? Collections.<String,String>emptyMap()
> -                : Collections.unmodifiableMap(new HashMap<String,String>(dirs));
> -                resource = res;
> +    AbstractCapabilityRequirement(final Resource res, final String ns, final Map<String, String> dirs, final Map<String, Object> attrs) {
> +        resource = Objects.requireNonNull(res, "Resource must not be null.");
> +        namespace = Objects.requireNonNull(ns, "Namespace must not be null.");
> +        directives = StringArrayMap.reduceMemory(dirs);
> +        attributes = StringArrayMap.reduceMemory(attrs);
>      }
>
>      /**
> @@ -82,45 +76,98 @@ abstract class AbstractCapabilityRequire
>          return resource;
>      }
>
> -    @Override
> -    public int hashCode() {
> -        final int prime = 31;
> -        int result = 1;
> -        result = prime * result + attributes.hashCode();
> -        result = prime * result + directives.hashCode();
> -        result = prime * result + namespace.hashCode();
> -
> -        if (resource != null)
> -            result = prime * result + resource.hashCode();
>
> -        return result;
> +    @Override
> +    public boolean equals(Object o) {
> +        if (this == o) return true;
> +        if (o == null || getClass() != o.getClass()) return false;
> +        AbstractCapabilityRequirement that = (AbstractCapabilityRequirement) o;
> +        return Objects.equals(resource, that.resource) &&
> +                Objects.equals(namespace, that.namespace) &&
> +                Objects.equals(attributes, that.attributes) &&
> +                Objects.equals(directives, that.directives);
>      }
>
>      @Override
> -    public boolean equals(Object obj) {
> -        if (this == obj)
> -            return true;
> -        if (obj == null)
> -            return false;
> -        if (getClass() != obj.getClass())
> -            return false;
> -        AbstractCapabilityRequirement other = (AbstractCapabilityRequirement) obj;
> -        if (!namespace.equals(other.namespace))
> -            return false;
> -        if (!attributes.equals(other.attributes))
> -            return false;
> -        if (!directives.equals(other.directives))
> -            return false;
> -        if (resource == null) {
> -            return other.resource == null;
> -        } else {
> -            return resource.equals(other.resource);
> -        }
> +    public int hashCode() {
> +        return Objects.hash(resource, namespace, attributes, directives);
>      }
>
>      @Override
>      public String toString() {
> -        return getClass().getSimpleName() + " [resource=" + resource + ", namespace=" + namespace + ", attributes=" + attributes
> -                + ", directives=" + directives + "]";
> +        return toString(getResource(), getNamespace(), getAttributes(), getDirectives());
> +    }
> +
> +    public static String toString(Resource res, String namespace, Map<String, Object> attrs, Map<String, String> dirs) {
> +        StringBuilder sb = new StringBuilder();
> +        if (res != null) {
> +            sb.append("[").append(res).append("] ");
> +        }
> +        sb.append(namespace);
> +        for (String key : attrs.keySet()) {
> +            sb.append("; ");
> +            append(sb, key, attrs.get(key), true);
> +        }
> +        for (String key : dirs.keySet()) {
> +            sb.append("; ");
> +            append(sb, key, dirs.get(key), false);
> +        }
> +        return sb.toString();
> +    }
> +
> +    private static void append(StringBuilder sb, String key, Object val, boolean attribute) {
> +        sb.append(key);
> +        if (val instanceof Version) {
> +            sb.append(":Version=");
> +            sb.append(val);
> +        } else if (val instanceof Long) {
> +            sb.append(":Long=");
> +            sb.append(val);
> +        } else if (val instanceof Double) {
> +            sb.append(":Double=");
> +            sb.append(val);
> +        } else if (val instanceof Iterable) {
> +            Iterable<?> it = (Iterable<?>) val;
> +            String scalar = null;
> +            for (Object o : it) {
> +                String ts;
> +                if (o instanceof String) {
> +                    ts = "String";
> +                } else if (o instanceof Long) {
> +                    ts = "Long";
> +                } else if (o instanceof Double) {
> +                    ts = "Double";
> +                } else if (o instanceof Version) {
> +                    ts = "Version";
> +                } else {
> +                    throw new IllegalArgumentException("Unsupported scalar type: " + o);
> +                }
> +                if (scalar == null) {
> +                    scalar = ts;
> +                } else if (!scalar.equals(ts)) {
> +                    throw new IllegalArgumentException("Unconsistent list type for attribute " + key);
> +                }
> +            }
> +            sb.append(":List<").append(scalar).append(">=");
> +            sb.append("\"");
> +            boolean first = true;
> +            for (Object o : it) {
> +                if (first) {
> +                    first = false;
> +                } else {
> +                    sb.append(",");
> +                }
> +                sb.append(o.toString().replace("\"", "\\\"").replace(",", "\\,"));
> +            }
> +            sb.append("\"");
> +        } else {
> +            sb.append(attribute ? "=" : ":=");
> +            String s = val.toString();
> +            if (s.matches("[0-9a-zA-Z_\\-.]*")) {
> +                sb.append(s);
> +            } else {
> +                sb.append("\"").append(s.replace("\"", "\\\\")).append("\"");
> +            }
> +        }
>      }
>  }
>
> Modified: felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java
> URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> ==============================================================================
> --- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java (original)
> +++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilityImpl.java Fri Apr 20 09:23:35 2018
> @@ -16,35 +16,50 @@
>   */
>  package org.apache.felix.utils.resource;
>
> +import org.osgi.framework.Constants;
>  import org.osgi.resource.Capability;
>  import org.osgi.resource.Resource;
>
> +import java.util.Collections;
> +import java.util.HashSet;
> +import java.util.List;
>  import java.util.Map;
> +import java.util.Set;
>
>  /**
>   * Implementation of the OSGi Capability interface.
>   */
>  public class CapabilityImpl extends AbstractCapabilityRequirement implements Capability {
> -    /**
> -     * Create a capability that is not associated with a resource.
> -     * @param res The resource associated with the capability. May be null.
> -     * @param ns The namespace of the capability.
> -     * @param attrs The attributes of the capability.
> -     * @param dirs The directives of the capability.
> -     */
> -    public CapabilityImpl(String ns, Map<String, Object> attrs, Map<String, String> dirs) {
> -        this(ns, attrs, dirs, null);
> -    }
> +
> +    protected final Set<String> mandatory;
>
>      /**
>       * Create a capability.
> +     * @param res The resource associated with the capability.
>       * @param ns The namespace of the capability.
>       * @param attrs The attributes of the capability.
>       * @param dirs The directives of the capability.
> -     * @param res The resource associated with the capability. May be null.
>       */
> -    public CapabilityImpl(String ns, Map<String, Object> attrs, Map<String, String> dirs, Resource res) {
> -        super(ns, attrs, dirs, res);
> +    public CapabilityImpl(Resource res, String ns, Map<String, String> dirs, Map<String, Object> attrs) {
> +        super(res, ns, dirs, attrs);
> +
> +        // Handle mandatory directive
> +        Set<String> mandatory = Collections.emptySet();
> +        String value = this.directives.get(Constants.MANDATORY_DIRECTIVE);
> +        if (value != null) {
> +            List<String> names = ResourceBuilder.parseDelimitedString(value, ",");
> +            mandatory = new HashSet<>(names.size());
> +            for (String name : names) {
> +                // If attribute exists, then record it as mandatory.
> +                if (this.attributes.containsKey(name)) {
> +                    mandatory.add(name);
> +                    // Otherwise, report an error.
> +                } else {
> +                    throw new IllegalArgumentException("Mandatory attribute '" + name + "' does not exist.");
> +                }
> +            }
> +        }
> +        this.mandatory = mandatory;
>      }
>
>      /**
> @@ -54,6 +69,10 @@ public class CapabilityImpl extends Abst
>       * @param resource The resource to be associated with the capability
>       */
>      public CapabilityImpl(Resource resource, Capability capability) {
> -        this(capability.getNamespace(), capability.getAttributes(), capability.getDirectives(), resource);
> +        this(resource, capability.getNamespace(), capability.getDirectives(), capability.getAttributes());
> +    }
> +
> +    public boolean isAttributeMandatory(String name) {
> +        return !mandatory.isEmpty() && mandatory.contains(name);
>      }
>  }
>
> Added: felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java
> URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java?rev=1829639&view=auto
> ==============================================================================
> --- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java (added)
> +++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/CapabilitySet.java Fri Apr 20 09:23:35 2018
> @@ -0,0 +1,469 @@
> +/*
> + * 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.resource;
> +
> +import java.lang.reflect.Array;
> +import java.lang.reflect.Constructor;
> +import java.util.ArrayList;
> +import java.util.Collection;
> +import java.util.HashMap;
> +import java.util.HashSet;
> +import java.util.Iterator;
> +import java.util.List;
> +import java.util.Map;
> +import java.util.Map.Entry;
> +import java.util.Set;
> +import java.util.TreeMap;
> +
> +import org.apache.felix.utils.version.VersionTable;
> +import org.osgi.framework.Constants;
> +import org.osgi.framework.Version;
> +import org.osgi.resource.Capability;
> +
> +@SuppressWarnings("rawtypes")
> +public class CapabilitySet {
> +
> +    private static final Class<?>[] STRING_CLASS = new Class[] {String.class};
> +
> +    private final Map<String, Map<Object, Set<Capability>>> indices;
> +    private final Set<Capability> capSet = new HashSet<>();
> +
> +    public CapabilitySet(List<String> indexProps) {
> +        indices = new TreeMap<>();
> +        for (int i = 0; (indexProps != null) && (i < indexProps.size()); i++) {
> +            indices.put(indexProps.get(i), new HashMap<Object, Set<Capability>>());
> +        }
> +    }
> +
> +    public void dump() {
> +        for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) {
> +            boolean header1 = false;
> +            for (Entry<Object, Set<Capability>> entry2 : entry.getValue().entrySet()) {
> +                boolean header2 = false;
> +                for (Capability cap : entry2.getValue()) {
> +                    if (!header1) {
> +                        System.out.println(entry.getKey() + ":");
> +                        header1 = true;
> +                    }
> +                    if (!header2) {
> +                        System.out.println("   " + entry2.getKey());
> +                        header2 = true;
> +                    }
> +                    System.out.println("      " + cap);
> +                }
> +            }
> +        }
> +    }
> +
> +    public void addCapability(Capability cap) {
> +        capSet.add(cap);
> +
> +        // Index capability.
> +        for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) {
> +            Object value = cap.getAttributes().get(entry.getKey());
> +            if (value != null) {
> +                if (value.getClass().isArray()) {
> +                    value = convertArrayToList(value);
> +                }
> +
> +                Map<Object, Set<Capability>> index = entry.getValue();
> +
> +                if (value instanceof Collection) {
> +                    Collection c = (Collection) value;
> +                    for (Object o : c) {
> +                        indexCapability(index, cap, o);
> +                    }
> +                } else {
> +                    indexCapability(index, cap, value);
> +                }
> +            }
> +        }
> +    }
> +
> +    private void indexCapability(Map<Object, Set<Capability>> index, Capability cap, Object capValue) {
> +        // TODO: when JDK8, should be:
> +        // TODO: index.computeIfAbsent(capValue, k -> new HashSet<>()).add(cap);
> +        Set<Capability> set = index.get(capValue);
> +        if (set == null) {
> +            set = new HashSet<>();
> +            index.put(capValue, set);
> +        }
> +        set.add(cap);
> +    }
> +
> +    public void removeCapability(Capability cap) {
> +        if (capSet.remove(cap)) {
> +            for (Entry<String, Map<Object, Set<Capability>>> entry : indices.entrySet()) {
> +                Object value = cap.getAttributes().get(entry.getKey());
> +                if (value != null) {
> +                    if (value.getClass().isArray()) {
> +                        value = convertArrayToList(value);
> +                    }
> +
> +                    Map<Object, Set<Capability>> index = entry.getValue();
> +
> +                    if (value instanceof Collection) {
> +                        Collection c = (Collection) value;
> +                        for (Object o : c) {
> +                            deindexCapability(index, cap, o);
> +                        }
> +                    } else {
> +                        deindexCapability(index, cap, value);
> +                    }
> +                }
> +            }
> +        }
> +    }
> +
> +    private void deindexCapability(
> +            Map<Object, Set<Capability>> index, Capability cap, Object value) {
> +        Set<Capability> caps = index.get(value);
> +        if (caps != null) {
> +            caps.remove(cap);
> +            if (caps.isEmpty()) {
> +                index.remove(value);
> +            }
> +        }
> +    }
> +
> +    public Set<Capability> match(SimpleFilter sf, boolean obeyMandatory) {
> +        Set<Capability> matches = match(capSet, sf);
> +        return obeyMandatory
> +                ? matchMandatory(matches, sf)
> +                : matches;
> +    }
> +
> +    @SuppressWarnings("unchecked")
> +    private Set<Capability> match(Set<Capability> caps, SimpleFilter sf) {
> +        Set<Capability> matches = new HashSet<>();
> +
> +        if (sf.getOperation() == SimpleFilter.MATCH_ALL) {
> +            matches.addAll(caps);
> +        } else if (sf.getOperation() == SimpleFilter.AND) {
> +            // Evaluate each subfilter against the remaining capabilities.
> +            // For AND we calculate the intersection of each subfilter.
> +            // We can short-circuit the AND operation if there are no
> +            // remaining capabilities.
> +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> +            for (int i = 0; (caps.size() > 0) && (i < sfs.size()); i++) {
> +                matches = match(caps, sfs.get(i));
> +                caps = matches;
> +            }
> +        } else if (sf.getOperation() == SimpleFilter.OR) {
> +            // Evaluate each subfilter against the remaining capabilities.
> +            // For OR we calculate the union of each subfilter.
> +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> +            for (SimpleFilter sf1 : sfs) {
> +                matches.addAll(match(caps, sf1));
> +            }
> +        } else if (sf.getOperation() == SimpleFilter.NOT) {
> +            // Evaluate each subfilter against the remaining capabilities.
> +            // For OR we calculate the union of each subfilter.
> +            matches.addAll(caps);
> +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> +            for (SimpleFilter sf1 : sfs) {
> +                matches.removeAll(match(caps, sf1));
> +            }
> +        } else {
> +            Map<Object, Set<Capability>> index = indices.get(sf.getName());
> +            if ((sf.getOperation() == SimpleFilter.EQ) && (index != null)) {
> +                Set<Capability> existingCaps = index.get(sf.getValue());
> +                if (existingCaps != null) {
> +                    matches.addAll(existingCaps);
> +                    matches.retainAll(caps);
> +                }
> +            } else {
> +                for (Capability cap : caps) {
> +                    Object lhs = cap.getAttributes().get(sf.getName());
> +                    if (lhs != null) {
> +                        if (compare(lhs, sf.getValue(), sf.getOperation())) {
> +                            matches.add(cap);
> +                        }
> +                    }
> +                }
> +            }
> +        }
> +
> +        return matches;
> +    }
> +
> +    public static boolean matches(Capability cap, SimpleFilter sf) {
> +        return matchesInternal(cap, sf) && matchMandatory(cap, sf);
> +    }
> +
> +    @SuppressWarnings("unchecked")
> +    private static boolean matchesInternal(Capability cap, SimpleFilter sf) {
> +        boolean matched = true;
> +
> +        if (sf.getOperation() == SimpleFilter.MATCH_ALL) {
> +            matched = true;
> +        } else if (sf.getOperation() == SimpleFilter.AND) {
> +            // Evaluate each subfilter against the remaining capabilities.
> +            // For AND we calculate the intersection of each subfilter.
> +            // We can short-circuit the AND operation if there are no
> +            // remaining capabilities.
> +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> +            for (int i = 0; matched && (i < sfs.size()); i++) {
> +                matched = matchesInternal(cap, sfs.get(i));
> +            }
> +        } else if (sf.getOperation() == SimpleFilter.OR) {
> +            // Evaluate each subfilter against the remaining capabilities.
> +            // For OR we calculate the union of each subfilter.
> +            matched = false;
> +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> +            for (int i = 0; !matched && (i < sfs.size()); i++) {
> +                matched = matchesInternal(cap, sfs.get(i));
> +            }
> +        } else if (sf.getOperation() == SimpleFilter.NOT) {
> +            // Evaluate each subfilter against the remaining capabilities.
> +            // For OR we calculate the union of each subfilter.
> +            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
> +            for (SimpleFilter sf1 : sfs) {
> +                matched = !(matchesInternal(cap, sf1));
> +            }
> +        } else {
> +            matched = false;
> +            Object lhs = cap.getAttributes().get(sf.getName());
> +            if (lhs != null) {
> +                matched = compare(lhs, sf.getValue(), sf.getOperation());
> +            }
> +        }
> +
> +        return matched;
> +    }
> +
> +    private static Set<Capability> matchMandatory(
> +            Set<Capability> caps, SimpleFilter sf) {
> +        for (Iterator<Capability> it = caps.iterator(); it.hasNext();) {
> +            Capability cap = it.next();
> +            if (!matchMandatory(cap, sf)) {
> +                it.remove();
> +            }
> +        }
> +        return caps;
> +    }
> +
> +    private static boolean matchMandatory(Capability cap, SimpleFilter sf) {
> +        if (cap instanceof CapabilityImpl) {
> +            for (Entry<String, Object> entry : cap.getAttributes().entrySet()) {
> +                if (((CapabilityImpl) cap).isAttributeMandatory(entry.getKey())
> +                        && !matchMandatoryAttribute(entry.getKey(), sf)) {
> +                    return false;
> +                }
> +            }
> +        } else {
> +            String value = cap.getDirectives().get(Constants.MANDATORY_DIRECTIVE);
> +            if (value != null) {
> +                List<String> names = ResourceBuilder.parseDelimitedString(value, ",");
> +                for (Entry<String, Object> entry : cap.getAttributes().entrySet()) {
> +                    if (names.contains(entry.getKey())
> +                            && !matchMandatoryAttribute(entry.getKey(), sf)) {
> +                        return false;
> +                    }
> +                }
> +            }
> +
> +        }
> +        return true;
> +    }
> +
> +    private static boolean matchMandatoryAttribute(String attrName, SimpleFilter sf) {
> +        if ((sf.getName() != null) && sf.getName().equals(attrName)) {
> +            return true;
> +        } else if (sf.getOperation() == SimpleFilter.AND) {
> +            List list = (List) sf.getValue();
> +            for (Object aList : list) {
> +                SimpleFilter sf2 = (SimpleFilter) aList;
> +                if ((sf2.getName() != null)
> +                        && sf2.getName().equals(attrName)) {
> +                    return true;
> +                }
> +            }
> +        }
> +        return false;
> +    }
> +
> +    @SuppressWarnings("unchecked")
> +    private static boolean compare(Object lhs, Object rhsUnknown, int op) {
> +        if (lhs == null) {
> +            return false;
> +        }
> +
> +        // If this is a PRESENT operation, then just return true immediately
> +        // since we wouldn't be here if the attribute wasn't present.
> +        if (op == SimpleFilter.PRESENT) {
> +            return true;
> +        }
> +
> +        // If the type is comparable, then we can just return the
> +        // result immediately.
> +        if (lhs instanceof Comparable) {
> +            // Spec says SUBSTRING is false for all types other than string.
> +            if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String)) {
> +                return false;
> +            }
> +
> +            Object rhs;
> +            if (op == SimpleFilter.SUBSTRING) {
> +                rhs = rhsUnknown;
> +            } else {
> +                try {
> +                    rhs = coerceType(lhs, (String) rhsUnknown);
> +                } catch (Exception ex) {
> +                    return false;
> +                }
> +            }
> +
> +            switch (op) {
> +            case SimpleFilter.EQ:
> +                try {
> +                    return ((Comparable) lhs).compareTo(rhs) == 0;
> +                } catch (Exception ex) {
> +                    return false;
> +                }
> +            case SimpleFilter.GTE:
> +                try {
> +                    return ((Comparable) lhs).compareTo(rhs) >= 0;
> +                } catch (Exception ex) {
> +                    return false;
> +                }
> +            case SimpleFilter.LTE:
> +                try {
> +                    return ((Comparable) lhs).compareTo(rhs) <= 0;
> +                } catch (Exception ex) {
> +                    return false;
> +                }
> +            case SimpleFilter.APPROX:
> +                return compareApproximate(lhs, rhs);
> +            case SimpleFilter.SUBSTRING:
> +                return SimpleFilter.compareSubstring((List<String>) rhs, (String) lhs);
> +            default:
> +                throw new RuntimeException("Unknown comparison operator: " + op);
> +            }
> +        }
> +
> +        // If the LHS is not a comparable or boolean, check if it is an
> +        // array. If so, convert it to a list so we can treat it as a
> +        // collection.
> +        if (lhs.getClass().isArray()) {
> +            lhs = convertArrayToList(lhs);
> +        }
> +
> +        // If LHS is a collection, then call compare() on each element
> +        // of the collection until a match is found.
> +        if (lhs instanceof Collection) {
> +            for (Object o : (Collection) lhs) {
> +                if (compare(o, rhsUnknown, op)) {
> +                    return true;
> +                }
> +            }
> +
> +            return false;
> +        }
> +
> +        // Spec says SUBSTRING is false for all types other than string.
> +        if (op == SimpleFilter.SUBSTRING) {
> +            return false;
> +        }
> +
> +        // Since we cannot identify the LHS type, then we can only perform
> +        // equality comparison.
> +        try {
> +            return lhs.equals(coerceType(lhs, (String) rhsUnknown));
> +        } catch (Exception ex) {
> +            return false;
> +        }
> +    }
> +
> +    private static boolean compareApproximate(Object lhs, Object rhs) {
> +        if (rhs instanceof String) {
> +            return removeWhitespace((String) lhs)
> +                    .equalsIgnoreCase(removeWhitespace((String) rhs));
> +        } else if (rhs instanceof Character) {
> +            return Character.toLowerCase((Character) lhs)
> +                    == Character.toLowerCase((Character) rhs);
> +        }
> +        return lhs.equals(rhs);
> +    }
> +
> +    private static String removeWhitespace(String s) {
> +        StringBuilder sb = new StringBuilder(s.length());
> +        for (int i = 0; i < s.length(); i++) {
> +            if (!Character.isWhitespace(s.charAt(i))) {
> +                sb.append(s.charAt(i));
> +            }
> +        }
> +        return sb.toString();
> +    }
> +
> +    private static Object coerceType(Object lhs, String rhsString) throws Exception {
> +        // If the LHS expects a string, then we can just return
> +        // the RHS since it is a string.
> +        if (lhs.getClass() == rhsString.getClass()) {
> +            return rhsString;
> +        }
> +
> +        // Try to convert the RHS type to the LHS type by using
> +        // the string constructor of the LHS class, if it has one.
> +        Object rhs;
> +        try {
> +            if (lhs instanceof Version) {
> +                rhs = VersionTable.getVersion(rhsString, false);
> +            } else
> +            // The Character class is a special case, since its constructor
> +            // does not take a string, so handle it separately.
> +            if (lhs instanceof Character) {
> +                rhs = rhsString.charAt(0);
> +            } else {
> +                // Spec says we should trim number types.
> +                if ((lhs instanceof Number) || (lhs instanceof Boolean)) {
> +                    rhsString = rhsString.trim();
> +                }
> +                Constructor ctor = lhs.getClass().getConstructor(STRING_CLASS);
> +                ctor.setAccessible(true);
> +                rhs = ctor.newInstance(rhsString);
> +            }
> +        } catch (Exception ex) {
> +            throw new Exception(
> +                    "Could not instantiate class "
> +                            + lhs.getClass().getName()
> +                            + " from string constructor with argument '"
> +                            + rhsString + "' because " + ex
> +            );
> +        }
> +
> +        return rhs;
> +    }
> +
> +    /**
> +     * This is an ugly utility method to convert an array of primitives
> +     * to an array of primitive wrapper objects. This method simplifies
> +     * processing LDAP filters since the special case of primitive arrays
> +     * can be ignored.
> +     *
> +     * @param array An array of primitive types.
> +     * @return An corresponding array using pritive wrapper objects.
> +     */
> +    private static List<Object> convertArrayToList(Object array) {
> +        int len = Array.getLength(array);
> +        List<Object> list = new ArrayList<>(len);
> +        for (int i = 0; i < len; i++) {
> +            list.add(Array.get(array, i));
> +        }
> +        return list;
> +    }
> +}
>
> Modified: felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java
> URL: http://svn.apache.org/viewvc/felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java?rev=1829639&r1=1829638&r2=1829639&view=diff
> ==============================================================================
> --- felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java (original)
> +++ felix/trunk/utils/src/main/java/org/apache/felix/utils/resource/RequirementImpl.java Fri Apr 20 09:23:35 2018
> @@ -16,6 +16,8 @@
>   */
>  package org.apache.felix.utils.resource;
>
> +import org.osgi.framework.Constants;
> +import org.osgi.resource.Capability;
>  import org.osgi.resource.Namespace;
>  import org.osgi.resource.Requirement;
>  import org.osgi.resource.Resource;
> @@ -27,26 +29,19 @@ import java.util.Map;
>   * Implementation of the OSGi Requirement interface.
>   */
>  public class RequirementImpl extends AbstractCapabilityRequirement implements Requirement {
> -    /**
> -     * Create a requirement that is not associated with a resource.
> -     * @param res The resource associated with the requirement.
> -     * @param ns The namespace of the requirement.
> -     * @param attrs The attributes of the requirement.
> -     * @param dirs The directives of the requirement.
> -     */
> -    public RequirementImpl(String ns, Map<String, Object> attrs, Map<String, String> dirs) {
> -        this(ns, attrs, dirs, null);
> -    }
> +
> +    private final SimpleFilter filter;
> +    private final boolean optional;
>
>      /**
>       * Create a requirement.
> +     * @param res The resource associated with the requirement.
>       * @param ns The namespace of the requirement.
>       * @param attrs The attributes of the requirement.
>       * @param dirs The directives of the requirement.
> -     * @param res The resource associated with the requirement.
>       */
> -    public RequirementImpl(String ns, Map<String, Object> attrs, Map<String, String> dirs, Resource res) {
> -        super(ns, attrs, dirs, res);
> +    public RequirementImpl(Resource res, String ns, Map<String, String> dirs, Map<String, Object> attrs) {
> +        this(res, ns, dirs, attrs, null);
>      }
>
>      /**
> @@ -54,14 +49,16 @@ public class RequirementImpl extends Abs
>        *
>        * This is a convenience method that creates a requirement with
>        * an empty attributes map and a single 'filter' directive.
> +     * @param res The resource associated with the requirement.
>        * @param ns The namespace for the requirement.
>        * @param filter The filter.
>        */
> -     public RequirementImpl(String ns, String filter)
> +     public RequirementImpl(Resource res, String ns, String filter)
>       {
> -         this(ns, Collections.<String, Object>emptyMap(),
> -             filter == null ? Collections.<String, String> emptyMap() :
> -             Collections.singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter));
> +         this(res, ns,
> +             filter == null ? Collections.<String, String>emptyMap() :
> +             Collections.singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter),
> +             null);
>       }
>
>      /**
> @@ -71,6 +68,26 @@ public class RequirementImpl extends Abs
>       * @param resource The resource to be associated with the requirement
>       */
>      public RequirementImpl(Resource resource, Requirement requirement) {
> -        this(requirement.getNamespace(), requirement.getAttributes(), requirement.getDirectives(), resource);
> +        this(resource, requirement.getNamespace(), requirement.getDirectives(), requirement.getAttributes());
> +    }
> +
> +    public RequirementImpl(Resource resource, String path, Map<String, String> dirs, Map<String, Object> attrs, SimpleFilter sf) {
> +        super(resource, path, dirs, attrs);
> +        this.filter = sf != null ? sf : SimpleFilter.convert(attributes);
> +        // Find resolution import directives.
> +        this.optional = Constants.RESOLUTION_OPTIONAL.equals(directives.get(Constants.RESOLUTION_DIRECTIVE));
> +    }
> +
> +    public boolean matches(Capability cap) {
> +        return CapabilitySet.matches(cap, getFilter());
>      }
> +
> +    public boolean isOptional() {
> +        return optional;
> +    }
> +
> +    public SimpleFilter getFilter() {
> +        return filter;
> +    }
> +
>  }
>
>



-- 
Karl Pauls
karlpauls@gmail.com