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);
}