felix-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gno...@apache.org
Subject svn commit: r1667217 [1/2] - in /felix/trunk/resolver: ./ src/test/java/org/apache/felix/resolver/test/ src/test/resources/
Date Tue, 17 Mar 2015 09:38:46 GMT
Author: gnodet
Date: Tue Mar 17 09:38:45 2015
New Revision: 1667217

URL: http://svn.apache.org/r1667217
Log:
[FELIX-4656] Add a big resolution test (disabled by default)

Added:
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BigResolutionTest.java
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/CandidateComparator.java
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/CapabilitySet.java
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/ClauseParser.java
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/IterativeResolver.java
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/JsonReader.java
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/SimpleFilter.java
    felix/trunk/resolver/src/test/resources/
    felix/trunk/resolver/src/test/resources/resolution.json
Modified:
    felix/trunk/resolver/pom.xml
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BundleCapability.java
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BundleRequirement.java
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/GenericCapability.java
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/GenericRequirement.java
    felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/ResourceImpl.java

Modified: felix/trunk/resolver/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/pom.xml?rev=1667217&r1=1667216&r2=1667217&view=diff
==============================================================================
--- felix/trunk/resolver/pom.xml (original)
+++ felix/trunk/resolver/pom.xml Tue Mar 17 09:38:45 2015
@@ -48,6 +48,12 @@
       <version>4.11</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.utils</artifactId>
+      <version>1.8.0</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
@@ -99,6 +105,7 @@
           <excludes>
             <exclude>src/**/packageinfo</exclude>
             <exclude>src/main/appended-resources/**</exclude>
+			<exclude>src/test/resources/resolution.json</exclude>
           </excludes>
         </configuration>
       </plugin>

Added: felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BigResolutionTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BigResolutionTest.java?rev=1667217&view=auto
==============================================================================
--- felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BigResolutionTest.java (added)
+++ felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BigResolutionTest.java Tue Mar 17 09:38:45 2015
@@ -0,0 +1,360 @@
+/*
+ * 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.resolver.test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.resolver.Logger;
+import org.apache.felix.resolver.ResolverImpl;
+import org.apache.felix.utils.version.VersionRange;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Version;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wire;
+import org.osgi.resource.Wiring;
+import org.osgi.service.resolver.HostedCapability;
+import org.osgi.service.resolver.ResolveContext;
+
+public class BigResolutionTest {
+
+    @Test
+    @Ignore
+    public void testResolutionSpeed() throws Exception {
+        ResolveContext rc = buildResolutionContext();
+
+        ResolverImpl resolver = new ResolverImpl(new Logger(Logger.LOG_INFO));
+        for (int i = 0; i < 10; i++) {
+            System.gc();
+            long t0 = System.currentTimeMillis();
+            resolver.resolve(rc);
+            long t1 = System.currentTimeMillis();
+            System.out.println("Resolver took " + (t1 - t0) + " ms");
+        }
+    }
+
+    @Test
+    @Ignore
+    public void testIterativeResolution() throws Exception {
+        ResolveContext rc = buildResolutionContext();
+
+        ResolverImpl resolver = new ResolverImpl(new Logger(Logger.LOG_INFO));
+
+        long t0 = System.currentTimeMillis();
+        Map<Resource, List<Wire>> wiring1 = resolver.resolve(rc);
+        long t1 = System.currentTimeMillis();
+        System.out.println("Resolver took " + (t1 - t0) + " ms");
+
+        long t2 = System.currentTimeMillis();
+        Map<Resource, List<Wire>> wiring2 = new IterativeResolver(resolver).resolve(rc);
+        long t3 = System.currentTimeMillis();
+        System.out.println("Iterative resolver took " + (t3 - t2) + " ms");
+
+        checkResolutions(wiring1, wiring2);
+    }
+
+    private ResolveContext buildResolutionContext() throws IOException, BundleException {
+        Object resolution;
+
+        InputStream is = getClass().getClassLoader().getResourceAsStream("resolution.json");
+        try {
+            resolution = JsonReader.read(is);
+        } finally {
+            is.close();
+        }
+
+        List<Resource> resources = new ArrayList<Resource>();
+        ResourceImpl system = new ResourceImpl("system-bundle");
+        parseCapability(system, "osgi.ee; osgi.ee=JavaSE; version=1.5");
+        parseCapability(system, "osgi.ee; osgi.ee=JavaSE; version=1.6");
+        parseCapability(system, "osgi.ee; osgi.ee=JavaSE; version=1.7");
+        resources.add(system);
+        for (Object r : (Collection) ((Map) resolution).get("resources")) {
+            resources.add(parseResource(r));
+        }
+        final List<Resource> mandatory = new ArrayList<Resource>();
+        for (Object r : (Collection) ((Map) resolution).get("mandatory")) {
+            mandatory.add(parseResource(r));
+        }
+
+        final Map<String, CapabilitySet> capSets = new HashMap<String, CapabilitySet>();
+        CapabilitySet svcSet = new CapabilitySet(Collections.singletonList("objectClass"));
+        capSets.put("osgi.service", svcSet);
+        for (Resource resource : resources) {
+            for (Capability cap : resource.getCapabilities(null)) {
+                String ns = cap.getNamespace();
+                CapabilitySet set = capSets.get(ns);
+                if (set == null) {
+                    set = new CapabilitySet(Collections.singletonList(ns));
+                    capSets.put(ns, set);
+                }
+                set.addCapability(cap);
+            }
+        }
+
+        return new ResolveContext() {
+            @Override
+            public Collection<Resource> getMandatoryResources() {
+                return mandatory;
+            }
+
+            @Override
+            public List<Capability> findProviders(Requirement requirement) {
+                SimpleFilter sf;
+                if (requirement.getDirectives().containsKey("filter")) {
+                    sf = SimpleFilter.parse(requirement.getDirectives().get("filter"));
+                } else {
+                    sf = SimpleFilter.convert(requirement.getAttributes());
+                }
+                CapabilitySet set = capSets.get(requirement.getNamespace());
+                List<Capability> caps = new ArrayList<Capability>(set.match(sf, true));
+                Collections.sort(caps, new CandidateComparator());
+                return caps;
+            }
+
+            @Override
+            public int insertHostedCapability(List<Capability> capabilities, HostedCapability hostedCapability) {
+                capabilities.add(hostedCapability);
+                return capabilities.size() - 1;
+            }
+
+            @Override
+            public boolean isEffective(Requirement requirement) {
+                return true;
+            }
+
+            @Override
+            public Map<Resource, Wiring> getWirings() {
+                return Collections.emptyMap();
+            }
+        };
+    }
+
+    private void checkResolutions(Map<Resource, List<Wire>> wireMap1, Map<Resource, List<Wire>> wireMap2) {
+        Set<Resource> resources;
+        resources = new HashSet<Resource>(wireMap1.keySet());
+        resources.removeAll(wireMap2.keySet());
+        for (Resource res : resources) {
+            System.out.println("Resource resolved in r1 and not in r2: " + res);
+        }
+        resources = new HashSet<Resource>(wireMap2.keySet());
+        resources.removeAll(wireMap1.keySet());
+        for (Resource res : resources) {
+            System.out.println("Resource resolved in r2 and not in r1: " + res);
+        }
+        resources = new HashSet<Resource>(wireMap2.keySet());
+        resources.retainAll(wireMap1.keySet());
+        for (Resource resource : resources) {
+            Set<Wire> wires1 = new HashSet<Wire>(wireMap1.get(resource));
+            Set<Wire> wires2 = new HashSet<Wire>(wireMap2.get(resource));
+            wires1.removeAll(wireMap2.get(resource));
+            wires2.removeAll(wireMap1.get(resource));
+            if (!wires1.isEmpty() || !wires2.isEmpty()) {
+                System.out.println("Different wiring for resource: " + resource);
+            }
+            for (Wire wire : wires1) {
+                System.out.println("\tR1: " + wire);
+            }
+            for (Wire wire : wires2) {
+                System.out.println("\tR2: " + wire);
+            }
+        }
+        if (!wireMap1.equals(wireMap2)) {
+            throw new RuntimeException("Different wiring");
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private Resource parseResource(Object resource) throws BundleException {
+        ResourceImpl res = new ResourceImpl();
+        for (String s : (Collection<String>) ((Map) resource).get("capabilities")) {
+            parseCapability(res, s);
+        }
+        for (String s : (Collection<String>) ((Map) resource).get("requirements")) {
+            parseRequirement(res, s);
+        }
+        return res;
+    }
+
+    private void parseRequirement(ResourceImpl res, String s) throws BundleException {
+        List<ClauseParser.ParsedHeaderClause> clauses = ClauseParser.parseStandardHeader(s);
+        normalizeRequirementClauses(clauses);
+        for (ClauseParser.ParsedHeaderClause clause : clauses) {
+            for (String path : clause.paths) {
+                GenericRequirement requirement = new GenericRequirement(res, path);
+                for (Map.Entry<String, String> dir : clause.dirs.entrySet()) {
+                    requirement.addDirective(dir.getKey(), dir.getValue());
+                }
+                for (Map.Entry<String, Object> attr : clause.attrs.entrySet()) {
+                    requirement.addAttribute(attr.getKey(), attr.getValue());
+                }
+                res.addRequirement(requirement);
+            }
+        }
+    }
+
+    private void parseCapability(ResourceImpl res, String s) throws BundleException {
+        List<ClauseParser.ParsedHeaderClause> clauses = ClauseParser.parseStandardHeader(s);
+        normalizeCapabilityClauses(clauses);
+        for (ClauseParser.ParsedHeaderClause clause : clauses) {
+            for (String path : clause.paths) {
+                GenericCapability capability = new GenericCapability(res, path);
+                for (Map.Entry<String, String> dir : clause.dirs.entrySet()) {
+                    capability.addDirective(dir.getKey(), dir.getValue());
+                }
+                for (Map.Entry<String, Object> attr : clause.attrs.entrySet()) {
+                    capability.addAttribute(attr.getKey(), attr.getValue());
+                }
+                res.addCapability(capability);
+            }
+        }
+    }
+
+    private static void normalizeRequirementClauses(
+            List<ClauseParser.ParsedHeaderClause> clauses)
+            throws BundleException {
+
+        // Convert attributes into specified types.
+        for (ClauseParser.ParsedHeaderClause clause : clauses)
+        {
+            for (Map.Entry<String, Object> entry : clause.attrs.entrySet())
+            {
+                String key = entry.getKey();
+                Object val = entry.getValue();
+                String type = clause.types.get(key);
+                if ("Version".equals(type) || "version".equals(key))
+                {
+                    clause.attrs.put(
+                            key,
+                            VersionRange.parseVersionRange(val.toString().trim()));
+                }
+            }
+        }
+    }
+
+    private static void normalizeCapabilityClauses(
+            List<ClauseParser.ParsedHeaderClause> clauses)
+            throws BundleException
+    {
+        // Convert attributes into specified types.
+        for (ClauseParser.ParsedHeaderClause clause : clauses)
+        {
+            for (Map.Entry<String, String> entry : clause.types.entrySet())
+            {
+                String type = entry.getValue();
+                if (!type.equals("String"))
+                {
+                    if (type.equals("Double"))
+                    {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Double(clause.attrs.get(entry.getKey()).toString().trim()));
+                    }
+                    else if (type.equals("Version"))
+                    {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Version(clause.attrs.get(entry.getKey()).toString().trim()));
+                    }
+                    else if (type.equals("Long"))
+                    {
+                        clause.attrs.put(
+                                entry.getKey(),
+                                new Long(clause.attrs.get(entry.getKey()).toString().trim()));
+                    }
+                    else if (type.startsWith("List"))
+                    {
+                        int startIdx = type.indexOf('<');
+                        int endIdx = type.indexOf('>');
+                        if (((startIdx > 0) && (endIdx <= startIdx))
+                                || ((startIdx < 0) && (endIdx > 0)))
+                        {
+                            throw new BundleException(
+                                    "Invalid Provide-Capability attribute list type for '"
+                                            + entry.getKey()
+                                            + "' : "
+                                            + type);
+                        }
+
+                        String listType = "String";
+                        if (endIdx > startIdx)
+                        {
+                            listType = type.substring(startIdx + 1, endIdx).trim();
+                        }
+
+                        List<String> tokens = ClauseParser.parseDelimitedString(
+                                clause.attrs.get(entry.getKey()).toString(), ",", false);
+                        List<Object> values = new ArrayList<Object>(tokens.size());
+                        for (String token : tokens)
+                        {
+                            if (listType.equals("String"))
+                            {
+                                values.add(token);
+                            }
+                            else if (listType.equals("Double"))
+                            {
+                                values.add(new Double(token.trim()));
+                            }
+                            else if (listType.equals("Version"))
+                            {
+                                values.add(new Version(token.trim()));
+                            }
+                            else if (listType.equals("Long"))
+                            {
+                                values.add(new Long(token.trim()));
+                            }
+                            else
+                            {
+                                throw new BundleException(
+                                        "Unknown Provide-Capability attribute list type for '"
+                                                + entry.getKey()
+                                                + "' : "
+                                                + type);
+                            }
+                        }
+                        clause.attrs.put(
+                                entry.getKey(),
+                                values);
+                    }
+                    else
+                    {
+                        throw new BundleException(
+                                "Unknown Provide-Capability attribute type for '"
+                                        + entry.getKey()
+                                        + "' : "
+                                        + type);
+                    }
+                }
+            }
+        }
+    }
+
+}

Modified: felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BundleCapability.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BundleCapability.java?rev=1667217&r1=1667216&r2=1667217&view=diff
==============================================================================
--- felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BundleCapability.java (original)
+++ felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BundleCapability.java Tue Mar 17 09:38:45 2015
@@ -37,17 +37,17 @@ class BundleCapability implements Capabi
         m_resource = resource;
         m_dirs = new HashMap<String, String>();
         m_attrs = new HashMap<String, Object>();
-        m_attrs.put(BundleNamespace.BUNDLE_NAMESPACE, name);
+        m_attrs.put(BundleNamespace.BUNDLE_NAMESPACE.intern(), name);
     }
 
     public String getNamespace()
     {
-        return BundleNamespace.BUNDLE_NAMESPACE;
+        return BundleNamespace.BUNDLE_NAMESPACE.intern();
     }
 
     public void addDirective(String name, String value)
     {
-        m_dirs.put(name, value);
+        m_dirs.put(name.intern(), value);
     }
 
     public Map<String, String> getDirectives()
@@ -57,7 +57,7 @@ class BundleCapability implements Capabi
 
     public void addAttribute(String name, Object value)
     {
-        m_attrs.put(name, value);
+        m_attrs.put(name.intern(), value);
     }
 
     public Map<String, Object> getAttributes()

Modified: felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BundleRequirement.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BundleRequirement.java?rev=1667217&r1=1667216&r2=1667217&view=diff
==============================================================================
--- felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BundleRequirement.java (original)
+++ felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/BundleRequirement.java Tue Mar 17 09:38:45 2015
@@ -37,14 +37,14 @@ class BundleRequirement implements Requi
         m_resource = resource;
         m_dirs = new HashMap<String, String>();
         m_dirs.put(
-            BundleNamespace.REQUIREMENT_FILTER_DIRECTIVE,
+            BundleNamespace.REQUIREMENT_FILTER_DIRECTIVE.intern(),
             "(" + BundleNamespace.BUNDLE_NAMESPACE + "=" + name + ")");
         m_attrs = new HashMap<String, Object>();
     }
 
     public String getNamespace()
     {
-        return BundleNamespace.BUNDLE_NAMESPACE;
+        return BundleNamespace.BUNDLE_NAMESPACE.intern();
     }
 
     public Map<String, String> getDirectives()

Added: felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/CandidateComparator.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/CandidateComparator.java?rev=1667217&view=auto
==============================================================================
--- felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/CandidateComparator.java (added)
+++ felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/CandidateComparator.java Tue Mar 17 09:38:45 2015
@@ -0,0 +1,144 @@
+/*
+ * 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.resolver.test;
+
+import java.util.Comparator;
+
+import org.apache.felix.resolver.Util;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.BundleNamespace;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.resource.Capability;
+
+public class CandidateComparator implements Comparator<Capability>
+{
+    public int compare(Capability cap1, Capability cap2)
+    {
+        int c = 0;
+        // Always prefer system bundle
+        if (cap1 instanceof BundleCapability && !(cap2 instanceof BundleCapability)) {
+            c = -1;
+        } else if (!(cap1 instanceof BundleCapability) && cap2 instanceof BundleCapability) {
+            c = 1;
+        }
+        // Compare revision capabilities.
+        if ((c == 0) && cap1.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
+        {
+            c = ((Comparable) cap1.getAttributes().get(BundleNamespace.BUNDLE_NAMESPACE))
+                    .compareTo(cap2.getAttributes().get(BundleNamespace.BUNDLE_NAMESPACE));
+            if (c == 0)
+            {
+                Version v1 = (!cap1.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap1.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+                Version v2 = (!cap2.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap2.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+                // Compare these in reverse order, since we want
+                // highest version to have priority.
+                c = compareVersions(v2, v1);
+            }
+        }
+        // Compare package capabilities.
+        else if ((c == 0) && cap1.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+        {
+            c = ((Comparable) cap1.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))
+                    .compareTo(cap2.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE));
+            if (c == 0)
+            {
+                Version v1 = (!cap1.getAttributes().containsKey(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap1.getAttributes().get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+                Version v2 = (!cap2.getAttributes().containsKey(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap2.getAttributes().get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+                // Compare these in reverse order, since we want
+                // highest version to have priority.
+                c = compareVersions(v2, v1);
+                // if same version, rather compare on the bundle version
+                if (c == 0)
+                {
+                    v1 = (!cap1.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE))
+                            ? Version.emptyVersion
+                            : (Version) cap1.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+                    v2 = (!cap2.getAttributes().containsKey(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE))
+                            ? Version.emptyVersion
+                            : (Version) cap2.getAttributes().get(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+                    // Compare these in reverse order, since we want
+                    // highest version to have priority.
+                    c = compareVersions(v2, v1);
+                }
+            }
+        }
+        // Compare feature capabilities
+        else if ((c == 0) && cap1.getNamespace().equals(IdentityNamespace.IDENTITY_NAMESPACE))
+        {
+            c = ((Comparable) cap1.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE))
+                    .compareTo(cap2.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE));
+            if (c == 0)
+            {
+                Version v1 = (!cap1.getAttributes().containsKey(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap1.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+                Version v2 = (!cap2.getAttributes().containsKey(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE))
+                        ? Version.emptyVersion
+                        : (Version) cap2.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+                // Compare these in reverse order, since we want
+                // highest version to have priority.
+                c = compareVersions(v2, v1);
+            }
+        }
+        if (c == 0) {
+            // We just want to have a deterministic heuristic
+            String n1 = cap1.toString();
+            String n2 = cap2.toString();
+            c = n1.compareTo(n2);
+            if (c == 0) {
+                n1 = cap1.getResource().toString();
+                n2 = cap2.getResource().toString();
+                c = n1.compareTo(n2);
+            }
+        }
+        return c;
+    }
+
+    private int compareVersions(Version v1, Version v2) {
+        int c = v1.getMajor() - v2.getMajor();
+        if (c != 0) {
+            return c;
+        }
+        c = v1.getMinor() - v2.getMinor();
+        if (c != 0) {
+            return c;
+        }
+        c = v1.getMicro() - v2.getMicro();
+        if (c != 0) {
+            return c;
+        }
+        String q1 = cleanQualifierForComparison(v1.getQualifier());
+        String q2 = cleanQualifierForComparison(v2.getQualifier());
+        return q1.compareTo(q2);
+    }
+
+    private String cleanQualifierForComparison(String qualifier) {
+        return qualifier.replaceAll("(redhat-[0-9]{3})([0-9]{3})", "$1-$2");
+    }
+}

Added: felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/CapabilitySet.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/CapabilitySet.java?rev=1667217&view=auto
==============================================================================
--- felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/CapabilitySet.java (added)
+++ felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/CapabilitySet.java Tue Mar 17 09:38:45 2015
@@ -0,0 +1,619 @@
+/*
+ * 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.resolver.test;
+
+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.resolver.util.CopyOnWriteSet;
+import org.apache.felix.resolver.util.OpenHashMap;
+import org.osgi.framework.Constants;
+import org.osgi.resource.Capability;
+
+public class CapabilitySet
+{
+    private final Map<String, Map<Object, Set<Capability>>> m_indices;
+    private final Set<Capability> m_capSet = new HashSet<Capability>();
+
+public void dump()
+{
+    for (Entry<String, Map<Object, Set<Capability>>> entry : m_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 CapabilitySet(List<String> indexProps)
+    {
+        m_indices = new TreeMap<String, Map<Object, Set<Capability>>>();
+        for (int i = 0; (indexProps != null) && (i < indexProps.size()); i++)
+        {
+            m_indices.put(
+                indexProps.get(i), new OpenHashMap<Object, Set<Capability>>());
+        }
+    }
+
+    public void addCapability(Capability cap)
+    {
+        m_capSet.add(cap);
+
+        // Index capability.
+        for (Entry<String, Map<Object, Set<Capability>>> entry : m_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)
+    {
+        Set<Capability> caps = index.get(capValue);
+        if (caps == null)
+        {
+            caps = new CopyOnWriteSet<Capability>();
+            index.put(capValue, caps);
+        }
+        caps.add(cap);
+    }
+
+    public void removeCapability(Capability cap)
+    {
+        if (m_capSet.remove(cap))
+        {
+            for (Entry<String, Map<Object, Set<Capability>>> entry : m_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(m_capSet, sf);
+        return (obeyMandatory)
+            ? matchMandatory(matches, sf)
+            : matches;
+    }
+
+    private Set<Capability> match(Set<Capability> caps, SimpleFilter sf)
+    {
+        Set<Capability> matches = new HashSet<Capability>(128);
+
+        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 (int i = 0; i < sfs.size(); i++)
+            {
+                matches.addAll(match(caps, 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.
+            matches.addAll(caps);
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; i < sfs.size(); i++)
+            {
+                matches.removeAll(match(caps, sfs.get(i)));
+            }
+        }
+        else
+        {
+            Map<Object, Set<Capability>> index = m_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 (Iterator<Capability> it = caps.iterator(); it.hasNext(); )
+                {
+                    Capability cap = it.next();
+                    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);
+    }
+
+    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 (int i = 0; i < sfs.size(); i++)
+            {
+                matched = !(matchesInternal(cap, sfs.get(i)));
+            }
+        }
+        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 = ClauseParser.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 (int i = 0; i < list.size(); i++)
+            {
+                SimpleFilter sf2 = (SimpleFilter) list.get(i);
+                if ((sf2.getName() != null)
+                    && sf2.getName().equals(attrName))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static final Class<?>[] STRING_CLASS = new Class[] { String.class };
+
+    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(((Comparable) lhs), rhs);
+                case SimpleFilter.SUBSTRING :
+                    return SimpleFilter.compareSubstring((List<String>) rhs, (String) lhs);
+                default:
+                    throw new RuntimeException(
+                        "Unknown comparison operator: " + op);
+            }
+        }
+        // Booleans do not implement comparable, so special case them.
+        else if (lhs instanceof Boolean)
+        {
+            Object rhs;
+            try
+            {
+                rhs = coerceType(lhs, (String) rhsUnknown);
+            }
+            catch (Exception ex)
+            {
+                return false;
+            }
+
+            switch (op)
+            {
+                case SimpleFilter.EQ :
+                case SimpleFilter.GTE :
+                case SimpleFilter.LTE :
+                case SimpleFilter.APPROX :
+                    return (lhs.equals(rhs));
+                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 (Iterator iter = ((Collection) lhs).iterator(); iter.hasNext(); )
+            {
+                if (compare(iter.next(), rhsUnknown, op))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        // Spec says SUBSTRING is false for all types other than string.
+        if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String))
+        {
+            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)
+    {
+        StringBuffer sb = new StringBuffer(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 = null;
+        try
+        {
+            // The Character class is a special case, since its constructor
+            // does not take a string, so handle it separately.
+            if (lhs instanceof Character)
+            {
+                rhs = new Character(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(new Object[] { 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 convertArrayToList(Object array)
+    {
+        int len = Array.getLength(array);
+        List list = new ArrayList(len);
+        for (int i = 0; i < len; i++)
+        {
+            list.add(Array.get(array, i));
+        }
+        return list;
+    }
+}

Added: felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/ClauseParser.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/ClauseParser.java?rev=1667217&view=auto
==============================================================================
--- felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/ClauseParser.java (added)
+++ felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/ClauseParser.java Tue Mar 17 09:38:45 2015
@@ -0,0 +1,296 @@
+/*
+ * 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.resolver.test;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ClauseParser {
+
+    private static final char EOF = (char) -1;
+
+    private static char charAt(int pos, String headers, int length)
+    {
+        if (pos >= length)
+        {
+            return EOF;
+        }
+        return headers.charAt(pos);
+    }
+
+    private static final int CLAUSE_START = 0;
+    private static final int PARAMETER_START = 1;
+    private static final int KEY = 2;
+    private static final int DIRECTIVE_OR_TYPEDATTRIBUTE = 4;
+    private static final int ARGUMENT = 8;
+    private static final int VALUE = 16;
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static List<ParsedHeaderClause> parseStandardHeader(String header)
+    {
+        List<ParsedHeaderClause> clauses = new ArrayList<ParsedHeaderClause>();
+        if (header == null)
+        {
+            return clauses;
+        }
+        ParsedHeaderClause clause = null;
+        String key = null;
+        Map targetMap = null;
+        int state = CLAUSE_START;
+        int currentPosition = 0;
+        int startPosition = 0;
+        int length = header.length();
+        boolean quoted = false;
+        boolean escaped = false;
+
+        char currentChar = EOF;
+        do
+        {
+            currentChar = charAt(currentPosition, header, length);
+            switch (state)
+            {
+            case CLAUSE_START:
+                clause = new ParsedHeaderClause();
+                clauses.add(clause);
+                state = PARAMETER_START;
+            case PARAMETER_START:
+                startPosition = currentPosition;
+                state = KEY;
+            case KEY:
+                switch (currentChar)
+                {
+                case ':':
+                case '=':
+                    key = header.substring(startPosition, currentPosition).trim();
+                    startPosition = currentPosition + 1;
+                    targetMap = clause.attrs;
+                    state = currentChar == ':' ? DIRECTIVE_OR_TYPEDATTRIBUTE : ARGUMENT;
+                    break;
+                case EOF:
+                case ',':
+                case ';':
+                    clause.paths.add(header.substring(startPosition, currentPosition).trim());
+                    state = currentChar == ',' ? CLAUSE_START : PARAMETER_START;
+                    break;
+                default:
+                    break;
+                }
+                currentPosition++;
+                break;
+            case DIRECTIVE_OR_TYPEDATTRIBUTE:
+                switch(currentChar)
+                {
+                case '=':
+                    if (startPosition != currentPosition)
+                    {
+                        clause.types.put(key, header.substring(startPosition, currentPosition).trim());
+                    }
+                    else
+                    {
+                        targetMap = clause.dirs;
+                    }
+                    state = ARGUMENT;
+                    startPosition = currentPosition + 1;
+                    break;
+                default:
+                    break;
+                }
+                currentPosition++;
+                break;
+            case ARGUMENT:
+                if (currentChar == '\"')
+                {
+                    quoted = true;
+                    currentPosition++;
+                }
+                else
+                {
+                    quoted = false;
+                }
+                if (!Character.isWhitespace(currentChar)) {
+                    state = VALUE;
+                }
+                else {
+                    currentPosition++;
+                }
+                break;
+            case VALUE:
+                if (escaped)
+                {
+                    escaped = false;
+                }
+                else
+                {
+                    if (currentChar == '\\' )
+                    {
+                        escaped = true;
+                    }
+                    else if (quoted && currentChar == '\"')
+                    {
+                        quoted = false;
+                    }
+                    else if (!quoted)
+                    {
+                        String value = null;
+                        switch(currentChar)
+                        {
+                        case EOF:
+                        case ';':
+                        case ',':
+                            value = header.substring(startPosition, currentPosition).trim();
+                            if (value.startsWith("\"") && value.endsWith("\""))
+                            {
+                                value = value.substring(1, value.length() - 1);
+                            }
+                            if (targetMap.put(key, value) != null)
+                            {
+                                throw new IllegalArgumentException(
+                                        "Duplicate '" + key + "' in: " + header);
+                            }
+                            state = currentChar == ';' ? PARAMETER_START : CLAUSE_START;
+                            break;
+                        default:
+                            break;
+                        }
+                    }
+                }
+                currentPosition++;
+                break;
+            default:
+                break;
+            }
+        } while ( currentChar != EOF);
+
+        if (state > PARAMETER_START)
+        {
+            throw new IllegalArgumentException("Unable to parse header: " + header);
+        }
+        return clauses;
+    }
+
+    public static List<String> parseDelimitedString(String value, String delim)
+    {
+        return parseDelimitedString(value, delim, true);
+    }
+
+    /**
+     * Parses delimited string and returns an array containing the tokens. This
+     * parser obeys quotes, so the delimiter character will be ignored if it is
+     * inside of a quote. This method assumes that the quote character is not
+     * included in the set of delimiter characters.
+     * @param value the delimited string to parse.
+     * @param delim the characters delimiting the tokens.
+     * @return a list of string or an empty list if there are none.
+     **/
+    public static List<String> parseDelimitedString(String value, String delim, boolean trim)
+    {
+        if (value == null)
+        {
+            value = "";
+        }
+
+        List<String> list = new ArrayList();
+
+        int CHAR = 1;
+        int DELIMITER = 2;
+        int STARTQUOTE = 4;
+        int ENDQUOTE = 8;
+
+        StringBuffer sb = new StringBuffer();
+
+        int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+        boolean isEscaped = false;
+        for (int i = 0; i < value.length(); i++)
+        {
+            char c = value.charAt(i);
+
+            boolean isDelimiter = (delim.indexOf(c) >= 0);
+
+            if (!isEscaped && (c == '\\'))
+            {
+                isEscaped = true;
+                continue;
+            }
+
+            if (isEscaped)
+            {
+                sb.append(c);
+            }
+            else if (isDelimiter && ((expecting & DELIMITER) > 0))
+            {
+                if (trim)
+                {
+                    list.add(sb.toString().trim());
+                }
+                else
+                {
+                    list.add(sb.toString());
+                }
+                sb.delete(0, sb.length());
+                expecting = (CHAR | DELIMITER | STARTQUOTE);
+            }
+            else if ((c == '"') && ((expecting & STARTQUOTE) > 0))
+            {
+                sb.append(c);
+                expecting = CHAR | ENDQUOTE;
+            }
+            else if ((c == '"') && ((expecting & ENDQUOTE) > 0))
+            {
+                sb.append(c);
+                expecting = (CHAR | STARTQUOTE | DELIMITER);
+            }
+            else if ((expecting & CHAR) > 0)
+            {
+                sb.append(c);
+            }
+            else
+            {
+                throw new IllegalArgumentException("Invalid delimited string: " + value);
+            }
+
+            isEscaped = false;
+        }
+
+        if (sb.length() > 0)
+        {
+            if (trim)
+            {
+                list.add(sb.toString().trim());
+            }
+            else
+            {
+                list.add(sb.toString());
+            }
+        }
+
+        return list;
+    }
+
+
+    static class ParsedHeaderClause {
+        public final List<String> paths = new ArrayList<String>();
+        public final Map<String, String> dirs = new LinkedHashMap<String, String>();
+        public final Map<String, Object> attrs = new LinkedHashMap<String, Object>();
+        public final Map<String, String> types = new LinkedHashMap<String, String>();
+    }
+
+}

Modified: felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/GenericCapability.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/GenericCapability.java?rev=1667217&r1=1667216&r2=1667217&view=diff
==============================================================================
--- felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/GenericCapability.java (original)
+++ felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/GenericCapability.java Tue Mar 17 09:38:45 2015
@@ -20,8 +20,7 @@ package org.apache.felix.resolver.test;
 
 import java.util.HashMap;
 import java.util.Map;
-import org.osgi.framework.namespace.IdentityNamespace;
-import org.osgi.framework.namespace.PackageNamespace;
+
 import org.osgi.resource.Capability;
 import org.osgi.resource.Resource;
 
@@ -35,7 +34,7 @@ class GenericCapability implements Capab
     public GenericCapability(Resource resource, String namespace)
     {
         m_resource = resource;
-        m_namespace = namespace;
+        m_namespace = namespace.intern();
         m_dirs = new HashMap<String, String>();
         m_attrs = new HashMap<String, Object>();
     }
@@ -47,7 +46,7 @@ class GenericCapability implements Capab
 
     public void addDirective(String name, String value)
     {
-        m_dirs.put(name, value);
+        m_dirs.put(name.intern(), value);
     }
 
     public Map<String, String> getDirectives()
@@ -57,7 +56,7 @@ class GenericCapability implements Capab
 
     public void addAttribute(String name, Object value)
     {
-        m_attrs.put(name, value);
+        m_attrs.put(name.intern(), value);
     }
 
     public Map<String, Object> getAttributes()

Modified: felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/GenericRequirement.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/GenericRequirement.java?rev=1667217&r1=1667216&r2=1667217&view=diff
==============================================================================
--- felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/GenericRequirement.java (original)
+++ felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/GenericRequirement.java Tue Mar 17 09:38:45 2015
@@ -20,8 +20,7 @@ package org.apache.felix.resolver.test;
 
 import java.util.HashMap;
 import java.util.Map;
-import org.osgi.framework.namespace.IdentityNamespace;
-import org.osgi.framework.namespace.PackageNamespace;
+
 import org.osgi.resource.Requirement;
 import org.osgi.resource.Resource;
 
@@ -35,7 +34,7 @@ class GenericRequirement implements Requ
     public GenericRequirement(Resource resource, String namespace)
     {
         m_resource = resource;
-        m_namespace = namespace;
+        m_namespace = namespace.intern();
         m_dirs = new HashMap<String, String>();
         m_attrs = new HashMap<String, Object>();
     }
@@ -47,7 +46,7 @@ class GenericRequirement implements Requ
 
     public void addDirective(String name, String value)
     {
-        m_dirs.put(name, value);
+        m_dirs.put(name.intern(), value);
     }
 
     public Map<String, String> getDirectives()
@@ -57,7 +56,7 @@ class GenericRequirement implements Requ
 
     public void addAttribute(String name, Object value)
     {
-        m_attrs.put(name, value);
+        m_attrs.put(name.intern(), value);
     }
 
     public Map<String, Object> getAttributes()

Added: felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/IterativeResolver.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/IterativeResolver.java?rev=1667217&view=auto
==============================================================================
--- felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/IterativeResolver.java (added)
+++ felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/IterativeResolver.java Tue Mar 17 09:38:45 2015
@@ -0,0 +1,195 @@
+/*
+ * 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.resolver.test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wire;
+import org.osgi.resource.Wiring;
+import org.osgi.service.resolver.HostedCapability;
+import org.osgi.service.resolver.ResolutionException;
+import org.osgi.service.resolver.ResolveContext;
+import org.osgi.service.resolver.Resolver;
+
+public class IterativeResolver implements Resolver {
+
+    private final Resolver resolver;
+
+    public IterativeResolver(Resolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public Map<Resource, List<Wire>> resolve(final ResolveContext context) throws ResolutionException {
+
+        final Map<Resource, List<Wire>> wires = new HashMap<Resource, List<Wire>>();
+        final Map<Resource, List<Wire>> invertedWires = new HashMap<Resource, List<Wire>>();
+        Collection<Resource> resources = context.getMandatoryResources();
+        for (final Resource resource : resources) {
+            // Build wiring
+            invertedWires.clear();
+            for (Resource res : wires.keySet()) {
+                for (Wire wire : wires.get(res)) {
+                    List<Wire> w = invertedWires.get(wire.getProvider());
+                    if (w == null) {
+                        w = new ArrayList<Wire>();
+                        invertedWires.put(wire.getProvider(), w);
+                    }
+                    w.add(wire);
+                }
+            }
+            final Map<Resource, Wiring> wiring = new HashMap<Resource, Wiring>();
+            for (Resource res : wires.keySet()) {
+                wiring.put(res, new SimpleWiring(res, wires, invertedWires));
+            }
+            // Resolve the new resource
+            Map<Resource, List<Wire>> tempWires = resolver.resolve(new ResolveContext() {
+                @Override
+                public Collection<Resource> getMandatoryResources() {
+                    return Collections.singleton(resource);
+                }
+
+                @Override
+                public Collection<Resource> getOptionalResources() {
+                    return context.getOptionalResources();
+                }
+
+                @Override
+                public List<Capability> findProviders(Requirement requirement) {
+                    return context.findProviders(requirement);
+                }
+
+                @Override
+                public int insertHostedCapability(List<Capability> capabilities, HostedCapability hostedCapability) {
+                    return context.insertHostedCapability(capabilities, hostedCapability);
+                }
+
+                @Override
+                public boolean isEffective(Requirement requirement) {
+                    return context.isEffective(requirement);
+                }
+
+                @Override
+                public Map<Resource, Wiring> getWirings() {
+                    return wiring;
+                }
+            });
+            // Merge wiring
+            wires.putAll(tempWires);
+        }
+
+        return wires;
+    }
+
+    private class SimpleWiring implements Wiring {
+        final Resource resource;
+        final Map<Resource, List<Wire>> wires;
+        final Map<Resource, List<Wire>> invertedWires;
+        List<Capability> resourceCapabilities;
+        List<Requirement> resourceRequirements;
+
+        private SimpleWiring(Resource resource, Map<Resource, List<Wire>> wires, Map<Resource, List<Wire>> invertedWires) {
+            this.resource = resource;
+            this.wires = wires;
+            this.invertedWires = invertedWires;
+        }
+
+        public List<Capability> getResourceCapabilities(String namespace) {
+            if (resourceCapabilities == null) {
+                resourceCapabilities = new ArrayList<Capability>();
+                for (Wire wire : invertedWires.get(resource)) {
+                    if (!resourceCapabilities.contains(wire.getCapability())) {
+                        resourceCapabilities.add(wire.getCapability());
+                    }
+                }
+            }
+            if (namespace != null) {
+                List<Capability> caps = new ArrayList<Capability>();
+                for (Capability cap : resourceCapabilities) {
+                    if (namespace.equals(cap.getNamespace())) {
+                        caps.add(cap);
+                    }
+                }
+                return caps;
+            }
+            return resourceCapabilities;
+        }
+
+        public List<Requirement> getResourceRequirements(String namespace) {
+            if (resourceRequirements == null) {
+                resourceRequirements = new ArrayList<Requirement>();
+                for (Wire wire : wires.get(resource)) {
+                    if (!resourceRequirements.contains(wire.getRequirement())) {
+                        resourceRequirements.add(wire.getRequirement());
+                    }
+                }
+            }
+            if (namespace != null) {
+                List<Requirement> reqs = new ArrayList<Requirement>();
+                for (Requirement req : resourceRequirements) {
+                    if (namespace.equals(req.getNamespace())) {
+                        reqs.add(req);
+                    }
+                }
+                return reqs;
+            }
+            return resourceRequirements;
+        }
+
+        public List<Wire> getProvidedResourceWires(String namespace) {
+            List<Wire> providedWires = invertedWires.get(resource);
+            if (namespace != null) {
+                List<Wire> wires = new ArrayList<Wire>();
+                for (Wire wire : providedWires) {
+                    if (namespace.equals(wire.getRequirement().getNamespace())) {
+                        wires.add(wire);
+                    }
+                }
+                return wires;
+            }
+            return providedWires;
+        }
+
+        public List<Wire> getRequiredResourceWires(String namespace) {
+            List<Wire> requiredWires = wires.get(resource);
+            if (namespace != null) {
+                List<Wire> wires = new ArrayList<Wire>();
+                for (Wire wire : requiredWires) {
+                    if (namespace.equals(wire.getCapability().getNamespace())) {
+                        wires.add(wire);
+                    }
+                }
+                return wires;
+            }
+            return requiredWires;
+        }
+
+        public Resource getResource() {
+            return resource;
+        }
+    }
+
+}

Added: felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/JsonReader.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/JsonReader.java?rev=1667217&view=auto
==============================================================================
--- felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/JsonReader.java (added)
+++ felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/JsonReader.java Tue Mar 17 09:38:45 2015
@@ -0,0 +1,350 @@
+/*
+ * 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.resolver.test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class JsonReader {
+
+    public static Object read(Reader reader) throws IOException {
+        return new JsonReader(reader).parse();
+    }
+
+    public static Object read(InputStream is) throws IOException {
+        return new JsonReader(new InputStreamReader(is)).parse();
+    }
+
+    //
+    // Implementation
+    //
+
+    private final Reader reader;
+    private final StringBuilder recorder;
+    private int current;
+    private int line = 1;
+    private int column = 0;
+
+    JsonReader(Reader reader) {
+        this.reader = reader;
+        recorder = new StringBuilder();
+    }
+
+    public Object parse() throws IOException {
+        read();
+        skipWhiteSpace();
+        Object result = readValue();
+        skipWhiteSpace();
+        if (!endOfText()) {
+            throw error("Unexpected character");
+        }
+        return result;
+    }
+
+    private Object readValue() throws IOException {
+        switch (current) {
+            case 'n':
+                return readNull();
+            case 't':
+                return readTrue();
+            case 'f':
+                return readFalse();
+            case '"':
+                return readString();
+            case '[':
+                return readArray();
+            case '{':
+                return readObject();
+            case '-':
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+                return readNumber();
+            default:
+                throw expected("value");
+        }
+    }
+
+    private Collection<?> readArray() throws IOException {
+        read();
+        Collection<Object> array = new ArrayList<Object>();
+        skipWhiteSpace();
+        if (readChar(']')) {
+            return array;
+        }
+        do {
+            skipWhiteSpace();
+            array.add(readValue());
+            skipWhiteSpace();
+        } while (readChar(','));
+        if (!readChar(']')) {
+            throw expected("',' or ']'");
+        }
+        return array;
+    }
+
+    private Map<String, Object> readObject() throws IOException {
+        read();
+        Map<String, Object> object = new LinkedHashMap<String, Object>();
+        skipWhiteSpace();
+        if (readChar('}')) {
+            return object;
+        }
+        do {
+            skipWhiteSpace();
+            String name = readName();
+            skipWhiteSpace();
+            if (!readChar(':')) {
+                throw expected("':'");
+            }
+            skipWhiteSpace();
+            object.put(name, readValue());
+            skipWhiteSpace();
+        } while (readChar(','));
+        if (!readChar('}')) {
+            throw expected("',' or '}'");
+        }
+        return object;
+    }
+
+    private Object readNull() throws IOException {
+        read();
+        readRequiredChar('u');
+        readRequiredChar('l');
+        readRequiredChar('l');
+        return null;
+    }
+
+    private Boolean readTrue() throws IOException {
+        read();
+        readRequiredChar('r');
+        readRequiredChar('u');
+        readRequiredChar('e');
+        return Boolean.TRUE;
+    }
+
+    private Boolean readFalse() throws IOException {
+        read();
+        readRequiredChar('a');
+        readRequiredChar('l');
+        readRequiredChar('s');
+        readRequiredChar('e');
+        return Boolean.FALSE;
+    }
+
+    private void readRequiredChar(char ch) throws IOException {
+        if (!readChar(ch)) {
+            throw expected("'" + ch + "'");
+        }
+    }
+
+    private String readString() throws IOException {
+        read();
+        recorder.setLength(0);
+        while (current != '"') {
+            if (current == '\\') {
+                readEscape();
+            } else if (current < 0x20) {
+                throw expected("valid string character");
+            } else {
+                recorder.append((char) current);
+                read();
+            }
+        }
+        read();
+        return recorder.toString();
+    }
+
+    private void readEscape() throws IOException {
+        read();
+        switch (current) {
+            case '"':
+            case '/':
+            case '\\':
+                recorder.append((char) current);
+                break;
+            case 'b':
+                recorder.append('\b');
+                break;
+            case 'f':
+                recorder.append('\f');
+                break;
+            case 'n':
+                recorder.append('\n');
+                break;
+            case 'r':
+                recorder.append('\r');
+                break;
+            case 't':
+                recorder.append('\t');
+                break;
+            case 'u':
+                char[] hexChars = new char[4];
+                for (int i = 0; i < 4; i++) {
+                    read();
+                    if (!isHexDigit(current)) {
+                        throw expected("hexadecimal digit");
+                    }
+                    hexChars[i] = (char) current;
+                }
+                recorder.append((char) Integer.parseInt(String.valueOf(hexChars), 16));
+                break;
+            default:
+                throw expected("valid escape sequence");
+        }
+        read();
+    }
+
+    private Number readNumber() throws IOException {
+        recorder.setLength(0);
+        readAndAppendChar('-');
+        int firstDigit = current;
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        if (firstDigit != '0') {
+            while (readAndAppendDigit()) {
+            }
+        }
+        readFraction();
+        readExponent();
+        return Double.parseDouble(recorder.toString());
+    }
+
+    private boolean readFraction() throws IOException {
+        if (!readAndAppendChar('.')) {
+            return false;
+        }
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        while (readAndAppendDigit()) {
+        }
+        return true;
+    }
+
+    private boolean readExponent() throws IOException {
+        if (!readAndAppendChar('e') && !readAndAppendChar('E')) {
+            return false;
+        }
+        if (!readAndAppendChar('+')) {
+            readAndAppendChar('-');
+        }
+        if (!readAndAppendDigit()) {
+            throw expected("digit");
+        }
+        while (readAndAppendDigit()) {
+        }
+        return true;
+    }
+
+    private String readName() throws IOException {
+        if (current != '"') {
+            throw expected("name");
+        }
+        readString();
+        return recorder.toString();
+    }
+
+    private boolean readAndAppendChar(char ch) throws IOException {
+        if (current != ch) {
+            return false;
+        }
+        recorder.append(ch);
+        read();
+        return true;
+    }
+
+    private boolean readChar(char ch) throws IOException {
+        if (current != ch) {
+            return false;
+        }
+        read();
+        return true;
+    }
+
+    private boolean readAndAppendDigit() throws IOException {
+        if (!isDigit(current)) {
+            return false;
+        }
+        recorder.append((char) current);
+        read();
+        return true;
+    }
+
+    private void skipWhiteSpace() throws IOException {
+        while (isWhiteSpace(current) && !endOfText()) {
+            read();
+        }
+    }
+
+    private void read() throws IOException {
+        if (endOfText()) {
+            throw error("Unexpected end of input");
+        }
+        column++;
+        if (current == '\n') {
+            line++;
+            column = 0;
+        }
+        current = reader.read();
+    }
+
+    private boolean endOfText() {
+        return current == -1;
+    }
+
+    private IOException expected(String expected) {
+        if (endOfText()) {
+            return error("Unexpected end of input");
+        }
+        return error("Expected " + expected);
+    }
+
+    private IOException error(String message) {
+        return new IOException(message + " at " + line + ":" + column);
+    }
+
+    private static boolean isWhiteSpace(int ch) {
+        return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
+    }
+
+    private static boolean isDigit(int ch) {
+        return ch >= '0' && ch <= '9';
+    }
+
+    private static boolean isHexDigit(int ch) {
+        return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F';
+    }
+
+}

Modified: felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/ResourceImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/ResourceImpl.java?rev=1667217&r1=1667216&r2=1667217&view=diff
==============================================================================
--- felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/ResourceImpl.java (original)
+++ felix/trunk/resolver/src/test/java/org/apache/felix/resolver/test/ResourceImpl.java Tue Mar 17 09:38:45 2015
@@ -30,6 +30,10 @@ public class ResourceImpl implements Res
     private final List<Capability> m_caps;
     private final List<Requirement> m_reqs;
 
+    public ResourceImpl() {
+        m_caps = new ArrayList<Capability>();
+        m_reqs = new ArrayList<Requirement>();
+    }
     public ResourceImpl(String name) {
         this(name, IdentityNamespace.TYPE_BUNDLE);
     }



Mime
View raw message