[Sling Feature Model] Refactor FeatureUtil out of the support module
Also move the Resolver API to the resolver module.
diff --git a/src/main/java/org/apache/sling/feature/resolver/ApplicationResolverAssembler.java b/src/main/java/org/apache/sling/feature/resolver/ApplicationResolverAssembler.java
new file mode 100644
index 0000000..84c1e42
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/resolver/ApplicationResolverAssembler.java
@@ -0,0 +1,128 @@
+/*
+ * 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.sling.feature.resolver;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.builder.ApplicationBuilder;
+import org.apache.sling.feature.builder.BuilderContext;
+import org.apache.sling.feature.builder.FeatureProvider;
+import org.apache.sling.feature.io.ArtifactHandler;
+import org.apache.sling.feature.io.ArtifactManager;
+import org.apache.sling.feature.io.IOUtils;
+import org.apache.sling.feature.io.json.FeatureJSONReader;
+import org.apache.sling.feature.io.json.FeatureJSONReader.SubstituteVariables;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ApplicationResolverAssembler {
+ /**
+ * Assemble an application based on the given files.
+ *
+ * Read the features and assemble the application
+ * @param app The optional application to use as a base.
+ * @param featureFiles The feature files.
+ * @param artifactManager The artifact manager
+ * @param fr
+ * @return The assembled application
+ * @throws IOException If a feature can't be read or no feature is found.
+ * @see #getFeatureFiles(File, String...)
+ */
+ public static Application assembleApplication(
+ Application app,
+ final ArtifactManager artifactManager,
+ final FeatureResolver fr,
+ final String... featureFiles)
+ throws IOException {
+ final List<Feature> features = new ArrayList<>();
+ for(final String initFile : featureFiles) {
+ final Feature f = IOUtils.getFeature(initFile, artifactManager, SubstituteVariables.RESOLVE);
+ features.add(f);
+ }
+
+ return assembleApplication(app, artifactManager, fr, features.toArray(new Feature[0]));
+ }
+
+ public static Feature[] sortFeatures(final FeatureResolver fr,
+ final Feature... features) {
+ final List<Feature> featureList = new ArrayList<>();
+ for(final Feature f : features) {
+ featureList.add(f);
+ }
+
+ final List<Feature> sortedFeatures;
+ if (fr != null) {
+ // order by dependency chain
+ final List<FeatureResource> sortedResources = fr.orderResources(featureList);
+
+ sortedFeatures = new ArrayList<>();
+ for (final FeatureResource rsrc : sortedResources) {
+ Feature f = rsrc.getFeature();
+ if (!sortedFeatures.contains(f)) {
+ sortedFeatures.add(rsrc.getFeature());
+ }
+ }
+ } else {
+ sortedFeatures = featureList;
+ Collections.sort(sortedFeatures);
+ }
+ return sortedFeatures.toArray(new Feature[sortedFeatures.size()]);
+ }
+
+ public static Application assembleApplication(
+ Application app,
+ final ArtifactManager artifactManager,
+ final FeatureResolver fr,
+ final Feature... features)
+ throws IOException {
+ if ( features.length == 0 ) {
+ throw new IOException("No features found.");
+ }
+
+ app = ApplicationBuilder.assemble(app, new BuilderContext(new FeatureProvider() {
+
+ @Override
+ public Feature provide(final ArtifactId id) {
+ try {
+ final ArtifactHandler handler = artifactManager.getArtifactHandler("mvn:" + id.toMvnPath());
+ try (final FileReader r = new FileReader(handler.getFile())) {
+ final Feature f = FeatureJSONReader.read(r, handler.getUrl(), SubstituteVariables.RESOLVE);
+ return f;
+ }
+
+ } catch (final IOException e) {
+ // ignore
+ }
+ return null;
+ }
+ }), sortFeatures(fr, features));
+
+ // check framework
+ if ( app.getFramework() == null ) {
+ // use hard coded Apache Felix
+ app.setFramework(IOUtils.getFelixFrameworkId(null));
+ }
+
+ return app;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/resolver/FeatureResolver.java b/src/main/java/org/apache/sling/feature/resolver/FeatureResolver.java
new file mode 100644
index 0000000..f40a9b1
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/resolver/FeatureResolver.java
@@ -0,0 +1,38 @@
+/*
+ * 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.sling.feature.resolver;
+
+import java.util.List;
+
+import org.apache.sling.feature.Feature;
+
+/**
+ * A resolver that can perform operations on the feature model.
+ */
+public interface FeatureResolver extends AutoCloseable {
+ /**
+ * Order the resources in list of features by their dependency chain.
+ * Each feature and its components are resolved. Then all the resources
+ * in the feature are ordered so that each resource is placed before
+ * the requiring feature/resources in the result.
+ *
+ * @param features
+ * The features to order.
+ * @return The ordered resources from the features.
+ */
+ List<FeatureResource> orderResources(List<Feature> features);
+}
diff --git a/src/main/java/org/apache/sling/feature/resolver/FeatureResource.java b/src/main/java/org/apache/sling/feature/resolver/FeatureResource.java
new file mode 100644
index 0000000..b420805
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/resolver/FeatureResource.java
@@ -0,0 +1,52 @@
+/*
+ * 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.sling.feature.resolver;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Feature;
+import org.osgi.framework.Version;
+import org.osgi.resource.Resource;
+
+/**
+ * A Resource that is associated with an Maven Artifact and belongs to a Feature.
+ */
+public interface FeatureResource extends Resource {
+ /**
+ * Obtain the ID of the resource. If the resource is a bundle then this
+ * is the bundle symbolic name.
+ * @return The ID of the resource.
+ */
+ String getId();
+
+ /**
+ * Obtain the version of the resource.
+ * @return The version of the resource.
+ */
+ Version getVersion();
+
+ /**
+ * Obtain the associated (Maven) Artifact.
+ * @return The artifact for this Resource.
+ */
+ Artifact getArtifact();
+
+ /**
+ * Obtain the feature that contains this resource.
+ * @return The feature that contains the resource.
+ */
+ Feature getFeature();
+}
diff --git a/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java b/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java
index 88efe0c..da09e69 100644
--- a/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java
+++ b/src/main/java/org/apache/sling/feature/resolver/FrameworkResolver.java
@@ -38,8 +38,6 @@
import org.apache.sling.feature.resolver.impl.ResolveContextImpl;
import org.apache.sling.feature.scanner.BundleDescriptor;
import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
-import org.apache.sling.feature.support.resolver.FeatureResolver;
-import org.apache.sling.feature.support.resolver.FeatureResource;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
diff --git a/src/main/java/org/apache/sling/feature/resolver/impl/BundleResourceImpl.java b/src/main/java/org/apache/sling/feature/resolver/impl/BundleResourceImpl.java
index 5e28c79..901b141 100644
--- a/src/main/java/org/apache/sling/feature/resolver/impl/BundleResourceImpl.java
+++ b/src/main/java/org/apache/sling/feature/resolver/impl/BundleResourceImpl.java
@@ -20,8 +20,8 @@
import org.apache.felix.utils.resource.RequirementImpl;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.resolver.FeatureResource;
import org.apache.sling.feature.scanner.BundleDescriptor;
-import org.apache.sling.feature.support.resolver.FeatureResource;
import org.apache.sling.feature.support.util.PackageInfo;
import org.osgi.framework.Version;
import org.osgi.framework.VersionRange;
diff --git a/src/main/java/org/apache/sling/feature/resolver/impl/FeatureResourceImpl.java b/src/main/java/org/apache/sling/feature/resolver/impl/FeatureResourceImpl.java
index f61e6e3..4acee42 100644
--- a/src/main/java/org/apache/sling/feature/resolver/impl/FeatureResourceImpl.java
+++ b/src/main/java/org/apache/sling/feature/resolver/impl/FeatureResourceImpl.java
@@ -20,7 +20,7 @@
import org.apache.felix.utils.resource.RequirementImpl;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.support.resolver.FeatureResource;
+import org.apache.sling.feature.resolver.FeatureResource;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.resource.Capability;
diff --git a/src/test/java/org/apache/sling/feature/resolver/AnalyserTest.java b/src/test/java/org/apache/sling/feature/resolver/AnalyserTest.java
new file mode 100644
index 0000000..c4b8110
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/resolver/AnalyserTest.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.resolver;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.Analyser;
+import org.apache.sling.feature.io.ArtifactManager;
+import org.apache.sling.feature.io.ArtifactManagerConfig;
+import org.apache.sling.feature.io.json.FeatureJSONReader;
+import org.apache.sling.feature.io.json.FeatureJSONReader.SubstituteVariables;
+import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.apache.sling.feature.scanner.Scanner;
+import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.fail;
+
+public class AnalyserTest {
+ @Test
+ public void testAnalyserWithCompleteFeature() throws Exception {
+ final Scanner scanner = new Scanner(new ArtifactManagerConfig());
+ final Analyser analyser = new Analyser(scanner);
+ try ( final Reader reader = new InputStreamReader(AnalyserTest.class.getResourceAsStream("/feature_complete.json"),
+ "UTF-8") ) {
+ Feature feature = FeatureJSONReader.read(reader, "feature", SubstituteVariables.RESOLVE);
+
+ Application app = ApplicationResolverAssembler.assembleApplication(null, ArtifactManager.getArtifactManager(new ArtifactManagerConfig()),
+ getTestResolver(), feature);
+
+ analyser.analyse(app);
+ }
+ }
+
+ @Test
+ public void testAnalyserWithInCompleteFeature() throws Exception {
+ final Scanner scanner = new Scanner(new ArtifactManagerConfig());
+ final Analyser analyser = new Analyser(scanner);
+ try ( final Reader reader = new InputStreamReader(AnalyserTest.class.getResourceAsStream("/feature_incomplete.json"),
+ "UTF-8") ) {
+ Feature feature = FeatureJSONReader.read(reader, "feature", SubstituteVariables.RESOLVE);
+
+ Application app = ApplicationResolverAssembler.assembleApplication(null, ArtifactManager.getArtifactManager(new ArtifactManagerConfig()),
+ getTestResolver(), feature);
+
+ try {
+ analyser.analyse(app);
+
+ fail("Expected an exception");
+ }
+ catch (Exception ex) {
+ // Pass
+ }
+ }
+ }
+
+ private FeatureResolver getTestResolver() {
+ return new FeatureResolver() {
+ @Override
+ public void close() throws Exception {
+ }
+
+ @Override
+ public List<FeatureResource> orderResources(List<Feature> features) {
+ try {
+ // Just return the resources in the same order as they are listed in the features
+ List<FeatureResource> l = new ArrayList<>();
+
+ for (Feature f : features) {
+ for (Artifact a : f.getBundles()) {
+ BundleDescriptor bd = getBundleDescriptor(ArtifactManager.getArtifactManager(new ArtifactManagerConfig()), a);
+ l.add(new TestBundleResourceImpl(bd, f));
+ }
+ }
+
+ return l;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private BundleDescriptor getBundleDescriptor(ArtifactManager artifactManager, Artifact b) throws IOException {
+ final File file = artifactManager.getArtifactHandler(b.getId().toMvnUrl()).getFile();
+ if ( file == null ) {
+ throw new IOException("Unable to find file for " + b.getId());
+ }
+
+ return new BundleDescriptorImpl(b, file, -1);
+ }
+ };
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/resolver/FrameworkResolverTest.java b/src/test/java/org/apache/sling/feature/resolver/FrameworkResolverTest.java
index 7094876..8365101 100644
--- a/src/test/java/org/apache/sling/feature/resolver/FrameworkResolverTest.java
+++ b/src/test/java/org/apache/sling/feature/resolver/FrameworkResolverTest.java
@@ -22,8 +22,6 @@
import org.apache.sling.feature.io.ArtifactManagerConfig;
import org.apache.sling.feature.io.json.FeatureJSONReader;
import org.apache.sling.feature.io.json.FeatureJSONReader.SubstituteVariables;
-import org.apache.sling.feature.support.resolver.FeatureResolver;
-import org.apache.sling.feature.support.resolver.FeatureResource;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
diff --git a/src/test/java/org/apache/sling/feature/resolver/TestBundleResourceImpl.java b/src/test/java/org/apache/sling/feature/resolver/TestBundleResourceImpl.java
new file mode 100644
index 0000000..86beec4
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/resolver/TestBundleResourceImpl.java
@@ -0,0 +1,231 @@
+/*
+ * 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.sling.feature.resolver;
+
+import org.apache.felix.utils.resource.CapabilityImpl;
+import org.apache.felix.utils.resource.RequirementImpl;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.apache.sling.feature.support.util.PackageInfo;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+import org.osgi.framework.namespace.BundleNamespace;
+import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
+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 java.util.stream.Collectors;
+
+/**
+ * Implementation of the OSGi Resource interface, used by the test
+ */
+public class TestBundleResourceImpl implements FeatureResource {
+ final Artifact artifact;
+ final String bsn;
+ final Version version;
+ final Map<String, List<Capability>> capabilities;
+ final Map<String, List<Requirement>> requirements;
+ final Feature feature;
+
+ /**
+ * Create a resource based on a BundleDescriptor.
+ * @param bd The BundleDescriptor to represent.
+ */
+ public TestBundleResourceImpl(BundleDescriptor bd, Feature feat) {
+ artifact = bd.getArtifact();
+ bsn = bd.getBundleSymbolicName();
+ version = bd.getArtifact().getId().getOSGiVersion();
+ feature = feat;
+
+ Map<String, List<Capability>> caps = new HashMap<>();
+ for (Capability c : bd.getCapabilities()) {
+ List<Capability> l = caps.get(c.getNamespace());
+ if (l == null) {
+ l = new ArrayList<>();
+ caps.put(c.getNamespace(), l);
+ }
+ l.add(new CapabilityImpl(this, c));
+ }
+
+ // Add the package capabilities (export package)
+ List<Capability> pkgCaps = new ArrayList<>();
+ for(PackageInfo exported : bd.getExportedPackages()) {
+ Map<String, Object> attrs = new HashMap<>();
+ attrs.put(PackageNamespace.PACKAGE_NAMESPACE, exported.getName());
+ attrs.put(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, exported.getPackageVersion());
+ attrs.put(PackageNamespace.CAPABILITY_BUNDLE_SYMBOLICNAME_ATTRIBUTE, bd.getBundleSymbolicName());
+ attrs.put(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, new Version(bd.getBundleVersion()));
+ pkgCaps.add(new CapabilityImpl(this, PackageNamespace.PACKAGE_NAMESPACE, null, attrs));
+ }
+ caps.put(PackageNamespace.PACKAGE_NAMESPACE, Collections.unmodifiableList(pkgCaps));
+
+ // Add the bundle capability
+ Map<String, Object> battrs = new HashMap<>();
+ battrs.put(BundleNamespace.BUNDLE_NAMESPACE, bd.getBundleSymbolicName());
+ battrs.put(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, new Version(bd.getBundleVersion()));
+ Capability bundleCap = new CapabilityImpl(this, BundleNamespace.BUNDLE_NAMESPACE, null, battrs);
+ caps.put(BundleNamespace.BUNDLE_NAMESPACE, Collections.singletonList(bundleCap));
+ capabilities = Collections.unmodifiableMap(caps);
+
+ Map<String, List<Requirement>> reqs = new HashMap<>();
+ for (Requirement r : bd.getRequirements()) {
+ List<Requirement> l = reqs.get(r.getNamespace());
+ if (l == null) {
+ l = new ArrayList<>();
+ reqs.put(r.getNamespace(), l);
+ }
+ // Add the requirement and associate with this resource
+ l.add(new RequirementImpl(this, r));
+ }
+
+ // TODO What do we do with the execution environment?
+ reqs.remove(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE);
+
+ // Add the package requirements (import package)
+ List<Requirement> pkgReqs = new ArrayList<>();
+ for(PackageInfo imported : bd.getImportedPackages()) {
+ Map<String, String> dirs = new HashMap<>();
+ VersionRange range = imported.getPackageVersionRange();
+ String rangeFilter;
+ if (range != null) {
+ rangeFilter = range.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+ } else {
+ rangeFilter = "";
+ }
+ dirs.put(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE,
+ "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + imported.getName() + ")" + rangeFilter + ")");
+ if (imported.isOptional())
+ dirs.put(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE,
+ PackageNamespace.RESOLUTION_OPTIONAL);
+ pkgReqs.add(new RequirementImpl(this, PackageNamespace.PACKAGE_NAMESPACE, dirs, null));
+ }
+ reqs.put(PackageNamespace.PACKAGE_NAMESPACE, Collections.unmodifiableList(pkgReqs));
+ requirements = Collections.unmodifiableMap(reqs);
+ }
+
+ @Override
+ public Artifact getArtifact() {
+ return artifact;
+ }
+
+ @Override
+ public String getId() {
+ return bsn;
+ }
+
+ @Override
+ public Version getVersion() {
+ return version;
+ }
+
+ @Override
+ public List<Capability> getCapabilities(String namespace) {
+ if (namespace == null) {
+ return capabilities.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
+ }
+
+ List<Capability> caps = capabilities.get(namespace);
+ if (caps == null)
+ return Collections.emptyList();
+ return caps;
+ }
+
+ @Override
+ public List<Requirement> getRequirements(String namespace) {
+ if (namespace == null) {
+ return requirements.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
+ }
+
+ List<Requirement> reqs = requirements.get(namespace);
+ if (reqs == null)
+ return Collections.emptyList();
+ return reqs;
+ }
+
+ @Override
+ public Feature getFeature() {
+ return feature;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((artifact == null) ? 0 : artifact.hashCode());
+ result = prime * result + ((bsn == null) ? 0 : bsn.hashCode());
+ result = prime * result + ((capabilities == null) ? 0 : capabilities.hashCode());
+ result = prime * result + ((feature == null) ? 0 : feature.hashCode());
+ result = prime * result + ((requirements == null) ? 0 : requirements.hashCode());
+ result = prime * result + ((version == null) ? 0 : version.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ TestBundleResourceImpl other = (TestBundleResourceImpl) obj;
+ if (artifact == null) {
+ if (other.artifact != null)
+ return false;
+ } else if (!artifact.equals(other.artifact))
+ return false;
+ if (bsn == null) {
+ if (other.bsn != null)
+ return false;
+ } else if (!bsn.equals(other.bsn))
+ return false;
+ if (capabilities == null) {
+ if (other.capabilities != null)
+ return false;
+ } else if (!capabilities.equals(other.capabilities))
+ return false;
+ if (feature == null) {
+ if (other.feature != null)
+ return false;
+ } else if (!feature.equals(other.feature))
+ return false;
+ if (requirements == null) {
+ if (other.requirements != null)
+ return false;
+ } else if (!requirements.equals(other.requirements))
+ return false;
+ if (version == null) {
+ if (other.version != null)
+ return false;
+ } else if (!version.equals(other.version))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "BundleResourceImpl [" + bsn + " " + version + "]";
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/resolver/impl/BundleResourceImplTest.java b/src/test/java/org/apache/sling/feature/resolver/impl/BundleResourceImplTest.java
index 3ca9870..eb307d2 100644
--- a/src/test/java/org/apache/sling/feature/resolver/impl/BundleResourceImplTest.java
+++ b/src/test/java/org/apache/sling/feature/resolver/impl/BundleResourceImplTest.java
@@ -21,10 +21,10 @@
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.resolver.FeatureResource;
import org.apache.sling.feature.scanner.BundleDescriptor;
import org.apache.sling.feature.scanner.Descriptor;
import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
-import org.apache.sling.feature.support.resolver.FeatureResource;
import org.apache.sling.feature.support.util.PackageInfo;
import org.junit.Test;
import org.mockito.Mockito;
diff --git a/src/test/resources/feature_complete.json b/src/test/resources/feature_complete.json
new file mode 100644
index 0000000..6271a9c
--- /dev/null
+++ b/src/test/resources/feature_complete.json
@@ -0,0 +1,82 @@
+{
+ "id" : "test/test.complete/0.1",
+
+ "bundles" : [
+ {
+ "id" : "org.apache.sling/org.apache.sling.commons.log/5.0.0",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.apache.sling/org.apache.sling.commons.logservice/1.0.6",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.slf4j/jcl-over-slf4j/1.7.21",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.slf4j/log4j-over-slf4j/1.7.21",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.slf4j/slf4j-api/1.7.21",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.configadmin/1.8.14",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.eventadmin/1.4.8",
+ "start-order" : 4
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.metatype/1.1.2",
+ "start-order" : 4
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.scr/2.0.12",
+ "start-order" : 4
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.http.jetty/3.4.2",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.http.servlet-api/1.1.2",
+ "start-order" : 5
+ },
+ {
+ "id" : "commons-io/commons-io/2.5",
+ "start-order" : 5
+ },
+ {
+ "id" : "commons-fileupload/commons-fileupload/1.3.2",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.inventory/1.0.4",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.ds/2.0.6",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.event/1.1.6",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.packageadmin/1.0.4",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.webconsole/4.3.4",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.sling/org.apache.sling.commons.log.webconsole/1.0.0",
+ "start-order" : 5
+ }
+ ]
+}
diff --git a/src/test/resources/feature_incomplete.json b/src/test/resources/feature_incomplete.json
new file mode 100644
index 0000000..514e878
--- /dev/null
+++ b/src/test/resources/feature_incomplete.json
@@ -0,0 +1,82 @@
+{
+ "id" : "test/test.incomplete/0.1",
+
+ "bundles" : [
+ {
+ "id" : "org.apache.sling/org.apache.sling.commons.log/5.0.0",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.apache.sling/org.apache.sling.commons.logservice/1.0.6",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.slf4j/jcl-over-slf4j/1.7.21",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.slf4j/log4j-over-slf4j/1.7.21",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.slf4j/slf4j-api/1.7.21",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.configadmin/1.8.14",
+ "start-order" : 1
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.eventadmin/1.4.8",
+ "start-order" : 4
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.metatype/1.1.2",
+ "start-order" : 4
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.scr/2.0.12",
+ "start-order" : 4
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.http.servlet-api/1.1.2",
+ "start-order" : 5
+ },
+ {
+ "id" : "commons-io/commons-io/2.5",
+ "start-order" : 5
+ },
+ {
+ "id" : "commons-fileupload/commons-fileupload/1.3.2",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.inventory/1.0.4",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.ds/2.0.6",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.event/1.1.6",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.webconsole.plugins.packageadmin/1.0.4",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.felix/org.apache.felix.webconsole/4.3.4",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.sling/org.apache.sling.commons.log.webconsole/1.0.0",
+ "start-order" : 5
+ },
+ {
+ "id" : "org.apache.sling/org.apache.sling.i18n/2.5.8",
+ "start-order" : 6
+ }
+ ]
+}