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 <[email protected]> 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, <[email protected]> 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 > [email protected] >
